2. QTcpSocket基础:从连接到断开的完整旅程

说实话,QTcpSocket 是我在 Qt 网络编程里打交道最多的类。没有之一。

你想想看,不管是写一个聊天软件,还是做一个数据采集系统,底层都离不开它。今天我就带你把它彻底搞明白。

2.1 QTcpSocket 是什么?

QTcpSocket 是 Qt 提供的 TCP 客户端套接字类。它继承自 QAbstractSocket,而 QAbstractSocket 又继承自 QIODevice。

这意味着什么?

意味着它不仅能读写网络数据,还能像操作文件一样用 read()、write() 这些接口。我当年刚接触时,就觉得这个设计特别巧妙——网络通信和文件操作统一了。

核心继承链:

QObject → QIODevice → QAbstractSocket → QTcpSocket

QTcpSocket 是异步的。它不会阻塞你的 UI 线程。这一点非常重要。

我记得有个新手同事,直接在 UI 线程里用 waitForConnected(),结果界面卡死了。嗯,这个坑我后面会细说。

2.2 连接服务器

连接服务器,说白了就是调用 connectToHost()。

QTcpSocket *socket = new QTcpSocket(this);
socket->connectToHost("192.168.1.100", 8080);

就这么简单?对,就这么简单。但背后的事情可不少。

connectToHost() 是异步的。它发起连接请求后立即返回。真正的连接结果,通过信号告诉你。

你需要监听两个关键信号:

  • connected() — 连接成功
  • errorOccurred(QAbstractSocket::SocketError) — 连接失败

我的习惯:连接前先检查 socket 状态。如果已经连接,先断开再重连。避免重复连接导致奇怪的问题。

if (socket->state() != QAbstractSocket::UnconnectedState) {
    socket->abort();  // 立即断开,不等待
}
socket->connectToHost("192.168.1.100", 8080);

为什么会用 abort() 而不是 disconnectFromHost()?

abort() 会立即关闭套接字,不等待数据发送完毕。而 disconnectFromHost() 会优雅地关闭。在重连场景下,我倾向于用 abort(),因为快。

2.3 发送数据

发送数据用 write()。它继承自 QIODevice。

QByteArray data = "Hello, Server!";
qint64 bytesWritten = socket->write(data);

write() 返回实际写入的字节数。但注意,这个写入是指写入到 Qt 内部的发送缓冲区,不是直接发送到网络。

真正的数据发送,由 Qt 的事件循环在后台完成。数据发送完毕后,会触发 bytesWritten(qint64) 信号。

我曾经踩过的坑:连续多次调用 write() 发送小数据包。你以为发了 10 次,实际上底层可能合并成一个包发送了。这叫 Nagle 算法。如果你需要实时性,可以调用 setSocketOption(QAbstractSocket::LowDelayOption, 1) 禁用它。

我个人习惯发送数据时,先判断 socket 状态:

if (socket->state() == QAbstractSocket::ConnectedState) {
    socket->write(data);
} else {
    qWarning() << "Socket not connected!";
}

嗯,这个检查虽然简单,但能避免很多莫名其妙的崩溃。

2.4 接收数据

接收数据是 QTcpSocket 里最容易出问题的地方。

核心信号是 readyRead()。当有新数据到达时,Qt 会发射这个信号。

connect(socket, &QTcpSocket::readyRead, this, [=]() {
    QByteArray data = socket->readAll();
    qDebug() << "Received:" << data;
});

看起来很简单对吧?但实际项目中,你可能会遇到这些问题:

  • 粘包问题:一次 readyRead() 可能收到多个数据包
  • 半包问题:一个完整的数据包可能分多次 readyRead() 到达

怎么解决?

我常用的方法是:自定义协议头。比如前 4 个字节表示数据长度。

void MyClass::onReadyRead() {
    // 先检查是否收到足够的数据
    if (socket->bytesAvailable() < 4) return;
    
    // 读取长度
    QByteArray lenData = socket->read(4);
    quint32 totalLen = *reinterpret_cast<quint32*>(lenData.data());
    
    // 等待完整数据
    if (socket->bytesAvailable() < totalLen) return;
    
    // 读取完整数据
    QByteArray payload = socket->read(totalLen);
    processPayload(payload);
}

避坑指南:不要在 readyRead() 里做耗时操作。我曾经在回调里写数据库,结果 UI 卡顿严重。正确的做法是:收到数据后,用信号抛出去,或者丢到工作线程处理。

2.5 断开连接

断开连接有两种方式:

方法 行为 适用场景
disconnectFromHost() 优雅关闭,等待数据发送完毕 正常退出
abort() 立即关闭,丢弃未发送数据 异常处理、快速重连

断开连接后,会触发 disconnected() 信号。

connect(socket, &QTcpSocket::disconnected, this, [=]() {
    qDebug() << "Disconnected from server";
    // 可以在这里做清理工作
});

我个人习惯在析构函数里主动断开:

MyClass::~MyClass() {
    if (socket) {
        socket->disconnectFromHost();
        if (socket->state() != QAbstractSocket::UnconnectedState) {
            socket->waitForDisconnected(1000);  // 最多等1秒
        }
    }
}

为什么要等?因为 disconnectFromHost() 是异步的。如果不等待,直接销毁 socket,可能导致数据丢失。

2.6 核心流程图

下面这张图,是我用 SVG 画的 QTcpSocket 生命周期。你看一眼就能明白整个流程。

QTcpSocket 生命周期流程图 UnconnectedState connectToHost() ConnectingState ConnectedState 发射 connected() 信号 连接失败 发射 errorOccurred() 发送数据: write() 接收数据: readyRead() disconnectFromHost() 或 abort() 回到 UnconnectedState 图例 状态节点 数据操作 成功状态 失败状态

2.7 完整示例代码

最后,给你一个完整的客户端示例。麻雀虽小,五脏俱全。

class TcpClient : public QObject {
    Q_OBJECT
public:
    TcpClient(QObject *parent = nullptr) : QObject(parent) {
        socket = new QTcpSocket(this);
        
        connect(socket, &QTcpSocket::connected, this, [=]() {
            qDebug() << "Connected!";
        });
        
        connect(socket, &QTcpSocket::disconnected, this, [=]() {
            qDebug() << "Disconnected!";
        });
        
        connect(socket, &QTcpSocket::readyRead, this, [=]() {
            QByteArray data = socket->readAll();
            qDebug() << "Received:" << data;
        });
        
        connect(socket, &QTcpSocket::errorOccurred, this, [=](QAbstractSocket::SocketError err) {
            qDebug() << "Error:" << socket->errorString();
        });
    }
    
    void connectToServer(const QString &host, quint16 port) {
        if (socket->state() != QAbstractSocket::UnconnectedState) {
            socket->abort();
        }
        socket->connectToHost(host, port);
    }
    
    void sendData(const QByteArray &data) {
        if (socket->state() == QAbstractSocket::ConnectedState) {
            socket->write(data);
        }
    }
    
    void disconnect() {
        socket->disconnectFromHost();
    }
    
private:
    QTcpSocket *socket;
};

好了,QTcpSocket 的基础就这些。你把它跑一遍,再结合我提到的那些坑,基本就能上手了。

可可精品资料库