Qt-network (TCP & UDP)

1. TCP协议数据传输

重点:在 .pro 项目文件中要加入 network 模块

cpp 复制代码
QT  +=  core gui network
  • QTcpServer 类: 服务器套接字 (该类只有在服务器端使用)

    • listen 方法, 用于设置端口

    • newConnection 信号,当新的客户端链接到服务器时触发

    • QTcpSocket nextPendingConnection 方法,获取新链接到服务器的对象,返回值 QTcpSocket 类型

  • QTcpSocket 类: 链接套接字

    • peerAddress 方法,获取新链接的地址

    • peerPort 方法,获取新链接的端口号

socket 介绍:

  • Socket是一种通信协议,是应用层和传输层之间的接口,用于在网络上实现进程之间的通信。Socket是指两个不同计算机之间的通信链路,包括IP地址和端口号。

  • 通过Socket,应用程序可以连接到其他计算机或网络设备,发送或接收数据,并可以监听或等待其他计算机或网络设备的请求。所以,开发网络应用程序离不开socket

  • Socket可以分为TCP Socket和UDP Socket两种类型

第一步: 创建服务器对象,监听端口号

第二步: 创建客户端,链接到服务器

第三步: 当有新的客户端链接时,触发 TcpServer 的 newConnection 信号

第四步: 成功链接到服务器,触发 socket 的connected 信号

2.1 创建服务器端

cpp 复制代码
Server::Server(QWidget *parent) : QWidget(parent), ui(new Ui::Server)
{
    ui->setupUi(this);
    this->setWindowTitle("服务器");

    // 指定父对象,可以自动回收
    this->tcpServer = new QTcpServer(this);
    // 指定监听的端口
    this->tcpServer->listen(QHostAddress::Any, 8888);

    // 新链接信号 和 槽
    connect(tcpServer, &QTcpServer::newConnection, [=](){
        // 获取最新的链接客户端
        this->serSocket = tcpServer->nextPendingConnection();

        // 获取客户端的 ip 和 端口号
        quint16 port =  this->serSocket->peerPort();
        QString ip = this->serSocket->peerAddress().toString();

        // 将链接信息显示到显示区
        QString str = QString("链接信息: %1:%2").arg(ip).arg(port);
        ui->textEdit->setText(str);
    });
}

2.2 创建客户端

cpp 复制代码
#include <QTcpSocket>

Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
  ui->setupUi(this);
  this->setWindowTitle("客户端");

	// 创建套接字
  tcpSocket = new QTcpSocket(this);

  // 链接到服务器按钮来处理链接
  connect(ui->connectBtn, &QPushButton::clicked, [=](){
    // 获取 ip 和 端口号
    QString ip = ui->ipLine->text();
    qint16 port = ui->portLine->text().toInt();
		// 链接到服务器
    tcpSocket->connectToHost(ip, port);
  });
  
  // 当成功链接到服务器后会自动触发 connected 信号
  connect(tcpSocket, &QTcpSocket::connected, [=](){
    // 在显示区提示成功信息
    ui->showEdit->setText("成功链接到服务器");
  });
}

2.3客户端向服务器发送信息

核心方法:

  • QTcpSocket::write(QByteArray content); 向 socket 中写入内容并发送

  • QByteArray QTcpSocket::readAll(); 读取 socket 接收到的内容

1.客户端向服务器发送信息

cpp 复制代码
// 点击发送按钮
connect(ui->sendBtn, &QPushButton::clicked, [=](){
  // 获取文本框中的内容
  QString content = ui->sendLine->text();
  // 将内容写入,并进行发送
  tcpSocket->write(content.toUtf8().data());
});

2.服务器接收

注意事项: 服务器接收需要写在建立链接的信号槽内,因为要应用到 tcpSocket

cpp 复制代码
connect(tcpServer, &QTcpServer::newConnection, [=](){
  // 建立与客户端的链接套接字
  tcpSocket = tcpServer->nextPendingConnection();

  // 获取ip和端口
  quint16 port =  tcpSocket->peerPort();
  QString ip = tcpSocket->peerAddress().toString();

  QString str = QString("链接信息: %1:%2").arg(ip).arg(port);
  ui->readEdit->setText(str);

  // 链接成功之后
  // 当服务器接收到客户端的数据后会触发 readyRead 信号
  connect(tcpSocket, &QTcpSocket::readyRead, [=](){
    // 从通信套接字中取出内容
    QByteArray c = tcpSocket->readAll();
    // 将内容追加到 TextEdit 中
    ui->readEdit->append(c);
  });
});

