当前位置: 首页 > news >正文

QT网络通信的接口与使用


文章目录

  • 前言
  • 1.服务端实现流程
    • 1.1步骤 1:创建 QTcpServer 并监听端口
    • 1.2步骤 2:处理新连接请求
    • 1.3步骤 3:接收客户端数据
    • 1.4步骤 4:处理客户端断开
  • 2.客户端实现流程
    • 2.1步骤 1:创建 QTcpSocket 并连接服务器
    • 2.2步骤 2:发送数据
    • 2.3步骤 3:接收服务器回复
    • 2.4步骤 4:处理连接和错误
  • 3.关键注意事项
  • 4.TCP粘包问题及其处理
    • 4.1TCP粘包是什么
    • 4.2TCP粘包为什么会产生
    • 4.3TCP粘包的解决方案


前言

在Qt中实现TCP通信主要依赖 QTcpServer(服务端)和 QTcpSocket(客户端和服务端通信)类。

TCP/IP通信(即SOCKET通信)是通过网线将服务器Server端和客户机Client端进行连接,在遵循ISO/OSI模型的四层层级构架的基础上通过TCP/IP协议建立的通讯。控制器可以设置为服务器端或客户端。

服务端(简化版)

class MyServer : public QObject {
    Q_OBJECT
public:
    MyServer(QObject *parent = nullptr) : QObject(parent) {
        server = new QTcpServer(this);
        connect(server, &QTcpServer::newConnection, this, &MyServer::onNewConnection);
        server->listen(QHostAddress::Any, 8888);
    }

private slots:
    void onNewConnection() { /* ... */ }
    void onReadyRead() { /* ... */ }
    void onDisconnected() { /* ... */ }

private:
    QTcpServer *server;
    QList<QTcpSocket*> m_clients;
};

客户端(简化版)

class MyClient : public QObject {
    Q_OBJECT
public:
    MyClient(QObject *parent = nullptr) : QObject(parent) {
        socket = new QTcpSocket(this);
        connect(socket, &QTcpSocket::connected, this, &MyClient::onConnected);
        connect(socket, &QTcpSocket::readyRead, this, &MyClient::onReadyRead);
        socket->connectToHost("127.0.0.1", 8888);
    }

    void send(const QString &message) {
        socket->write(message.toUtf8());
    }

private slots:
    void onConnected() { /* ... */ }
    void onReadyRead() { /* ... */ }

private:
    QTcpSocket *socket;
};

运行效果
服务端启动后监听端口,客户端连接并发送数据。
服务端接收数据并回复,客户端显示回复内容。
断开连接后资源自动释放。

1.服务端实现流程

1.1步骤 1:创建 QTcpServer 并监听端口

// 创建TCP服务端对象
QTcpServer *server = new QTcpServer(this);

// 监听所有IP的指定端口(例如8888)
if (!server->listen(QHostAddress::Any, 8888)) {
    qDebug() << "Server could not start. Error:" << server->errorString();
} else {
    qDebug() << "Server started on port 8888";
}

1.2步骤 2:处理新连接请求

当客户端连接时,QTcpServer 会触发 newConnection 信号,需通过槽函数处理:

// 连接信号到槽函数
connect(server, &QTcpServer::newConnection, this, &MyServer::onNewConnection);

// 槽函数实现
void MyServer::onNewConnection() {
    // 获取新连接的socket对象
    QTcpSocket *socket = server->nextPendingConnection();
    
    // 存储socket以便后续通信(例如添加到列表)
    m_clients.append(socket);
    
    // 处理客户端数据到达的信号
    connect(socket, &QTcpSocket::readyRead, this, &MyServer::onReadyRead);
    
    // 处理断开连接的信号
    connect(socket, &QTcpSocket::disconnected, this, &MyServer::onDisconnected);
}

1.3步骤 3:接收客户端数据

通过 readyRead 信号读取数据:

void MyServer::onReadyRead() {
    QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
    if (!socket) return;
    
    QByteArray data = socket->readAll();
    qDebug() << "Received data:" << data;
    
    // 示例:回复客户端
    socket->write("Server received: " + data);
}

