> 技术文档 > Qt开发:QTcpSocket的详解

Qt开发:QTcpSocket的详解


文章目录

    • 一、QTcpSocket 简介
    • 二、常用方法的介绍和使用
    • 三、常用的信号函数

一、QTcpSocket 简介

  QTcpSocket 是 Qt 网络模块中用于实现基于 TCP 协议的客户端通信的类。它提供了一个面向流的接口,允许程序通过套接字连接到远程主机,发送和接收数据

  • 所属模块:QtNetwork
  • 用于建立 TCP 客户端连接,读取/写入数据。
  • 特点:异步非阻塞(配合信号槽机制)、支持事件驱动编程模型。

二、常用方法的介绍和使用

2.1 void QTcpSocket::connectToHost(hostname, port)
用于发起 TCP 连接的核心函数,常用于客户端向服务器发起连接请求。

参数说明:
Qt开发:QTcpSocket的详解
使用示例

QTcpSocket* socket = new QTcpSocket(this);// 连接信号槽connect(socket, &QTcpSocket::connected, this, [](){ qDebug() << \"Connected to server.\";});connect(socket, &QTcpSocket::errorOccurred, this, [](QAbstractSocket::SocketError err){ qDebug() << \"Connection error:\" << err;});// 发起连接socket->connectToHost(\"127.0.0.1\", 12345);

阻塞等待连接:
如果在非 GUI 程序中使用,或者想同步等待连接结果,可以使用:

