QT 网络TCP编程入门

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();
}

注意事项

  1. 内存管理 :使用nextPendingConnection()返回的QTcpSocket对象需要手动管理生命周期

  2. 线程安全:QTcpServer通常在主线程中使用,跨线程使用需要小心

  3. 错误处理:始终检查listen()的返回值和监听错误信号

  4. 性能考虑:对于高并发场景,考虑使用线程池处理连接

  5. 平台差异:某些属性(如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();
}

注意事项

  1. 线程安全:QTcpSocket实例通常应在创建它的线程中使用

  2. 内存管理:确保正确管理QTcpSocket的生命周期,特别是在多线程环境中

  3. 错误处理:始终检查操作返回值并处理错误信号

  4. 数据边界:TCP是流协议,需要自己处理消息边界

  5. 性能优化:大数据传输时考虑分块处理和使用缓冲区

  6. 超时处理:重要操作应设置合理的超时时间

服务器端实现

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;
};

注意事项

  1. 线程安全:网络操作通常在单独的线程中进行,避免阻塞主线程

  2. 错误处理:始终检查连接状态和处理错误

  3. 数据格式:定义应用层协议来处理消息边界(如添加消息长度前缀)

  4. 资源管理:及时释放不再使用的socket连接

  5. 跨平台兼容: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;

注意事项

  1. 版本兼容性 :始终设置相同的QDataStream版本(setVersion())

  2. 字节序 :QDataStream默认使用大端序,可通过setByteOrder()更改

  3. 数据类型匹配:读取时的数据类型必须与写入时完全一致

  4. 错误处理 :检查流状态(status())处理可能的错误

  5. 性能考虑:对于大量数据,考虑使用压缩或分块传输

实现TCP心跳机制保持长连接

1. 心跳机制基本原理

心跳机制是通过定期发送小数据包来:

  1. 检测连接是否存活

  2. 防止中间设备(如NAT路由器)断开空闲连接

  3. 维持长连接状态

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. 注意事项

  1. 心跳间隔选择:通常30秒-5分钟,太短会增加负担,太长可能被NAT断开

  2. 超时时间设置:一般为心跳间隔的2-3倍

  3. 资源消耗:大量连接时考虑优化定时器实现

  4. 安全性:心跳包可以加入简单加密或签名防止伪造

  5. 移动网络适配:考虑移动网络下更频繁的心跳(如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. 性能优化建议

  1. 调整分块大小:根据网络状况动态调整分块大小(如从64KB到1MB)

  2. 并行传输:将文件分成多个部分并行传输

  3. 压缩传输:在传输前对数据进行压缩

  4. 滑动窗口:实现类似TCP的滑动窗口机制提高吞吐量

  5. 内存映射:对大文件使用内存映射(QFile::map)提高读取效率