Qt QTcpSocket 详解
1. QTcpSocket 概述
QTcpSocket 是 Qt 中用于 TCP 网络通信的类,它提供了面向连接的可靠数据传输。
2. 基本使用流程
2.1 创建和连接
cpp
// 创建 TCP Socket
QTcpSocket *socket = new QTcpSocket(this);
// 连接到服务器
socket->connectToHost("example.com", 1234);
// 检查连接状态
if (socket->waitForConnected(3000)) {
qDebug() << "Connected!";
} else {
qDebug() << "Connection failed:" << socket->errorString();
}
2.2 信号槽连接
cpp
// 连接相关信号
connect(socket, &QTcpSocket::connected, this, &MyClass::onConnected);
connect(socket, &QTcpSocket::disconnected, this, &MyClass::onDisconnected);
connect(socket, &QTcpSocket::readyRead, this, &MyClass::onReadyRead);
connect(socket, &QTcpSocket::bytesWritten, this, &MyClass::onBytesWritten);
connect(socket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::errorOccurred),
this, &MyClass::onSocketError);
// 或者使用旧式信号槽语法(兼容性更好)
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(onSocketError(QAbstractSocket::SocketError)));
3. 数据传输
3.1 发送数据
cpp
// 发送文本数据
QString message = "Hello Server!";
QByteArray data = message.toUtf8(); // 转换为 UTF-8 字节数组
socket->write(data);
socket->flush(); // 强制发送缓冲区数据
// 发送二进制数据
QByteArray binaryData;
QDataStream stream(&binaryData, QIODevice::WriteOnly);
stream << qint32(100) << QString("Test") << QByteArray("Data");
socket->write(binaryData);
// 发送带长度前缀的数据(推荐)
void sendData(const QByteArray &data)
{
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_15);
// 先写入数据长度(4字节)
out << quint32(0) << data;
// 回到开头,写入实际长度
out.device()->seek(0);
out << quint32(block.size() - sizeof(quint32));
socket->write(block);
}
3.2 接收数据
cpp
// 读取所有可用数据
void onReadyRead()
{
QByteArray data = socket->readAll();
qDebug() << "Received:" << data;
}
// 按行读取(如果使用换行符分隔)
void onReadyRead()
{
while (socket->canReadLine()) {
QByteArray line = socket->readLine();
qDebug() << "Line:" << line.trimmed();
}
}
// 读取带长度前缀的数据
void onReadyRead()
{
static qint32 blockSize = 0;
while (socket->bytesAvailable() > 0) {
if (blockSize == 0) {
// 检查是否有足够的数据读取长度前缀
if (socket->bytesAvailable() < sizeof(quint32))
return;
QDataStream in(socket);
in.setVersion(QDataStream::Qt_5_15);
in >> blockSize;
}
// 检查完整数据包是否到达
if (socket->bytesAvailable() < blockSize)
return;
// 读取完整数据
QByteArray data = socket->read(blockSize);
processData(data);
blockSize = 0; // 重置,准备读取下一个数据包
}
}
4. 错误处理
cpp
void onSocketError(QAbstractSocket::SocketError socketError)
{
switch (socketError) {
case QAbstractSocket::ConnectionRefusedError:
qDebug() << "Connection refused";
break;
case QAbstractSocket::RemoteHostClosedError:
qDebug() << "Remote host closed connection";
break;
case QAbstractSocket::HostNotFoundError:
qDebug() << "Host not found";
break;
case QAbstractSocket::SocketTimeoutError:
qDebug() << "Connection timeout";
break;
case QAbstractSocket::NetworkError:
qDebug() << "Network error";
break;
default:
qDebug() << "Error:" << socket->errorString();
}
}
5. 状态管理
cpp
// 检查当前状态
QAbstractSocket::SocketState state = socket->state();
switch (state) {
case QAbstractSocket::UnconnectedState:
qDebug() << "Not connected";
break;
case QAbstractSocket::HostLookupState:
qDebug() << "Looking up host...";
break;
case QAbstractSocket::ConnectingState:
qDebug() << "Connecting...";
break;
case QAbstractSocket::ConnectedState:
qDebug() << "Connected";
break;
case QAbstractSocket::ClosingState:
qDebug() << "Closing...";
break;
}
// 检查是否可读/可写
if (socket->isReadable()) {
// 可以读取数据
}
if (socket->isWritable()) {
// 可以写入数据
}
// 获取本地和远程地址信息
QString localAddress = socket->localAddress().toString();
quint16 localPort = socket->localPort();
QString peerAddress = socket->peerAddress().toString();
quint16 peerPort = socket->peerPort();
QString peerName = socket->peerName();
6. 高级功能
6.1 设置 Socket 选项
cpp
// 设置缓冲区大小
socket->setReadBufferSize(1024 * 1024); // 1MB
socket->setSocketOption(QAbstractSocket::SendBufferSizeSocketOption, 1024 * 1024);
// 设置超时
socket->setSocketOption(QAbstractSocket::LowDelayOption, 1); // 启用 Nagle 算法
// 设置保持连接
socket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
// 获取选项值
int bufferSize = socket->socketOption(QAbstractSocket::SendBufferSizeSocketOption).toInt();
6.2 使用代理
cpp
// 设置代理
QNetworkProxy proxy;
proxy.setType(QNetworkProxy::Socks5Proxy);
proxy.setHostName("proxy.example.com");
proxy.setPort(1080);
proxy.setUser("username");
proxy.setPassword("password");
socket->setProxy(proxy);
6.3 SSL/TLS 支持(使用 QSslSocket)
cpp
#include <QSslSocket>
#include <QSslConfiguration>
QSslSocket *sslSocket = new QSslSocket(this);
// 设置 SSL 配置
QSslConfiguration sslConfig = sslSocket->sslConfiguration();
sslConfig.setProtocol(QSsl::TlsV1_2OrLater);
sslSocket->setSslConfiguration(sslConfig);
// 验证证书
connect(sslSocket, QOverload<const QList<QSslError> &>::of(&QSslSocket::sslErrors),
this, &MyClass::onSslErrors);
// 建立安全连接
sslSocket->connectToHostEncrypted("secure.example.com", 443);
7. 完整客户端示例
cpp
class TcpClient : public QObject
{
Q_OBJECT
public:
explicit TcpClient(QObject *parent = nullptr)
: QObject(parent)
{
socket = new QTcpSocket(this);
// 连接信号槽
connect(socket, &QTcpSocket::connected, this, &TcpClient::onConnected);
connect(socket, &QTcpSocket::disconnected, this, &TcpClient::onDisconnected);
connect(socket, &QTcpSocket::readyRead, this, &TcpClient::onReadyRead);
connect(socket, &QTcpSocket::bytesWritten, this, &TcpClient::onBytesWritten);
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(onSocketError(QAbstractSocket::SocketError)));
}
void connectToServer(const QString &host, quint16 port)
{
qDebug() << "Connecting to" << host << ":" << port;
socket->connectToHost(host, port);
if (!socket->waitForConnected(5000)) {
qDebug() << "Connection failed:" << socket->errorString();
}
}
void sendMessage(const QString &message)
{
if (socket->state() == QAbstractSocket::ConnectedState) {
QByteArray data = message.toUtf8();
socket->write(data);
qDebug() << "Sent:" << message;
}
}
void disconnectFromServer()
{
socket->disconnectFromHost();
if (socket->state() != QAbstractSocket::UnconnectedState) {
socket->waitForDisconnected(3000);
}
}
private slots:
void onConnected()
{
qDebug() << "Connected to server";
emit connected();
}
void onDisconnected()
{
qDebug() << "Disconnected from server";
emit disconnected();
}
void onReadyRead()
{
QByteArray data = socket->readAll();
QString message = QString::fromUtf8(data);
qDebug() << "Received:" << message;
emit messageReceived(message);
}
void onBytesWritten(qint64 bytes)
{
qDebug() << "Bytes written:" << bytes;
}
void onSocketError(QAbstractSocket::SocketError error)
{
qDebug() << "Socket error:" << socket->errorString();
emit errorOccurred(socket->errorString());
}
signals:
void connected();
void disconnected();
void messageReceived(const QString &message);
void errorOccurred(const QString &errorString);
private:
QTcpSocket *socket;
};
8. 与 QTcpServer 配合使用
cpp
// 服务器端示例
class TcpServer : public QObject
{
Q_OBJECT
public:
explicit TcpServer(QObject *parent = nullptr)
: QObject(parent)
{
server = new QTcpServer(this);
connect(server, &QTcpServer::newConnection, this, &TcpServer::onNewConnection);
}
bool start(quint16 port)
{
if (!server->listen(QHostAddress::Any, port)) {
qDebug() << "Server could not start:" << server->errorString();
return false;
}
qDebug() << "Server started on port" << port;
return true;
}
void stop()
{
server->close();
qDeleteAll(clients);
clients.clear();
}
private slots:
void onNewConnection()
{
QTcpSocket *clientSocket = server->nextPendingConnection();
Client *client = new Client(clientSocket, this);
clients.append(client);
connect(client, &Client::disconnected, this, [this, client]() {
clients.removeOne(client);
client->deleteLater();
});
qDebug() << "New client connected from"
<< clientSocket->peerAddress().toString()
<< ":" << clientSocket->peerPort();
}
private:
QTcpServer *server;
QList<Client*> clients;
};
// 客户端处理器
class Client : public QObject
{
Q_OBJECT
public:
Client(QTcpSocket *socket, QObject *parent = nullptr)
: QObject(parent), socket(socket)
{
connect(socket, &QTcpSocket::readyRead, this, &Client::onReadyRead);
connect(socket, &QTcpSocket::disconnected, this, &Client::disconnected);
}
private slots:
void onReadyRead()
{
QByteArray data = socket->readAll();
// 处理客户端数据
processData(data);
// 回复客户端
QString response = "OK";
socket->write(response.toUtf8());
}
signals:
void disconnected();
private:
QTcpSocket *socket;
};
9. 最佳实践和注意事项
9.1 数据编码
cpp
// 始终指定编码
QString text = QString::fromUtf8(data); // 从 UTF-8 解码
QByteArray data = text.toUtf8(); // 编码为 UTF-8
// 或者在 QDataStream 中指定版本
QDataStream stream(&data, QIODevice::ReadOnly);
stream.setVersion(QDataStream::Qt_5_15); // 指定 Qt 版本以确保兼容性
9.2 超时处理
cpp
// 设置操作超时
if (!socket->waitForConnected(5000)) {
// 连接超时
}
if (!socket->waitForReadyRead(3000)) {
// 读取超时
}
if (!socket->waitForBytesWritten(3000)) {
// 写入超时
}
9.3 线程安全
cpp
// 在多线程环境中使用
class NetworkThread : public QThread
{
Q_OBJECT
public:
void run() override {
QTcpSocket socket;
socket.connectToHost("example.com", 80);
// ... 其他操作
}
};
// 或者使用 moveToThread
QTcpSocket *socket = new QTcpSocket();
socket->moveToThread(workerThread);
9.4 资源清理
cpp
// 正确关闭连接
void closeConnection()
{
socket->disconnectFromHost();
if (socket->state() != QAbstractSocket::UnconnectedState) {
socket->waitForDisconnected(3000);
}
socket->close();
socket->deleteLater();
}
// 在析构函数中清理
~MyClass() {
if (socket && socket->state() == QAbstractSocket::ConnectedState) {
socket->disconnectFromHost();
socket->waitForDisconnected(1000);
}
delete socket;
}
10. 常见问题解决
10.1 粘包/拆包问题
cpp
// 解决方案:使用固定长度、分隔符或长度前缀
// 示例:长度前缀法
void sendPacket(const QByteArray &data)
{
quint32 length = data.size();
QByteArray packet;
QDataStream out(&packet, QIODevice::WriteOnly);
out << length;
packet.append(data);
socket->write(packet);
}
QByteArray readPacket()
{
if (expectedSize == 0) {
if (socket->bytesAvailable() < sizeof(quint32))
return QByteArray();
QDataStream in(socket);
in >> expectedSize;
}
if (socket->bytesAvailable() < expectedSize)
return QByteArray();
QByteArray data = socket->read(expectedSize);
expectedSize = 0;
return data;
}
10.2 大文件传输
cpp
// 分块传输大文件
void sendFile(const QString &filePath)
{
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly))
return;
// 发送文件信息(大小、文件名等)
QByteArray header;
QDataStream out(&header, QIODevice::WriteOnly);
out << quint64(file.size()) << QFileInfo(file).fileName();
socket->write(header);
// 分块发送文件内容
const qint64 chunkSize = 64 * 1024; // 64KB
while (!file.atEnd()) {
QByteArray chunk = file.read(chunkSize);
socket->write(chunk);
socket->waitForBytesWritten();
}
file.close();
}
总结
-
QTcpSocket 提供完整的 TCP 客户端功能
-
使用信号槽机制处理异步事件
-
注意数据编码和格式,特别是跨平台时
-
正确处理粘包/拆包问题
-
实现适当的错误处理和超时机制
-
在多线程环境中小心使用,确保线程安全
-
及时释放资源,避免内存泄漏