Qt开发:QTcpSocket的详解
文章目录
-
- 一、QTcpSocket 简介
- 二、常用方法的介绍和使用
- 三、常用的信号函数
一、QTcpSocket 简介
QTcpSocket 是 Qt 网络模块中用于实现基于 TCP 协议的客户端通信的类。它提供了一个面向流的接口,允许程序通过套接字连接到远程主机,发送和接收数据。
- 所属模块:QtNetwork
- 用于建立 TCP 客户端连接,读取/写入数据。
- 特点:异步非阻塞(配合信号槽机制)、支持事件驱动编程模型。
二、常用方法的介绍和使用
2.1 void QTcpSocket::connectToHost(hostname, port)
用于发起 TCP 连接的核心函数,常用于客户端向服务器发起连接请求。
参数说明:
使用示例:
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();}
常见错误排查:
2.2 void QAbstractSocket::disconnectFromHost()
用于主动断开 TCP 连接的函数。它的作用是通知对方:客户端希望关闭连接,然后进行连接释放。
使用说明:
当调用 disconnectFromHost() 后:
- 客户端发送 TCP 的 FIN 包;
- 如果连接关闭成功,触发 disconnected() 信号;
- 如果对方已经关闭连接,你仍然可以调用它释放资源;
- 若要强制关闭连接,可以使用 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() 的区别:
建议:正常退出时用 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); }});
注意事项:
示例 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).\";}
区别总结:
建议使用方式:
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!\";}
三、常用的信号函数
基本使用示例:
#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 配合使用实现结构化数据传输。