1.4步骤 4:处理客户端断开

void MyServer::onDisconnected() {
    QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
    if (!socket) return;
    
    m_clients.removeOne(socket);
    socket->deleteLater();
    qDebug() << "Client disconnected";
}

2.客户端实现流程

2.1步骤 1:创建 QTcpSocket 并连接服务器

QTcpSocket *socket = new QTcpSocket(this);

// 连接服务器(假设服务器IP为127.0.0.1,端口8888)
socket->connectToHost("127.0.0.1", 8888);

// 监听连接成功信号
connect(socket, &QTcpSocket::connected, this, &MyClient::onConnected);

// 监听数据到达信号
connect(socket, &QTcpSocket::readyRead, this, &MyClient::onReadyRead);

// 监听错误信号
connect(socket, &QTcpSocket::errorOccurred, this, &MyClient::onError);

2.2步骤 2:发送数据

void MyClient::sendData(const QByteArray &data) {
    if (socket->state() == QAbstractSocket::ConnectedState) {
        socket->write(data);
        socket->flush(); // 确保立即发送
    }
}

2.3步骤 3:接收服务器回复

void MyClient::onReadyRead() {
    QByteArray data = socket->readAll();
    qDebug() << "Server response:" << data;
}

2.4步骤 4:处理连接和错误

void MyClient::onConnected() {
    qDebug() << "Connected to server!";
}

void MyClient::onError(QAbstractSocket::SocketError error) {
    qDebug() << "Error:" << socket->errorString();
}

3.关键注意事项

  1. 异步通信:
    Qt的TCP操作基于事件循环,所有操作(连接、读写)都是异步的,需通过信号槽处理结果。

  2. 数据分包与粘包:
    TCP是流式协议,需自行处理数据边界(例如定义协议头尾或使用长度前缀)。

  3. 资源管理:
    及时释放断开连接的 QTcpSocket 对象(调用 deleteLater)。

  4. 跨线程操作:
    若在多线程中使用,需将 QTcpSocket 或 QTcpServer 移至子线程(使用 moveToThread)。

4.TCP粘包问题及其处理

4.1TCP粘包是什么

TCP的粘包和拆包问题往往出现在基于TCP协议的通讯中,比如RPC框架、Netty等。

TCP在接受数据的时候,有一个滑动窗口来控制接受数据的大小,这个滑动窗口你就可以理解为一个缓冲区的大小。缓冲区满了就会把数据发送。数据包的大小是不固定的,有时候比缓冲区大有时候小。
如果一次请求发送的数据量比较小,没达到缓冲区大小,TCP则会将多个请求合并为同一个请求进行发送,这就形成了粘包问题;
如果一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送,这就是拆包,也就是将一个大的包拆分为多个小包进行发送。

这是最好理解的粘包问题的产生原因。还有一些其他的原因比如
1   客户端的发送频率远高于服务器的接收频率,就会导致数据在服务器的tcp接收缓冲区滞留形成粘连,比如客户端1s内连续发送了两个hello world!,服务器过了2s才接收数据,那一次性读出两个hello world!。
2   tcp底层的安全和效率机制不允许字节数特别少的小包发送频率过高,tcp会在底层累计数据长度到一定大小才一起发送,比如连续发送1字节的数据要累计到多个字节才发送,可以了解下tcp底层的Nagle算法。
3   再就是我们提到的最简单的情况,发送端缓冲区有上次未发送完的数据或者接收端的缓冲区里有未取出的数据导致数据粘连。
在这里插入图片描述

4.2TCP粘包为什么会产生

1.TCP会发生粘包问题:TCP 是面向连接的传输协议,TCP 传输的数据是以流的形式,而流数据是没有明确的开始结尾边界,所以 TCP 也没办法判断哪一段流属于一个消息;TCP协议是流式协议;所谓流式协议,即协议的内容是像流水一样的字节流,内容与内容之间没有明确的分界标志,需要认为手动地去给这些协议划分边界。
粘包时:发送方每次写入数据 < 接收方套接字(Socket)缓冲区大小。
拆包时:发送方每次写入数据 > 接收方套接字(Socket)缓冲区大小。

