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 的信号流转:
实际项目中,我一般这样连接信号槽:
// 连接信号与槽
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 练练手。等把这些基础打牢了,后面讲高级特性时你才能跟得上。
可可精品资料库