2.4 服务器向客户端发送信息

逻辑同上

1)服务器

  • 点击发送按钮时获取用户输入内容

  • 调用 tcpSocket 中的 write 方法将数据写入并发送

cpp 复制代码
// 点击发送按钮时获取
connect(ui->btnSend, &QPushButton::clicked, [=](){
  QString content = ui->writeEdit->toPlainText();
  tcpSocket->write(content.toUtf8().data());
});

2)客户端

调用 tcpSocket 中 readAll 方法获取套接字中的数据显示到屏幕

cpp 复制代码
connect(tcpSocket, &QTcpSocket::readyRead, [=](){
  QByteArray content = tcpSocket->readAll();
  ui->showEdit->append(content);
});

2.4 断开链接

  • tcpSocket->disconnectFromHost() // 断开与服务器的链接

  • tcpSocket->close() // 关闭套接字

cpp 复制代码
connect(ui->disconnectBtn, &QPushButton::clicked, [=](){
  tcpSocket->disconnectFromHost();
  tcpSocket->close();
});

步骤小结:

  1. 建立 客户端与服务器的链接

① 使用 QTCPServer 创建了一个 server对象,再使用 server 对象监听端口

cpp 复制代码
QTcpServer *server = new QTcpServer(this);
server->listen(QHostAddress::Any, 8888);

② 客户端通过 socket 建立与服务器端的链接

cpp 复制代码
QTcpSocket *c_socket = new QTcpSocket(this);

c_socket->connectToHost(ip, port);

③ 服务器通过 QTcpServer::newConnection 信号来监听是否有新的客户端链接进来

cpp 复制代码
connect(server, &QTcpServer::newConntion, [=](){
  // 将链接信息写入到 屏幕
  socket = server->nextPendingConnection();
  QString ip = socket->peerAddress().toString();
  int port = socket->peerPort();
});

④ 链接成功之后,也会触发客户端 socket::connected 信号

cpp 复制代码
connect(socket, &QTcpSocket::connected, [](){
  // 将链接成功信息写入到屏幕
  ....
});

2.5 tcp通信完整代码

客户端:

cpp 复制代码
// client.h

#ifndef CLIENT_H
#define CLIENT_H

#include <QWidget>
#include <QTcpSocket>

namespace Ui {
class Client;
}

class Client : public QWidget
{
    Q_OBJECT

public:
    explicit Client(QWidget *parent = nullptr);
    ~Client();

private:
    Ui::Client *ui;

    QTcpSocket *clientSocket;
};

#endif // CLIENT_H


// client.c

#include "client.h"
#include "ui_client.h"

Client::Client(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Client)
{
    ui->setupUi(this);

    this->setWindowTitle("客户端");

    this->clientSocket = new QTcpSocket(this);


    // 2. 点击"链接服务器"按钮,链接到端口号为8888的服务器
    connect(ui->connBtn, &QPushButton::clicked, [this](){
        // 获取用户填写的ip和端口号
        QString ip = ui->ipLine->text();
        int port = ui->portLine->text().toInt();

        // 链接到服务器
        this->clientSocket->connectToHost(ip, port);

    });

    // 4. 当客户端成功链接到服务器后,会触发 QTcpSocket::connected 信号
    connect(this->clientSocket, &QTcpSocket::connected, [this](){
        ui->textEdit->setText("成功链接到服务器");
    });


    // 5. 点击"发送"按钮,得到用户编写的信息,通过socket发送给客户端
    // 发送数据就是将 数据写入socket
    connect(ui->sendBtn, &QPushButton::clicked, [this](){
        QString msg = ui->msgLine->text();
        this->clientSocket->write(msg.toUtf8());

        ui->msgLine->clear();
    });

    // 接收服务端发送的数据
    connect(this->clientSocket, &QTcpSocket::readyRead, [this](){
        QByteArray buf = this->clientSocket->readAll();
        ui->textEdit->append(buf);
    });


    connect(ui->disconnBtn, &QPushButton::clicked, [this](){
        this->clientSocket->disconnectFromHost();
    });
}

Client::~Client()
{
    delete ui;
}

服务端:

cpp 复制代码
// server.h

#ifndef SERVER_H
#define SERVER_H

#include <QWidget>

