QT提供了强大的网络编程支持,使得TCP网络应用的开发变得简单。本指南将带你入门QT的TCP网络编程。
基本概念
QT中TCP网络编程主要涉及两个类:
-
QTcpSocket
- 用于建立TCP连接和传输数据 -
QTcpServer
- 用于监听和接受TCP连接
QTcpServer 属性、接口、信号与槽
属性总结
属性 | 类型 | 说明 | 访问函数 |
---|---|---|---|
maxPendingConnections |
int |
最大等待连接数,超过此数的新连接将被拒绝 | int maxPendingConnections() const void setMaxPendingConnections(int numConnections) |
listenBacklog |
int |
底层监听队列的大小(仅在某些平台有效) | int listenBacklog() const void setListenBacklog(int backlog) |
serverAddress |
QHostAddress |
服务器监听的地址 | QHostAddress serverAddress() const |
serverPort |
quint16 |
服务器监听的端口 | quint16 serverPort() const |
socketDescriptor |
qintptr |
服务器套接字描述符 | qintptr socketDescriptor() const |
isListening |
bool |
是否正在监听连接 | bool isListening() const |
主要接口方法
方法 | 说明 |
---|---|
bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0) |
开始监听指定地址和端口 |
void close() |
停止监听并关闭服务器 |
bool waitForNewConnection(int msec = 0, bool *timedOut = nullptr) |
等待新连接(阻塞方式) |
QTcpSocket *nextPendingConnection() |
返回下一个挂起的连接作为QTcpSocket对象 |
QAbstractSocket::SocketError serverError() |
返回最后一次发生的错误 |
QString errorString() |
返回最后一次错误的可读描述 |
void pauseAccepting() |
暂停接受新连接 |
void resumeAccepting() |
恢复接受新连接 |
信号
信号 | 说明 |
---|---|
void newConnection() |
当有新连接可用时发射 |
void acceptError(QAbstractSocket::SocketError socketError) |
在接受新连接发生错误时发射 |
常用槽连接示例
1. 处理新连接
cpp
connect(tcpServer, &QTcpServer::newConnection, this, [this]() {
while (tcpServer->hasPendingConnections()) {
QTcpSocket *clientSocket = tcpServer->nextPendingConnection();
// 处理新客户端连接
handleNewClient(clientSocket);
}
});
2. 处理接受错误
cpp
connect(tcpServer, &QTcpServer::acceptError, this, [](QAbstractSocket::SocketError error) {
qWarning() << "Accept error:" << error;
});
完整使用示例
cpp
// 创建服务器
QTcpServer *server = new QTcpServer(this);
// 设置属性
server->setMaxPendingConnections(10); // 最多10个等待连接
// 连接信号
connect(server, &QTcpServer::newConnection, this, &MyClass::handleNewConnection);
connect(server, &QTcpServer::acceptError, this, &MyClass::handleAcceptError);
// 开始监听
if (!server->listen(QHostAddress::Any, 12345)) {
qDebug() << "Server could not start:" << server->errorString();
} else {
qDebug() << "Server started on port" << server->serverPort();
}
// 处理新连接的槽函数
void MyClass::handleNewConnection()
{
QTcpSocket *clientSocket = server->nextPendingConnection();
// 连接客户端信号
connect(clientSocket, &QTcpSocket::readyRead, this, &MyClass::readClientData);
connect(clientSocket, &QTcpSocket::disconnected, clientSocket, &QTcpSocket::deleteLater);
qDebug() << "New connection from:" << clientSocket->peerAddress().toString();
}
// 处理接受错误的槽函数
void MyClass::handleAcceptError(QAbstractSocket::SocketError socketError)
{
qDebug() << "Accept error:" << server->errorString();
}
注意事项
-
内存管理 :使用
nextPendingConnection()
返回的QTcpSocket对象需要手动管理生命周期 -
线程安全:QTcpServer通常在主线程中使用,跨线程使用需要小心
-
错误处理:始终检查listen()的返回值和监听错误信号
-
性能考虑:对于高并发场景,考虑使用线程池处理连接
-
平台差异:某些属性(如listenBacklog)在不同平台上的行为可能不同
QTcpSocket 属性、接口、信号与槽
属性总结
属性 | 类型 | 说明 | 访问函数 |
---|---|---|---|
localAddress |
QHostAddress |
本地绑定的IP地址 | QHostAddress localAddress() const |
localPort |
quint16 |
本地绑定的端口号 | quint16 localPort() const |
peerAddress |
QHostAddress |
远程主机的IP地址 | QHostAddress peerAddress() const |
peerPort |
quint16 |
远程主机的端口号 | quint16 peerPort() const |
peerName |
QString |
远程主机名称 | QString peerName() const void setPeerName(const QString &name) |
socketDescriptor |
qintptr |
底层套接字描述符 | qintptr socketDescriptor() const |
state |
QAbstractSocket::SocketState |
套接字当前状态 | QAbstractSocket::SocketState state() const |
error |
QAbstractSocket::SocketError |
最后发生的错误类型 | QAbstractSocket::SocketError error() const |
errorString |
QString |
最后错误的可读描述 | QString errorString() const |
readBufferSize |
qint64 |
读取缓冲区大小(字节) | qint64 readBufferSize() const void setReadBufferSize(qint64 size) |
主要接口方法
连接相关
方法 | 说明 |
---|---|
void connectToHost(const QString &hostName, quint16 port, OpenMode mode = ReadWrite, NetworkLayerProtocol protocol = AnyIPProtocol) |
连接到主机 |
void connectToHost(const QHostAddress &address, quint16 port, OpenMode mode = ReadWrite) |
通过IP地址连接到主机 |
void disconnectFromHost() |
断开连接 |
void abort() |
立即关闭连接,丢弃写缓冲区中的数据 |
bool waitForConnected(int msec = 30000) |
等待连接建立(阻塞) |
bool waitForDisconnected(int msec = 30000) |
等待断开连接(阻塞) |
数据传输相关
方法 | 说明 |
---|---|
qint64 write(const char *data, qint64 len) |
写入原始数据 |
qint64 write(const QByteArray &data) |
写入字节数组 |
qint64 read(char *data, qint64 maxlen) |
读取原始数据 |
QByteArray read(qint64 maxlen) |
读取指定长度的字节 |
QByteArray readAll() |
读取所有可用数据 |
bool canReadLine() const |
是否有整行数据可读 |
QByteArray readLine(qint64 maxlen = 0) |
读取一行数据 |
bool flush() |
尝试刷新内部写缓冲区 |
qint64 bytesAvailable() const |
可读字节数 |
qint64 bytesToWrite() const |
等待写入的字节数 |
bool waitForReadyRead(int msec = 30000) |
等待数据可读(阻塞) |
bool waitForBytesWritten(int msec = 30000) |
等待数据写入完成(阻塞) |
状态查询
方法 | 说明 |
---|---|
bool isValid() const |
套接字是否有效 |
bool isSequential() const |
是否为顺序设备(总是true) |
bool atEnd() const |
是否已到达数据末尾 |
bool isOpen() const |
套接字是否打开 |
bool isReadable() const |
是否可读 |
bool isWritable() const |
是否可写 |
信号
信号 | 说明 |
---|---|
void connected() |
成功建立连接时发射 |
void disconnected() |
连接断开时发射 |
void errorOccurred(QAbstractSocket::SocketError socketError) |
发生错误时发射 |
void stateChanged(QAbstractSocket::SocketState socketState) |
状态改变时发射 |
void readyRead() |
有新数据可读时发射 |
void bytesWritten(qint64 bytes) |
数据写入完成时发射 |
void aboutToClose() |
套接字即将关闭时发射 |
void readChannelFinished() |
读取通道关闭时发射 |
常用槽连接示例
1. 基本连接管理
cpp
connect(socket, &QTcpSocket::connected, this, []() {
qDebug() << "Connected to server!";
});
connect(socket, &QTcpSocket::disconnected, this, []() {
qDebug() << "Disconnected from server";
});
connect(socket, &QTcpSocket::errorOccurred, this, [](QAbstractSocket::SocketError error) {
qDebug() << "Socket error:" << error;
});
2. 数据传输处理
cpp
// 数据可读时处理
connect(socket, &QTcpSocket::readyRead, this, [this]() {
while (socket->bytesAvailable()) {
QByteArray data = socket->readAll();
processData(data); // 处理接收到的数据
}
});
// 数据写入完成通知
connect(socket, &QTcpSocket::bytesWritten, this, [](qint64 bytes) {
qDebug() << bytes << "bytes written";
});
3. 状态监控
cpp
connect(socket, &QTcpSocket::stateChanged, this, [](QAbstractSocket::SocketState state) {
qDebug() << "Socket state changed to:" << state;
});
完整使用示例
cpp
// 创建套接字
QTcpSocket *socket = new QTcpSocket(this);
// 设置属性
socket->setReadBufferSize(1024 * 1024); // 设置1MB的读取缓冲区
// 连接信号
connect(socket, &QTcpSocket::connected, this, &MyClass::onConnected);
connect(socket, &QTcpSocket::disconnected, this, &MyClass::onDisconnected);
connect(socket, &QTcpSocket::readyRead, this, &MyClass::onReadyRead);
connect(socket, &QTcpSocket::errorOccurred, this, &MyClass::onError);
// 连接到服务器
socket->connectToHost("example.com", 1234);
// 发送数据
void MyClass::sendData(const QByteArray &data)
{
if (socket->state() == QAbstractSocket::ConnectedState) {
socket->write(data);
}
}
// 槽函数实现
void MyClass::onConnected()
{
qDebug() << "Connected to" << socket->peerAddress() << "port" << socket->peerPort();
}
void MyClass::onDisconnected()
{
qDebug() << "Disconnected from server";
socket->deleteLater();
}
void MyClass::onReadyRead()
{
QByteArray data = socket->readAll();
qDebug() << "Received data:" << data;
}
void MyClass::onError(QAbstractSocket::SocketError error)
{
qDebug() << "Socket error:" << socket->errorString();
}
注意事项
-
线程安全:QTcpSocket实例通常应在创建它的线程中使用
-
内存管理:确保正确管理QTcpSocket的生命周期,特别是在多线程环境中
-
错误处理:始终检查操作返回值并处理错误信号
-
数据边界:TCP是流协议,需要自己处理消息边界
-
性能优化:大数据传输时考虑分块处理和使用缓冲区
-
超时处理:重要操作应设置合理的超时时间
服务器端实现
1. 创建TCP服务器
cpp
#include <QTcpServer>
#include <QTcpSocket>
// 创建服务器实例
QTcpServer *server = new QTcpServer(this);
// 监听指定端口
if (!server->listen(QHostAddress::Any, 1234)) {
qDebug() << "Server could not start!";
} else {
qDebug() << "Server started!";
}
2. 处理新连接
cpp
// 连接新连接信号到槽函数
connect(server, &QTcpServer::newConnection, this, &MyClass::newConnection);
void MyClass::newConnection()
{
// 获取客户端连接
QTcpSocket *socket = server->nextPendingConnection();
// 连接断开信号
connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater);
// 连接数据可读信号
connect(socket, &QTcpSocket::readyRead, this, &MyClass::readData);
qDebug() << "Client connected:" << socket->peerAddress().toString();
}
3. 读取数据
cpp
void MyClass::readData()
{
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
if (!socket) return;
QByteArray data = socket->readAll();
qDebug() << "Received:" << data;
// 回复客户端
socket->write("Message received");
}
客户端实现
1. 创建TCP客户端
cpp
#include <QTcpSocket>
QTcpSocket *socket = new QTcpSocket(this);
// 连接信号
connect(socket, &QTcpSocket::connected, this, &MyClass::connected);
connect(socket, &QTcpSocket::readyRead, this, &MyClass::readyRead);
connect(socket, &QTcpSocket::disconnected, this, &MyClass::disconnected);
connect(socket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error),
this, &MyClass::socketError);
// 连接到服务器
socket->connectToHost("127.0.0.1", 1234);
2. 处理连接事件
cpp
void MyClass::connected()
{
qDebug() << "Connected to server!";
socket->write("Hello server!");
}
void MyClass::disconnected()
{
qDebug() << "Disconnected from server";
}
void MyClass::socketError(QAbstractSocket::SocketError error)
{
qDebug() << "Error:" << socket->errorString();
}
3. 读取服务器数据
cpp
void MyClass::readyRead()
{
QByteArray data = socket->readAll();
qDebug() << "Received:" << data;
}
完整示例
服务器端完整代码
cpp
#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>
class MyServer : public QObject
{
Q_OBJECT
public:
MyServer(QObject *parent = nullptr) : QObject(parent)
{
server = new QTcpServer(this);
connect(server, &QTcpServer::newConnection, this, &MyServer::newConnection);
if (!server->listen(QHostAddress::Any, 1234)) {
qDebug() << "Server could not start!";
} else {
qDebug() << "Server started!";
}
}
private slots:
void newConnection()
{
QTcpSocket *socket = server->nextPendingConnection();
connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater);
connect(socket, &QTcpSocket::readyRead, this, &MyServer::readData);
qDebug() << "Client connected:" << socket->peerAddress().toString();
socket->write("Welcome to the server!");
}
void readData()
{
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
if (!socket) return;
QByteArray data = socket->readAll();
qDebug() << "Received:" << data;
socket->write("Echo: " + data);
}
private:
QTcpServer *server;
};
客户端完整代码
cpp
#include <QTcpSocket>
#include <QDebug>
class MyClient : public QObject
{
Q_OBJECT
public:
MyClient(QObject *parent = nullptr) : QObject(parent)
{
socket = new QTcpSocket(this);
connect(socket, &QTcpSocket::connected, this, &MyClient::connected);
connect(socket, &QTcpSocket::readyRead, this, &MyClient::readyRead);
connect(socket, &QTcpSocket::disconnected, this, &MyClient::disconnected);
connect(socket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error),
this, &MyClient::socketError);
socket->connectToHost("127.0.0.1", 1234);
}
void sendMessage(const QString &message)
{
if (socket->state() == QAbstractSocket::ConnectedState) {
socket->write(message.toUtf8());
}
}
private slots:
void connected()
{
qDebug() << "Connected to server!";
socket->write("Hello server!");
}
void disconnected()
{
qDebug() << "Disconnected from server";
}
void socketError(QAbstractSocket::SocketError error)
{
qDebug() << "Error:" << socket->errorString();
}
void readyRead()
{
QByteArray data = socket->readAll();
qDebug() << "Received:" << data;
}
private:
QTcpSocket *socket;
};
注意事项
-
线程安全:网络操作通常在单独的线程中进行,避免阻塞主线程
-
错误处理:始终检查连接状态和处理错误
-
数据格式:定义应用层协议来处理消息边界(如添加消息长度前缀)
-
资源管理:及时释放不再使用的socket连接
-
跨平台兼容:QT网络模块在不同平台上行为一致
使用QDataStream进行结构化数据读写
QT中的QDataStream
类提供了一种方便的方式来序列化和反序列化结构化数据,非常适合在TCP网络通信中传输复杂数据类型。
基本用法
1. 写入数据到QByteArray
cpp
QByteArray byteArray;
QDataStream out(&byteArray, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_15); // 设置版本确保兼容性
// 写入各种类型数据
out << QString("Hello World") << qint32(42) << QDateTime::currentDateTime();
2. 从QByteArray读取数据
cpp
QDataStream in(&byteArray, QIODevice::ReadOnly);
in.setVersion(QDataStream::Qt_5_15);
QString str;
qint32 num;
QDateTime date;
// 读取顺序必须与写入顺序一致
in >> str >> num >> date;
在TCP通信中的应用
服务器端发送结构化数据
cpp
void MyServer::sendDataToClient(QTcpSocket *socket)
{
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_15);
// 写入数据
out << QString("Server Message")
<< QDateTime::currentDateTime()
<< qint64(123456789);
// 在实际应用中,你可能需要先发送数据大小
// socket->write(QByteArray::number(block.size()));
// 发送数据
socket->write(block);
}
客户端接收结构化数据
cpp
void MyClient::readyRead()
{
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
if (!socket) return;
QDataStream in(socket);
in.setVersion(QDataStream::Qt_5_15);
// 如果缓冲区数据不足,等待更多数据
if (socket->bytesAvailable() < sizeof(quint16))
return;
QString message;
QDateTime timestamp;
qint64 number;
in >> message >> timestamp >> number;
qDebug() << "Received message:" << message;
qDebug() << "Timestamp:" << timestamp.toString();
qDebug() << "Number:" << number;
}
处理消息边界问题
TCP是流式协议,没有消息边界概念,需要自己实现:
方案1:固定大小消息
cpp
// 发送固定大小消息
const int messageSize = 1024;
QByteArray message = prepareMessage(); // 你的消息准备函数
message.resize(messageSize); // 填充到固定大小
socket->write(message);
方案2:长度前缀法(推荐)
cpp
// 发送端
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_15);
out << (quint16)0; // 预留2字节空间写长度
out << yourData; // 写入实际数据
out.device()->seek(0); // 回到开头
out << (quint16)(block.size() - sizeof(quint16)); // 写入实际长度
socket->write(block);
// 接收端
void MyClient::readyRead()
{
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
if (!socket) return;
QDataStream in(socket);
in.setVersion(QDataStream::Qt_5_15);
// 等待足够数据到达
if (m_blockSize == 0) {
if (socket->bytesAvailable() < sizeof(quint16))
return;
in >> m_blockSize;
}
if (socket->bytesAvailable() < m_blockSize)
return;
// 读取完整消息
QByteArray data = socket->read(m_blockSize);
processData(data); // 处理数据
m_blockSize = 0; // 重置为下一个消息准备
}
自定义数据类型序列化
要让QDataStream支持你的自定义类型,需要重载操作符:
cpp
class CustomData {
public:
QString name;
QVector<int> values;
QMap<QString, QVariant> properties;
};
QDataStream &operator<<(QDataStream &out, const CustomData &data)
{
out << data.name << data.values << data.properties;
return out;
}
QDataStream &operator>>(QDataStream &in, CustomData &data)
{
in >> data.name >> data.values >> data.properties;
return in;
}
// 使用示例
CustomData myData;
QByteArray byteArray;
QDataStream out(&byteArray, QIODevice::WriteOnly);
out << myData;
QDataStream in(&byteArray, QIODevice::ReadOnly);
CustomData receivedData;
in >> receivedData;
注意事项
-
版本兼容性 :始终设置相同的QDataStream版本(
setVersion()
) -
字节序 :QDataStream默认使用大端序,可通过
setByteOrder()
更改 -
数据类型匹配:读取时的数据类型必须与写入时完全一致
-
错误处理 :检查流状态(
status()
)处理可能的错误 -
性能考虑:对于大量数据,考虑使用压缩或分块传输
实现TCP心跳机制保持长连接
1. 心跳机制基本原理
心跳机制是通过定期发送小数据包来:
-
检测连接是否存活
-
防止中间设备(如NAT路由器)断开空闲连接
-
维持长连接状态
2. 心跳包设计
2.1 简单心跳包格式
cpp
#pragma pack(push, 1)
struct HeartbeatPacket {
char header[4] = {'H', 'E', 'A', 'T'}; // 包头标识
quint32 timestamp; // 时间戳
quint16 crc; // 校验位
};
#pragma pack(pop)
2.2 使用QDataStream序列化
cpp
QByteArray createHeartbeatPacket() {
QByteArray packet;
QDataStream out(&packet, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_5_15);
out << quint8(0xAA) // 起始标志
<< quint8(0x01) // 心跳包类型
<< QDateTime::currentMSecsSinceEpoch(); // 时间戳
return packet;
}
3. 完整心跳实现
3.1 心跳管理器类
cpp
class HeartbeatManager : public QObject
{
Q_OBJECT
public:
explicit HeartbeatManager(QTcpSocket* socket, QObject* parent = nullptr)
: QObject(parent), m_socket(socket), m_timeoutTimer(new QTimer(this)),
m_heartbeatTimer(new QTimer(this))
{
// 超时计时器(检测无响应)
m_timeoutTimer->setInterval(15000); // 15秒超时
connect(m_timeoutTimer, &QTimer::timeout, this, [this](){
qWarning() << "Heartbeat timeout, disconnecting...";
m_socket->abort();
});
// 心跳发送计时器
m_heartbeatTimer->setInterval(5000); // 每5秒发送一次
connect(m_heartbeatTimer, &QTimer::timeout, this, &HeartbeatManager::sendHeartbeat);
// 收到数据时重置超时计时器
connect(m_socket, &QTcpSocket::readyRead, this, [this](){
m_timeoutTimer->start();
});
}
void start() {
m_timeoutTimer->start();
m_heartbeatTimer->start();
}
void stop() {
m_timeoutTimer->stop();
m_heartbeatTimer->stop();
}
signals:
void timeout();
private slots:
void sendHeartbeat() {
if (m_socket->state() == QAbstractSocket::ConnectedState) {
QByteArray packet = createHeartbeatPacket();
m_socket->write(packet);
qDebug() << "Heartbeat sent at" << QDateTime::currentDateTime().toString();
}
}
private:
QTcpSocket* m_socket;
QTimer* m_timeoutTimer;
QTimer* m_heartbeatTimer;
};
3.2 在TCP连接中使用
cpp
// 客户端使用示例
QTcpSocket* socket = new QTcpSocket(this);
socket->connectToHost("127.0.0.1", 1234);
connect(socket, &QTcpSocket::connected, [socket](){
HeartbeatManager* hbManager = new HeartbeatManager(socket, socket);
hbManager->start();
connect(hbManager, &HeartbeatManager::timeout, [](){
qDebug() << "Connection lost due to heartbeat timeout";
});
});
// 服务端处理心跳包
connect(socket, &QTcpSocket::readyRead, [socket](){
while (socket->bytesAvailable() >= sizeof(HeartbeatPacket)) {
QDataStream in(socket);
quint8 startFlag, pkgType;
qint64 timestamp;
in >> startFlag >> pkgType >> timestamp;
if (startFlag == 0xAA && pkgType == 0x01) {
qDebug() << "Received heartbeat at"
<< QDateTime::fromMSecsSinceEpoch(timestamp).toString();
// 可以在这里回复心跳响应
QByteArray response = createHeartbeatResponse();
socket->write(response);
} else {
// 处理其他类型数据包
processNormalData(in);
}
}
});
4. 高级心跳机制
4.1 自适应心跳间隔
cpp
// 根据网络状况动态调整心跳间隔
void HeartbeatManager::adjustInterval(int latency) {
// 基础间隔5秒,根据延迟动态调整(1-30秒)
int newInterval = qBound(1000, 5000 + latency * 2, 30000);
if (qAbs(newInterval - m_heartbeatTimer->interval()) > 2000) {
m_heartbeatTimer->setInterval(newInterval);
qDebug() << "Adjusted heartbeat interval to" << newInterval << "ms";
}
}
4.2 心跳响应与往返时间(RTT)计算
cpp
// 发送带序列号的心跳包
void HeartbeatManager::sendHeartbeat() {
m_lastSeq = QDateTime::currentMSecsSinceEpoch();
QByteArray packet;
QDataStream out(&packet, QIODevice::WriteOnly);
out << quint8(0xAA) << quint8(0x01) << m_lastSeq;
m_socket->write(packet);
m_pingTime = QDateTime::currentMSecsSinceEpoch();
}
// 收到响应时计算RTT
void processHeartbeatResponse(QDataStream& in) {
quint8 ackType;
qint64 seq;
in >> ackType >> seq;
if (ackType == 0x02 && seq == m_lastSeq) {
qint64 rtt = QDateTime::currentMSecsSinceEpoch() - m_pingTime;
adjustInterval(rtt);
}
}
5. 心跳包与业务数据分离
5.1 使用独立通道
cpp
// 为心跳使用单独的端口
QTcpSocket* heartbeatSocket = new QTcpSocket(this);
heartbeatSocket->connectToHost("127.0.0.1", 1235); // 专门的心跳端口
5.2 协议层分离
cpp
// 数据包格式设计
struct PacketHeader {
quint8 type; // 0x01:心跳 0x02:业务数据
quint32 length;
};
6. 处理异常情况
6.1 断线重连机制
cpp
void reconnect() {
static int retryCount = 0;
static const int maxRetry = 5;
if (retryCount < maxRetry) {
int delay = qMin(10000, 1000 * (1 << retryCount)); // 指数退避
QTimer::singleShot(delay, this, [this](){
qDebug() << "Reconnecting attempt" << (retryCount+1);
m_socket->connectToHost(m_host, m_port);
});
retryCount++;
} else {
qDebug() << "Max reconnection attempts reached";
}
}
connect(m_socket, &QTcpSocket::disconnected, this, &MyClass::reconnect);
6.2 网络状态检测
cpp
// 检测网络变化
QNetworkConfigurationManager mgr;
connect(&mgr, &QNetworkConfigurationManager::onlineStateChanged,
this, [this](bool isOnline) {
if (isOnline && m_socket->state() != QAbstractSocket::ConnectedState) {
reconnect();
}
});
7. 完整实现示例
7.1 心跳服务端
cpp
class HeartbeatServer : public QTcpServer
{
Q_OBJECT
public:
explicit HeartbeatServer(QObject* parent = nullptr)
: QTcpServer(parent)
{
connect(this, &QTcpServer::newConnection, this, &HeartbeatServer::handleNewConnection);
}
private slots:
void handleNewConnection() {
QTcpSocket* client = nextPendingConnection();
HeartbeatManager* hbManager = new HeartbeatManager(client, client);
hbManager->start();
connect(client, &QTcpSocket::readyRead, [client](){
QDataStream in(client);
while (client->bytesAvailable() >= sizeof(PacketHeader)) {
PacketHeader header;
in >> header.type >> header.length;
if (header.type == 0x01) { // 心跳包
qDebug() << "Heartbeat from" << client->peerAddress();
// 发送响应
QByteArray response = createHeartbeatResponse();
client->write(response);
} else {
// 处理业务数据
processBusinessData(in, header.length);
}
}
});
}
};
7.2 心跳客户端
cpp
class TcpClientWithHeartbeat : public QObject
{
Q_OBJECT
public:
explicit TcpClientWithHeartbeat(QObject* parent = nullptr)
: QObject(parent), m_socket(new QTcpSocket(this)),
m_hbManager(new HeartbeatManager(m_socket, this))
{
connect(m_socket, &QTcpSocket::connected, [this](){
m_hbManager->start();
qDebug() << "Connected and heartbeat started";
});
connect(m_socket, &QTcpSocket::disconnected, [](){
qDebug() << "Disconnected";
});
m_socket->connectToHost("127.0.0.1", 1234);
}
private:
QTcpSocket* m_socket;
HeartbeatManager* m_hbManager;
};
8. 注意事项
-
心跳间隔选择:通常30秒-5分钟,太短会增加负担,太长可能被NAT断开
-
超时时间设置:一般为心跳间隔的2-3倍
-
资源消耗:大量连接时考虑优化定时器实现
-
安全性:心跳包可以加入简单加密或签名防止伪造
-
移动网络适配:考虑移动网络下更频繁的心跳(如1分钟)
实现TCP大文件传输
1. 基本设计思路
大文件传输需要考虑以下几个关键点:
-
分块传输:将大文件分割成小块传输
-
进度反馈:实时反馈传输进度
-
断点续传:支持传输中断后继续传输
-
校验机制:确保数据完整性
2. 文件发送端实现
2.1 文件分块发送类
cpp
class FileSender : public QObject
{
Q_OBJECT
public:
explicit FileSender(QTcpSocket* socket, QObject* parent = nullptr)
: QObject(parent), m_socket(socket), m_file(nullptr)
{
connect(m_socket, &QTcpSocket::bytesWritten,
this, &FileSender::onBytesWritten);
}
void sendFile(const QString& filePath) {
m_file = new QFile(filePath, this);
if (!m_file->open(QIODevice::ReadOnly)) {
emit error(tr("无法打开文件"));
return;
}
m_fileSize = m_file->size();
m_bytesWritten = 0;
// 发送文件头信息(文件名+文件大小)
QByteArray header;
QDataStream ds(&header, QIODevice::WriteOnly);
ds << qint64(0) << qint64(0) << QFileInfo(filePath).fileName();
qint64 totalSize = m_file->size();
ds.device()->seek(0);
ds << totalSize << qint64(header.size() - sizeof(qint64)*2);
m_socket->write(header);
}
signals:
void progress(qint64 bytesSent, qint64 totalBytes);
void finished();
void error(const QString& message);
private slots:
void onBytesWritten(qint64 bytes) {
m_bytesWritten += bytes;
// 如果还有数据需要发送
if (m_bytesToWrite > 0 && m_file->pos() < m_fileSize) {
QByteArray chunk = m_file->read(qMin(m_bytesToWrite, m_chunkSize));
qint64 written = m_socket->write(chunk);
m_bytesToWrite -= written;
}
emit progress(m_bytesWritten, m_fileSize);
// 传输完成
if (m_bytesWritten == m_fileSize) {
m_file->close();
emit finished();
}
}
private:
QTcpSocket* m_socket;
QFile* m_file;
qint64 m_fileSize;
qint64 m_bytesWritten;
qint64 m_bytesToWrite;
const qint64 m_chunkSize = 64 * 1024; // 64KB分块
};
2.2 发送端使用示例
cpp
// 创建TCP连接
QTcpSocket* socket = new QTcpSocket(this);
socket->connectToHost("127.0.0.1", 1234);
// 创建文件发送器
FileSender* sender = new FileSender(socket, this);
connect(sender, &FileSender::progress, [](qint64 sent, qint64 total){
qDebug() << "进度:" << sent * 100.0 / total << "%";
});
connect(sender, &FileSender::finished, [](){
qDebug() << "文件传输完成";
});
// 开始发送文件
sender->sendFile("/path/to/large/file.iso");
3. 文件接收端实现
3.1 文件分块接收类
cpp
class FileReceiver : public QObject
{
Q_OBJECT
public:
explicit FileReceiver(QTcpSocket* socket, QObject* parent = nullptr)
: QObject(parent), m_socket(socket), m_file(nullptr),
m_fileSize(0), m_bytesReceived(0), m_headerSize(sizeof(qint64)*2)
{
connect(m_socket, &QTcpSocket::readyRead,
this, &FileReceiver::onReadyRead);
}
signals:
void progress(qint64 bytesReceived, qint64 totalBytes);
void finished(const QString& filePath);
void error(const QString& message);
private slots:
void onReadyRead() {
QDataStream in(m_socket);
// 如果还在接收文件头
if (m_bytesReceived < m_headerSize) {
if (m_socket->bytesAvailable() < m_headerSize - m_bytesReceived)
return;
in >> m_fileSize >> m_headerSize;
m_bytesReceived += sizeof(qint64)*2;
// 读取文件名
QString fileName;
in >> fileName;
m_bytesReceived += m_headerSize - sizeof(qint64)*2;
// 创建文件
m_file = new QFile(fileName, this);
if (!m_file->open(QIODevice::WriteOnly)) {
emit error(tr("无法创建文件"));
m_socket->abort();
return;
}
emit progress(m_bytesReceived, m_fileSize + m_headerSize);
return;
}
// 接收文件数据
qint64 bytesAvailable = m_socket->bytesAvailable();
qint64 bytesToWrite = qMin(bytesAvailable, m_fileSize - m_file->pos());
QByteArray buffer = m_socket->read(bytesToWrite);
m_file->write(buffer);
m_bytesReceived += bytesToWrite;
emit progress(m_bytesReceived, m_fileSize + m_headerSize);
// 传输完成
if (m_bytesReceived == m_fileSize + m_headerSize) {
m_file->close();
emit finished(m_file->fileName());
}
}
private:
QTcpSocket* m_socket;
QFile* m_file;
qint64 m_fileSize;
qint64 m_bytesReceived;
qint64 m_headerSize;
};
3.2 接收端使用示例
cpp
// 创建TCP服务器
QTcpServer server;
if (!server.listen(QHostAddress::Any, 1234)) {
qDebug() << "服务器启动失败:" << server.errorString();
return;
}
connect(&server, &QTcpServer::newConnection, [&](){
QTcpSocket* socket = server.nextPendingConnection();
FileReceiver* receiver = new FileReceiver(socket);
connect(receiver, &FileReceiver::progress, [](qint64 received, qint64 total){
qDebug() << "接收进度:" << received * 100.0 / total << "%";
});
connect(receiver, &FileReceiver::finished, [](const QString& filePath){
qDebug() << "文件接收完成,保存为:" << filePath;
});
});
4. 高级功能实现
4.1 断点续传实现
cpp
// 发送端修改
void FileSender::resumeSendFile(const QString& filePath, qint64 startPos) {
m_file = new QFile(filePath, this);
if (!m_file->open(QIODevice::ReadOnly)) {
emit error(tr("无法打开文件"));
return;
}
m_fileSize = m_file->size();
m_bytesWritten = startPos;
m_file->seek(startPos);
// 发送续传请求头
QByteArray header;
QDataStream ds(&header, QIODevice::WriteOnly);
ds << qint64(m_fileSize) << qint64(-startPos) << QFileInfo(filePath).fileName();
m_socket->write(header);
}
// 接收端修改
void FileReceiver::onReadyRead() {
// ...原有代码...
// 如果是续传请求(headerSize为负数)
if (m_headerSize < 0) {
qint64 startPos = -m_headerSize;
if (m_file->open(QIODevice::Append)) {
m_file->seek(startPos);
m_bytesReceived = startPos + sizeof(qint64)*2;
}
// ...其余处理...
}
}
4.2 校验机制(MD5校验)
cpp
// 发送端添加校验
void FileSender::sendFileWithChecksum(const QString& filePath) {
// ...原有代码...
// 计算文件MD5
QCryptographicHash hash(QCryptographicHash::Md5);
if (m_file->open(QIODevice::ReadOnly)) {
while (!m_file->atEnd()) {
hash.addData(m_file->read(8192));
}
m_file->close();
}
QByteArray md5 = hash.result().toHex();
// 将MD5包含在文件头中
ds << md5;
}
// 接收端验证
void FileReceiver::verifyChecksum() {
QCryptographicHash hash(QCryptographicHash::Md5);
if (m_file->open(QIODevice::ReadOnly)) {
while (!m_file->atEnd()) {
hash.addData(m_file->read(8192));
}
m_file->close();
}
if (hash.result().toHex() != m_expectedMd5) {
qDebug() << "文件校验失败!";
m_file->remove();
}
}
5. 性能优化建议
-
调整分块大小:根据网络状况动态调整分块大小(如从64KB到1MB)
-
并行传输:将文件分成多个部分并行传输
-
压缩传输:在传输前对数据进行压缩
-
滑动窗口:实现类似TCP的滑动窗口机制提高吞吐量
-
内存映射:对大文件使用内存映射(QFile::map)提高读取效率