2.UDP不会发生粘包问题:UDP具有保护消息边界,在每个UDP包中就有了消息头(UDP长度、源端口、目的端口、校验和)。
粘包拆包问题在数据链路层、网络层以及传输层都有可能发生。日常的网络应用开发大都在传输层进行,由于UDP有消息保护边界,不会发生粘包拆包问题,因此粘包拆包问题只发生在TCP协议中

4.3TCP粘包的解决方案

  1. 客户端在发送数据包的时候,每个包都固定长度,比如1024个字节大小,如果客户端发送的数据长度不足1024个字节,则通过补充空格的方式补全到指定长度;
  2. 客户端在每个包的末尾使用固定的分隔符,例如\r\n,如果一个包被拆分了,则等待下一个包发送过来之后找到其中的\r\n,然后对其拆分后的头部部分与前一个包的剩余部分进行合并,这样就得到了一个完整的包;
  3. 将消息分为头部和消息体,在头部中保存有当前整个消息的长度,只有在读取到足够长度的消息之后才算是读到了一个完整的消息;
  4. 通过自定义协议进行粘包和拆包的处理。

优缺点分析

  • 解决方案1:固定数据大小
    虽然这种方式可以解决粘包问题,但这种固定数据大小的传输方式,当数据量比较小时会使用空字符来填充,所以会额外的增加网络传输的负担,因此不是理想的解决方案。
  • 解决方案2:特殊字符结尾
    以特殊符号作为粘包的解决方案的最大优点是实现简单,但存在一定的局限性,比如当一条消息中间如果出现了结束符就会造成半包的问题,所以如果是复杂的字符串要对内容进行编码和解码处理,这样才能保证结束符的正确性。
  • 解决方案4:设置消息头
    此解决方案可以解决粘包问题,并且对于空间的利用也相对高
  • 解决方案4:自定义请求协议
    此解决方案虽然可以解决粘包问题,但消息的设计和代码的实现复杂度比较高,所以也不是理想的解决方案

相关文章:

  • 城电科技|景观光伏花 太阳能发电的景观光伏太阳花向日葵
  • 高校校园交友微信小程序的设计与实现【lw+源码+部署+讲解】
  • 接口自动化框架篇:自定义异常日志封装!
  • 【T2I】Divide Bind Your Attention for Improved Generative Semantic Nursing
  • 基于QT(C++)实现绘图程序
  • 枪机AI人工智能的识别镜头图像技术
  • 深入理解指针(1)(C语言版)
  • Android Compose 框架副作用管理(SideEffect、EffectScope)深入剖析(十八)
  • 基于Vue.js的组件化开发技术与实践探索
  • 基于Spring Boot的乡村养老服务管理系统的设计与实现(LW+源码+讲解)
  • Spring Security核心源码和功能实现
  • Driver具体负责什么工作
  • RAG优化:python从零实现[吃一堑长一智]循环反馈Feedback
  • 【腾讯云架构师技术沙龙2025.03.22】
  • 前端面试常见的计算机网络内容梳理
  • RocketMQ 面试备战指南
  • Podman 学习总结
  • Can Large Language Models be Anomaly Detectors for Time Series? 解读
  • C#中Interlocked.Exchange的作用
  • vmware虚拟机快照、克隆、迁移区别说明
  • A股午后回暖,三大股指涨跌互现:港口板块重新走强,两市成交近1.1万亿元
  • 瑞幸首度牵手成都国际非遗节,用一杯饮品将非遗之美推向全国
  • 肖钢:一季度证券业金融科技投资强度在金融各子行业中居首
  • 广西隆林突发山洪,致3人遇难1人失联
  • 2025年上海科技节开幕,人形机器人首次登上科学红毯
  • 光明日报社副总编辑薄洁萍调任求是杂志社副总编辑