#include <QTcpServer>
#include <QTcpSocket>

namespace Ui {
class Server;
}

class Server : public QWidget
{
    Q_OBJECT

public:
    explicit Server(QWidget *parent = nullptr);
    ~Server();

private:
    Ui::Server *ui;

    QTcpServer *server;
    QTcpSocket *serverSocket;
};

#endif // SERVER_H



//server.c

#include "server.h"
#include "ui_server.h"
#include <QDebug>
#include "client.h"

Server::Server(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Server)
{
    ui->setupUi(this);

    this->setWindowTitle("服务端");
    this->move(150, 100);

    auto *c = new Client;
    c->show();
    c->move(700, 100);


    // 1. 创建服务器端
    this->server = new QTcpServer(this);
    this->server->listen(QHostAddress::Any, 8888);


    // 3. 当有客户端链接当前服务器时,就会触发 QTcpServer::newConnection 信号
    connect(this->server, &QTcpServer::newConnection, [this](){
        // nextPendingConnection: 用来获取连入服务器的客户端的socket对象信息
        this->serverSocket = this->server->nextPendingConnection();

        // 获取连入的客户端的ip地址和端口号
        QString ip = this->serverSocket->peerAddress().toString();
        quint16 port = this->serverSocket->peerPort();

        ui->screen->setText(QString("%1:%2 -- 已成链接").arg(ip).arg(port));


        //6.  每当客户端发送数据过来的时候就会触发 QTcpSocket::readyRead 信号
        // 接收数据就是从socket中读取
        connect(this->serverSocket, &QTcpSocket::readyRead, [this](){
            QByteArray buf = this->serverSocket->readAll();
            ui->screen->append(buf);
        });
    });


    // 点击"发送"按钮,得到用户输入的信息,写入socket
    connect(ui->sendBtn, &QPushButton::clicked, [this](){
        QString msg = ui->msgEdit->toPlainText();
        this->serverSocket->write(msg.toUtf8());

        ui->msgEdit->clear();
    });

    // 点击"断开链接",断开服务端与客户端的链接
    connect(ui->disconnBtn, &QPushButton::clicked, [this](){
        this->serverSocket->disconnectFromHost();
    });
}

Server::~Server()
{
    delete ui;
}

主函数:

cpp 复制代码
#include "server.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Server w;
    w.show();

    return a.exec();
}

2. UDP协议发送数据

QUdpSocket:

  • bind: 绑定端口号

  • writeDatagram: 写数据 (发送数据)

  • readDatagram: 读数据

  • readyRead (信号):接收到对方发送的数据时,自动触发 readyRead 信号

cpp 复制代码
client::client(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::client)
{
    ui->setupUi(this);
    this->setWindowTitle("端口号: 9999");

    updSocket = new QUdpSocket(this);

    // 绑定端口号
    updSocket->bind(9999);

    // 接收到对方发送的数据时,自动触发 readyRead 信号
    connect(updSocket, &QUdpSocket::readyRead, this, &client::dealMsg);


    // 发送数据
    connect(ui->sendBtn, &QPushButton::clicked, [=](){
        // 获取对方 ip 和 端口
        QString ip = ui->ipLine->text();
        qint16 port = ui->portLine->text().toInt();

        // 获取发送内容
        QString str = ui->sendLine->text();

        // 发送
        updSocket->writeDatagram(str.toUtf8(), QHostAddress(ip), port);

    });
}

void client::dealMsg()
{
    // 读取对方发送的内容
    char buf[1024] = {0};
    // 对方地址
    QHostAddress ip;
    // 对方端口
    quint16 port;
    // readDatagram 用来读取对方发送的数据
    // 参数1: 保存对方发送的数据
    // 参数2: 指定最多能保存多少数据
    // 参数3: 保存对方的ip地址
    // 参数4: 保存对方的端口号
    // 返回值: 一共读取了多长的数据
    qint64 len = updSocket->readDatagram(buf, 1024, &ip, &port);
    if (len > 0)
    {
        QString str = QString("[%1:%2] -> %3").arg(ip.toString()).arg(port).arg(buf);
        qDebug() << str;
        ui->screenEdit->setText(str);
    }
}

2.1 UDP通信完整代码

c1:

cpp 复制代码
#ifndef C1_H
#define C1_H

#include <QMainWindow>
#include <QUdpSocket>

namespace Ui {
class C1;
}