socket->connectToHost(\"127.0.0.1\", 12345);if (socket->waitForConnected(3000)) { qDebug() << \"Connected!\";} else { qDebug() << \"Connection failed:\" << socket->errorString();}

常见错误排查:
Qt开发:QTcpSocket的详解
2.2 void QAbstractSocket::disconnectFromHost()
用于主动断开 TCP 连接的函数。它的作用是通知对方:客户端希望关闭连接,然后进行连接释放。

使用说明:
当调用 disconnectFromHost() 后:

  1. 客户端发送 TCP 的 FIN 包;
  2. 如果连接关闭成功,触发 disconnected() 信号;
  3. 如果对方已经关闭连接,你仍然可以调用它释放资源;
  4. 若要强制关闭连接,可以使用 abort()。

基本示例:

QTcpSocket* socket = new QTcpSocket(this);// 建立连接socket->connectToHost(\"127.0.0.1\", 12345);connect(socket, &QTcpSocket::connected, this, [socket]() { qDebug() << \"Connected to server.\"; // 写入数据后断开 socket->write(\"Bye Server!\"); socket->flush(); // 确保发送出去了 socket->disconnectFromHost();});connect(socket, &QTcpSocket::disconnected, this, []() { qDebug() << \"Disconnected from server.\";});

同步等待断开:

socket->disconnectFromHost();if (socket->state() != QAbstractSocket::UnconnectedState) { socket->waitForDisconnected(3000); // 最多等 3 秒}

与 abort() 的区别:
Qt开发:QTcpSocket的详解
建议:正常退出时用 disconnectFromHost();发生错误/异常时使用 abort() 立即关闭连接。

2.3 qint64 QIODevice::write(const char *data, qint64 maxSize)
它在客户端和服务端之间发送字节流,是实现 socket 通信的核心方法之一。

参数说明:

  • data:指向要发送的原始数据。
  • maxSize:要发送的数据长度(字节数)。

返回值:返回实际写入的字节数,如果返回 -1,表示写入失败。

注意事项:

  • write() 只将数据写入到内部缓冲区,并不保证立刻发送。
  • 若想确保数据被真正写出,可使用 flush() 或等待 bytesWritten(qint64) 信号。
  • 如果 socket 未连接,调用 write() 会失败。

示例代码:
示例 1:发送一个 C 风格字符串

QTcpSocket *socket = new QTcpSocket(this);socket->connectToHost(\"127.0.0.1\", 8888);if (socket->waitForConnected(3000)) { const char *msg = \"Hello from client!\"; qint64 bytesWritten = socket->write(msg, strlen(msg)); qDebug() << \"Bytes written:\" << bytesWritten;}

示例 2:发送结构体(二进制数据)

struct MyData { int id; float value; char name[16];};MyData data = {42, 3.14f, \"example\"};qint64 size = sizeof(data);QTcpSocket *socket = new QTcpSocket(this);socket->connectToHost(\"127.0.0.1\", 8888);if (socket->waitForConnected(3000)) { qint64 bytesWritten = socket->write(reinterpret_cast<const char*>(&data), size); qDebug() << \"Sent struct of size:\" << bytesWritten;}

示例3:写入大量数据
如果是大量数据,可能无法一次性全部写入完。这时候返回值可能小于你提供的 maxSize,即只写入了一部分数据。这是因为:

  • QTcpSocket 的底层发送缓冲区是有限的;
  • 写入时如果缓冲区满了,write() 只写部分内容或返回 -1;
  • 它是非阻塞的,不会等待所有数据写入成功才返回。

正确处理方式:分段循环写入 + 监听 bytesWritten()

class MySender : public QObject { Q_OBJECTpublic: MySender(QTcpSocket *sock, QObject *parent = nullptr) : QObject(parent), socket(sock), totalSize(0), bytesSent(0), buffer(nullptr) {connect(socket, &QTcpSocket::bytesWritten, this, &MySender::onBytesWritten);} void sendLargeData(const char *data, qint64 size) { buffer = data; totalSize = size; bytesSent = 0; writeMore(); }private slots: void onBytesWritten(qint64 bytes) { Q_UNUSED(bytes); }private: void writeMore() { const int CHUNK_SIZE = 64 * 1024; // 每次最多写 64KB while (bytesSent < totalSize) { qint64 remaining = totalSize - bytesSent; qint64 toWrite = qMin(remaining, static_cast<qint64>(CHUNK_SIZE)); qint64 written = socket->write(buffer + bytesSent, toWrite); if (written == -1) { qDebug() << \"Write failed!\"; return; } bytesSent += written; if (written < toWrite) { // 写缓冲区满了,等下次 bytesWritten 再继续 break; } } if (bytesSent == totalSize) { qDebug() << \"All data sent.\"; disconnect(socket, &QTcpSocket::bytesWritten, this, &MySender::onBytesWritten); } } QTcpSocket *socket; const char *buffer; qint64 totalSize; qint64 bytesSent;};

使用方式:

MySender *sender = new MySender(socket, this);sender->sendLargeData(bigBuffer, bigSize);

2.4 qint64 QIODevice::write(const QByteArray &byteArray)
参数说明:byteArray是要写入的数据,封装在一个 QByteArray 对象中。

返回值:

  • 返回实际写入的字节数。
  • 返回 -1 表示写入失败(如设备未打开或不可写)。

使用示例:
示例 1:使用 QByteArray 向服务器发送文本数据

QTcpSocket *socket = new QTcpSocket(this);socket->connectToHost(\"127.0.0.1\", 12345);if (socket->waitForConnected(3000)) { QByteArray data = \"Hello, server!\"; qint64 bytesWritten = socket->write(data); socket->flush(); // 建议 flush 确保尽快发送 qDebug() << \"Bytes written:\" << bytesWritten;}

示例 2:构造二进制数据并发送

QByteArray data;QDataStream stream(&data, QIODevice::WriteOnly);stream.setByteOrder(QDataStream::LittleEndian);// 写入数据(可用来发送结构化内容)int id = 42;float value = 3.14f;stream << id << value;socket->write(data);

示例 3:文件读取并通过 socket 发送(推荐使用 QByteArray)

QFile file(\"image.jpg\");if (file.open(QIODevice::ReadOnly)) { QByteArray fileData = file.readAll(); // 读入整个文件内容 socket->write(fileData);}

使用 QByteArray + 分段写入大数据的完整示例:
LargeDataSender.h

#ifndef LARGEDATASENDER_H#define LARGEDATASENDER_H#include #include class LargeDataSender : public QObject{ Q_OBJECTpublic: explicit LargeDataSender(QTcpSocket *socket, QObject *parent = nullptr); void send(const QByteArray &data); // 启动发送过程private slots: void onBytesWritten(qint64 bytes);private: void writeNextChunk(); QTcpSocket *m_socket; QByteArray m_data; qint64 m_totalSize; qint64 m_bytesSent; const int CHUNK_SIZE = 64 * 1024; // 每次写 64KB};#endif // LARGEDATASENDER_H

LargeDataSender.cpp

#include \"LargeDataSender.h\"#include LargeDataSender::LargeDataSender(QTcpSocket *socket, QObject *parent) : QObject(parent), m_socket(socket), m_totalSize(0), m_bytesSent(0){ connect(m_socket, &QTcpSocket::bytesWritten, this, &LargeDataSender::onBytesWritten);}void LargeDataSender::send(const QByteArray &data){ m_data = data; m_totalSize = data.size(); m_bytesSent = 0; writeNextChunk(); // 立即尝试写第一段}void LargeDataSender::writeNextChunk(){ while (m_bytesSent < m_totalSize) { qint64 remaining = m_totalSize - m_bytesSent; qint64 toWrite = qMin(remaining, static_cast<qint64>(CHUNK_SIZE)); qint64 written = m_socket->write(m_data.constData() + m_bytesSent, toWrite); if (written == -1) { qWarning() << \"Socket write failed!\"; return; } m_bytesSent += written; if (written < toWrite) { // 写入未完全,可能缓冲区满了,等 bytesWritten 再继续 break; } } if (m_bytesSent == m_totalSize) { qDebug() << \"All data sent (\" << m_totalSize << \" bytes)\"; disconnect(m_socket, &QTcpSocket::bytesWritten, this, &LargeDataSender::onBytesWritten); }}void LargeDataSender::onBytesWritten(qint64 /*bytes*/){ writeNextChunk(); // 写入下一段数据}

使用方法:

QTcpSocket *socket = new QTcpSocket(this);socket->connectToHost(\"127.0.0.1\", 12345);if (socket->waitForConnected(3000)) { QFile file(\"largefile.bin\"); if (file.open(QIODevice::ReadOnly)) { QByteArray fileData = file.readAll(); LargeDataSender *sender = new LargeDataSender(socket, this); sender->send(fileData); // 启动分段发送 }}

2.5 QByteArray QIODevice::read(qint64 maxSize)

参数说明:maxSize是要读取的最大字节数。实际读取的可能小于 maxSize,具体取决于设备中当前可用的数据量。

返回值

  • 返回一个 QByteArray,包含读取到的数据。
  • 如果没有数据可读,则返回空的 QByteArray(不是 nullptr)。
  • 如果读取失败(如设备未打开),返回空。

使用示例(基于 QTcpSocket)
示例 1:简单读取 socket 中的数据

connect(socket, &QTcpSocket::readyRead, this, [=]() { QByteArray data = socket->read(1024); // 最多读取 1024 字节 qDebug() << \"Received:\" << data;});

示例 2:持续读取直到没有更多数据(适合大数据)

connect(socket, &QTcpSocket::readyRead, this, [=]() { while (socket->bytesAvailable() > 0) { QByteArray chunk = socket->read(4096); // 每次读 4KB processData(chunk); }});

注意事项
Qt开发:QTcpSocket的详解
示例 3:读取带长度前缀的结构化数据
这种方式适合从 TCP 中接收完整消息(防止粘包问题)。

void MyReceiver::onReadyRead(){ QDataStream in(socket); in.setByteOrder(QDataStream::BigEndian); static quint32 expectedSize = 0; while (true) { if (expectedSize == 0) { if (socket->bytesAvailable() < sizeof(quint32)) return; in >> expectedSize; // 先读长度 } if (socket->bytesAvailable() < expectedSize) return; QByteArray payload = socket->read(expectedSize); processPayload(payload); expectedSize = 0; // 重置准备读下一包 }}

2.6 QByteArray QIODevice::readAll() const
功能说明:

  • 读取并返回 socket 缓冲区中当前已接收到的全部数据。
  • 不会阻塞,只能读取当前可用的部分数据(TCP 流可能分段到达)。
  • 回一个 QByteArray,包含已读数据;若没有数据,返回空。

使用示例:
示例 1:读取客户端发来的完整数据(非结构化协议)

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

2.7 bool QIODevice::isOpen() const

作用:判断 socket(或其他 IO 设备)是否被成功打开。对 QTcpSocket 来说,打开意味着 connectToHost() 已成功调用,并进入连接状态(ConnectedState)。

使用示例

QTcpSocket *socket = new QTcpSocket(this);socket->connectToHost(\"127.0.0.1\", 12345);if (socket->waitForConnected(3000)) { if (socket->isOpen()) { qDebug() << \"Socket is open.\"; }}

2.8 bool QAbstractSocket::isValid() const

作用:判断 socket 的底层文件描述符是否有效。也就是它是否关联了一个系统资源(如 socket fd)。
即使 isOpen() 返回 true,isValid() 也可能是 false,如果底层 socket 描述符丢失或已关闭。

使用示例

if (socket->isValid()) { qDebug() << \"Socket is valid (underlying descriptor exists).\";}

区别总结
Qt开发:QTcpSocket的详解
建议使用方式

if (socket->isOpen() && socket->isValid()) { socket->write(\"ping\");}

检测当前 socket 是否仍处于有效连接状态

bool isSocketConnected(QTcpSocket *socket){ if (!socket) return false; // 判断 socket 是否处于 ConnectedState if (socket->state() != QAbstractSocket::ConnectedState) return false; // 是否逻辑打开 if (!socket->isOpen()) return false; // 底层 socket 是否还有效 if (!socket->isValid()) return false; // 可选:检测是否最近有错误 if (socket->error() != QAbstractSocket::UnknownSocketError) return false; return true;}

使用方法:

if (isSocketConnected(socket)) { socket->write(\"Hello\\n\");} else { qDebug() << \"Socket disconnected or invalid!\";}

2.9 bool QAbstractSocket::waitForConnected(int msecs = 30000)

参数说明:msecs是最长等待时间,单位是毫秒(ms)默认是 30000ms(30秒)

返回值:true表示成功连接到服务器,false表示超时、错误,未连接成功

使用示例(客户端连接服务器)

QTcpSocket *socket = new QTcpSocket(this);socket->connectToHost(\"127.0.0.1\", 12345); // 发起连接if (socket->waitForConnected(3000)) { // 最多等待 3 秒 qDebug() << \"Connected to server!\"; socket->write(\"hello\");} else { qWarning() << \"Connection failed:\" << socket->errorString();}

2.10 bool QIODevice::waitForReadyRead(int msecs)
是 Qt 中 QIODevice 提供的一个阻塞函数,常用于 QTcpSocket 等设备,等待有数据可读。当你想同步读取数据时,这个函数非常实用。

参数说明:最多等待时间(单位:毫秒)默认由我们传入,例如 3000 表示最多等 3 秒

返回值:true表示已有数据可读,readyRead() 被触发,false表示超时或设备出错,无数据可读

使用示例(读取数据)

QTcpSocket socket;socket.connectToHost(\"127.0.0.1\", 12345);if (socket.waitForConnected(3000)) { socket.write(\"hello\"); if (socket.waitForReadyRead(3000)) { QByteArray data = socket.readAll(); qDebug() << \"Received:\" << data; } else { qWarning() << \"No response from server, timeout!\"; }} else { qWarning() << \"Failed to connect to server!\";}

三、常用的信号函数

Qt开发:QTcpSocket的详解
基本使用示例:

#include #include class MyTcpClient : public QObject { Q_OBJECTpublic: MyTcpClient(QObject* parent = nullptr) : QObject(parent) { socket = new QTcpSocket(this); connect(socket, &QTcpSocket::connected, this, &MyTcpClient::onConnected); connect(socket, &QTcpSocket::readyRead, this, &MyTcpClient::onReadyRead); connect(socket, &QTcpSocket::disconnected, this, &MyTcpClient::onDisconnected); connect(socket, &QTcpSocket::error, this, &MyTcpClient::onError); socket->connectToHost(\"127.0.0.1\", 12345); // 连接服务器 }private slots: void onConnected() { qDebug() << \"Connected to server.\"; socket->write(\"Hello, Server!\"); // 向服务器发送数据 } void onReadyRead() { QByteArray data = socket->readAll(); qDebug() << \"Received from server:\" << data; } void onDisconnected() { qDebug() << \"Disconnected from server.\"; } void onError(QAbstractSocket::SocketError socketError) { qDebug() << \"Socket error:\" << socketError << socket->errorString(); }private: QTcpSocket* socket;};

注意事项:

  • QTcpSocket 一般在主线程使用,不阻塞 UI,数据通过信号处理;
  • 使用 write() 后可能不会立刻发送,建议配合 flush() 或等待 bytesWritten;
  • 注意处理粘包和拆包问题,TCP 是面向流的协议;
  • 可与 QDataStream 配合使用实现结构化数据传输。