2. QTcpSocket基础

QTcpSocket 这个类,说白了就是 Qt 为我们封装好的 TCP 客户端套接字。我刚开始接触网络编程时,总觉得 TCP 协议很神秘——三次握手、四次挥手、滑动窗口……听着就头大。但 Qt 把这些底层细节都藏起来了,你只需要关心几个核心 API 就行。

我个人习惯把 QTcpSocket 看作一根「网线」——你把它插上(连接),往里面塞数据(发送),从里面取数据(接收),用完了拔掉(断开)。就这么简单。

1.1 核心 API 一览

先看一张我整理的 API 速查表,平时开发时我经常翻它:

功能分类 API 名称 说明
连接管理 connectToHost() 连接到指定主机和端口
连接管理 disconnectFromHost() 主动断开连接
连接管理 state() 获取当前连接状态
数据发送 write() 发送数据(QByteArray)
数据发送 flush() 强制将缓冲区数据发出
数据接收 read() / readAll() 读取接收到的数据
数据接收 bytesAvailable() 检查可读字节数
信号 connected() 连接成功时触发
信号 disconnected() 断开连接时触发
信号 readyRead() 有新数据可读时触发
信号 errorOccurred() 发生错误时触发

嗯,这里要注意:error() 信号在 Qt 5.15 之后被标记为废弃,新项目请用 errorOccurred()。我曾经在升级旧项目时踩过这个坑,编译报错找了半天才发现是信号名变了。

1.2 连接与断开服务器

连接服务器是网络通信的第一步。看看这段代码,我一般这样写:

QTcpSocket *socket = new QTcpSocket(this);

// 连接到服务器
socket->connectToHost("192.168.1.100", 8080);

// 检查连接是否成功
if (socket->waitForConnected(3000)) {
    qDebug() << "连接成功!";
} else {
    qDebug() << "连接失败:" << socket->errorString();
}

这里有个细节我想强调一下:waitForConnected() 会阻塞当前线程 3 秒。如果你在主线程里调用它,界面会卡住。我个人习惯在非 UI 线程里用阻塞方式,或者干脆用信号槽异步连接。

断开连接就更简单了:

socket->disconnectFromHost();
if (socket->state() == QAbstractSocket::UnconnectedState) {
    qDebug() << "已断开连接";
}

你想想看,为什么断开后还要检查状态?因为 disconnectFromHost() 是异步的——它只是发起断开请求,真正断开需要等底层完成。我曾经在项目中没注意这点,断开后立刻重连,结果状态还是 Connecting,导致逻辑混乱。

1.3 发送与接收数据

发送数据用 write(),接收数据靠 readyRead() 信号驱动。这是最核心的通信模式。

先看发送:

// 发送文本数据
QByteArray data = "Hello, Server!";
socket->write(data);

// 发送二进制数据
QByteArray binData;
binData.append(0x01);
binData.append(0x02);
binData.append(0x03);
socket->write(binData);

接收数据时,我建议永远不要在 readyRead() 里假设「一次信号就是一条完整消息」。为什么?因为 TCP 是流式协议,数据可能被拆成多个包到达。看这个例子:

// 错误示范:假设一次读完就是一条消息
connect(socket, &QTcpSocket::readyRead, [=]() {
    QByteArray data = socket->readAll();
    processMessage(data);  // 可能收到半条消息!
});

// 正确做法:使用缓冲区拼接
QByteArray buffer;
connect(socket, &QTcpSocket::readyRead, [&]() {
    buffer.append(socket->readAll());
    // 检查是否收到完整消息(比如按长度或分隔符判断)
    while (buffer.size() >= 4) {
        int msgLen = (buffer[0] << 24) | (buffer[1] << 16) | 
                     (buffer[2] << 8) | buffer[3];
        if (buffer.size() < 4 + msgLen) break;  // 还没收完
        QByteArray msg = buffer.mid(4, msgLen);
        processMessage(msg);
        buffer.remove(0, 4 + msgLen);
    }
});

这个「粘包处理」的坑,我当年至少花了两个通宵才调通。说白了,就是要在应用层自己定义消息边界。

1.4 信号与槽机制在网络中的应用

Qt 的信号槽在网络编程里简直是神器。你不用轮询、不用回调地狱,只需要把信号和槽连起来就行。

我画了一张图,帮你理清 QTcpSocket 的信号流转:

QTcpSocket 信号与槽工作流程 客户端 QTcpSocket 服务器 QTcpServer connectToHost() connected() 信号 readyRead() 信号 disconnected() 信号 槽函数处理 • onConnected():更新UI状态 • onReadyRead():读取并解析数据 • onDisconnected():清理资源 • onError():显示错误信息

实际项目中,我一般这样连接信号槽:

// 连接信号与槽
connect(socket, &QTcpSocket::connected, this, &MyClass::onConnected);
connect(socket, &QTcpSocket::readyRead, this, &MyClass::onReadyRead);
connect(socket, &QTcpSocket::disconnected, this, &MyClass::onDisconnected);
connect(socket, &QTcpSocket::errorOccurred, this, &MyClass::onError);

// 槽函数实现
void MyClass::onConnected() {
    ui->statusLabel->setText("已连接到服务器");
    ui->connectButton->setEnabled(false);
    ui->disconnectButton->setEnabled(true);
}

void MyClass::onReadyRead() {
    QByteArray data = socket->readAll();
    ui->textEdit->append("收到: " + QString::fromUtf8(data));
}

void MyClass::onDisconnected() {
    ui->statusLabel->setText("已断开连接");
    ui->connectButton->setEnabled(true);
    ui->disconnectButton->setEnabled(false);
}

void MyClass::onError(QAbstractSocket::SocketError error) {
    Q_UNUSED(error)
    ui->statusLabel->setText("错误: " + socket->errorString());
}

核心要点:QTcpSocket 的信号槽机制让网络编程变成了事件驱动。你不需要自己写循环去轮询数据,系统会在数据到达时自动通知你。这就是 Qt 网络模块最优雅的地方。

小技巧:如果你需要在一个类里管理多个 socket 连接,可以用 sender() 来区分是哪个 socket 触发的信号:

void MyClass::onReadyRead() {
    QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
    if (socket) {
        QByteArray data = socket->readAll();
        // 根据 socket 指针区分不同连接
    }
}

注意:不要在 readyRead() 槽函数里做耗时操作!如果数据处理很慢,可以考虑用 QThread 或者 QtConcurrent 把数据扔到工作线程处理。我曾经在 readyRead() 里写文件,结果 UI 卡得鼠标都动不了。

好了,QTcpSocket 的基础就这些。说白了就是三板斧:连上、发数据、收数据。但每个环节都有不少细节,尤其是粘包处理那块,我建议你多写几个 demo 练练手。等把这些基础打牢了,后面讲高级特性时你才能跟得上。

可可精品资料库