class C1 : public QMainWindow
{
    Q_OBJECT

public:
    explicit C1(QWidget *parent = nullptr);
    ~C1();

private:
    Ui::C1 *ui;

    QUdpSocket *udp_c1;
};

#endif // C1_H


//c1.c

#include "c1.h"
#include "ui_c1.h"

#include "c2.h"

C1::C1(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::C1)
{
    ui->setupUi(this);

    this->setWindowTitle("8888端口");
    this->move(200, 100);

    C2 *c2 = new C2;
    c2->show();
    c2->move(700, 100);


    // 1. 实例化udpSocket对象,并绑定端口号
    this->udp_c1 = new QUdpSocket(this);
    this->udp_c1->bind(8888);

    // 2. 点击"发送"按钮时,得到对方的ip和port、发送的数据
    //  udp协议不需要建立链接,只要有对方的ip和port就能发数据
    connect(ui->pushButton, &QPushButton::clicked, [this](){
        QString ip = ui->ipLine->text();
        QString port = ui->portLine->text();
        QString msg = ui->msgLine->text();

        this->udp_c1->writeDatagram(msg.toUtf8(), QHostAddress(ip), port.toInt());
    });


    connect(this->udp_c1, &QUdpSocket::readyRead, [this](){
        char c[1024] = {0};
        QHostAddress ip;
        quint16 port;

        this->udp_c1->readDatagram(c, 1024, &ip, &port);

        ui->textEdit->append(QString("%1:%2 --> %3").arg(ip.toString()).arg(port).arg(c));
   });
}

C1::~C1()
{
    delete ui;
}

c2:

cpp 复制代码
#ifndef C2_H
#define C2_H

#include <QWidget>
#include <QUdpSocket>

namespace Ui {
class C2;
}

class C2 : public QWidget
{
    Q_OBJECT

public:
    explicit C2(QWidget *parent = nullptr);
    ~C2();

private:
    Ui::C2 *ui;

    QUdpSocket *udp_c2;
};

#endif // C2_H



//c2.h

#include "c2.h"
#include "ui_c2.h"

C2::C2(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::C2)
{
    ui->setupUi(this);

    this->setWindowTitle("9999端口");

    this->udp_c2 = new QUdpSocket(this);
    this->udp_c2->bind(9999);

    // 一旦接收到信息就会触发 QUdpSocket::readyRead 信号
    connect(this->udp_c2, &QUdpSocket::readyRead, [this](){
        char c[1024] = {0};
        QHostAddress ip;
        quint16 port;

        this->udp_c2->readDatagram(c, 1024, &ip, &port);

        ui->textEdit->append(QString("%1:%2 --> %3").arg(ip.toString()).arg(port).arg(c));
   });

    connect(ui->pushButton, &QPushButton::clicked, [this](){
        QString ip = ui->ipLine->text();
        QString port = ui->portLine->text();
        QString msg = ui->msgLine->text();

        this->udp_c2->writeDatagram(msg.toUtf8(), QHostAddress(ip), port.toInt());
    });
}

C2::~C2()
{
    delete ui;
}

主函数:

cpp 复制代码
include "c1.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    C1 w;
    w.show();

    return a.exec();
}
相关推荐
終不似少年遊*3 分钟前
华为云计算HCIE笔记05
网络·华为云·云计算·学习笔记·hcie·认证·hcs
蜜獾云17 分钟前
docker 安装雷池WAF防火墙 守护Web服务器
linux·运维·服务器·网络·网络安全·docker·容器
小林熬夜学编程1 小时前
【Linux网络编程】第十四弹---构建功能丰富的HTTP服务器:从状态码处理到服务函数扩展
linux·运维·服务器·c语言·网络·c++·http
Hacker_Fuchen1 小时前
天融信网络架构安全实践
网络·安全·架构
上海运维Q先生2 小时前
面试题整理15----K8s常见的网络插件有哪些
运维·网络·kubernetes
ProtonBase2 小时前
如何从 0 到 1 ,打造全新一代分布式数据架构
java·网络·数据库·数据仓库·分布式·云原生·架构
mahuifa11 小时前
混合开发环境---使用编程AI辅助开发Qt
人工智能·vscode·qt·qtcreator·编程ai
冷眼看人间恩怨11 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
fantasy_arch11 小时前
CPU性能优化-磁盘空间和解析时间
网络·性能优化
njnu@liyong12 小时前
图解HTTP-HTTP报文
网络协议·计算机网络·http