本文基于VS2015和Qt5.9实现。在实际的TCP通讯中,并不能像一些网上关于QTcpSocket和QTcpServer使用例程一样,建立连接后有数据就直接调用ReadAll方法读取内容。要考虑很多情况,比如常见的TCP拆包、粘包和丢包。所以要有一些机制来确保发送的包可以被完好的接收到,并且在丢包后可以重发。下表是我们定义一个TCP数据包的格式,我们要发的数据会被打包到这个数据格式里,以确保对方收到后可以完整地把数据解析出来。

2字节头 4字节ID 1字节功能 4字节数据长度 任意字节数据
0xEF 0xFF

从1开始递增。对方收到后会回复一个ID为负发送方ID

的空数据包以帮助发送方确认收到。比如:你发送一个

数据包的ID为2,对方会回复一个ID为-2的空包

0代表发送的是本机名称,该功能是内置功能,

只在建立连接后发送一次,用户无需关注这个

包。1代表的是发送普通数据

代表“你的数据”的长度 你的数据

以下是代码。头文件:

#pragma once

#include "qabstractsocket.h"
#include "qdatetime.h"
#include "qmap.h"

class QTcpSocket;
class QTcpServer;
class QTimer;

class ClientWrapper : public QObject
{
    Q_OBJECT

public:
    ClientWrapper(const QString& name = u8"未命名", QObject* parent = 0);
    void connectToHost(const QString& ip, quint16 port);
    void sendData(const QByteArray& data);
    void disconnect();

signals:
    void connectStatusChanged(bool isConnected);
    void dataReceived(const QByteArray& data);

private slots:
    void socketError();
    void socketConnected();
    void socketReadyRead();
    void timerTimeout();

private:
    struct Message
    {
        int id;
        int sendCount;
        QDateTime lastSendTime;
        QByteArray data;
    };

    struct RecData
    {
        char type;
        int id;
        int dataLength;
        QByteArray data;
    };

    QByteArray constructPackage(const QByteArray& input, int id, char type = 1);
    bool handleReceiveData(RecData& rd);
    void handleResponse(const RecData& rd);
    void checkIfConnectFailed(const QVector<int>& timeouts);
    void sendMyName();
    int nextId();

private:
    const static int retryCount;
    const static int msExpired;
    static int uniqueId;
    QVector<Message> pendings;
    QVector<Message> recIds;
    QString socketName;
    RecData rdata;
    QTcpSocket* client;
    QTimer* timer;
};

class ServerWrapper : public QObject
{
    Q_OBJECT

public:
    ServerWrapper(QObject* parent = 0);
    void sendData(const QByteArray& data);
    void disconnect(const QString& name = QString());

signals:
    void dataReceived(const QString& name, const QByteArray& data);
    void newClientConnected(const QString& name);
    void clientDisconnected(const QString& name);

    private slots:
    void tcpServerNewConnection();
    void clientReadyRead();
    void clientError();
    void timerTimeout();

private:
    struct Message
    {
        int id;
        int sendCount;
        QDateTime lastSendTime;
        QByteArray data;
    };

    struct RecData
    {
        char type;
        int id;
        int dataLength;
        QByteArray data;
    };

    QByteArray constructPackage(const QByteArray& input, int id, char type = 1);
    bool handleReceiveData(QTcpSocket* socket, RecData& rd);
    void handleNewPackage(QTcpSocket* socket, const QByteArray& package, char type);
    void handleResponse(int id);
    int nextId();

private:
    const static int retryCount;
    const static int msExpired;
    static int uniqueId;
    QTcpServer* server;
    QTimer* timer;
    QMultiMap<QString, QTcpSocket*> nameOfClients;
    QMap<QTcpSocket*, RecData> clients;
    QVector<Message> pendings;
    QVector<Message> recIds;
};

CPP文件:

#include "TcpComm.h"
#include "qtcpserver.h"
#include "qtcpsocket.h"
#include "qtimer.h"

const int ClientWrapper::retryCount = 3;
const int ClientWrapper::msExpired = 5000;
int ClientWrapper::uniqueId = 1; /* [1, +无穷大) */

ClientWrapper::ClientWrapper(const QString& name, QObject* parent) : 
    QObject(parent), socketName(name)
{
    rdata.id = 0;
    rdata.dataLength = -1;
    client = new QTcpSocket(this);
    using SocketError = QAbstractSocket::SocketError;
    connect(client, QOverload<SocketError>::of(&QTcpSocket::error), this, &ClientWrapper::socketError);
    connect(client, &QTcpSocket::connected, this, &ClientWrapper::socketConnected);
    connect(client, &QTcpSocket::readyRead, this, &ClientWrapper::socketReadyRead);
    timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, &ClientWrapper::timerTimeout);
    timer->start(50);
}

void ClientWrapper::timerTimeout()
{
    QVector<int> dels;
    QDateTime curr = QDateTime::currentDateTime();
    int rcount = recIds.size();
    for (int i = 0; i < rcount; i++)
    {
        /* 防止收到重复id的包 */
        if (recIds[i].lastSendTime.msecsTo(curr) >= 3 * msExpired)
        {
            dels.push_back(i);
        }
    }
    for (auto i = dels.rbegin(); i != dels.rend(); i++)
    {
        recIds.removeAt(*i);
    }
    dels.clear();
    int pcount = pendings.size();
    for (int i = 0; i < pcount; i++)
    {
        if (pendings[i].lastSendTime.msecsTo(curr) >= msExpired)
        {
            if (pendings[i].sendCount >= retryCount)
            {
                dels.push_back(i);
                continue;
            }
            if (pendings[i].id == 0)
            {
                QList<QByteArray> strs = pendings[i].data.split(':');
                client->connectToHost(strs[0], strs[1].toUShort());
            }
            else
            {
                client->write(pendings[i].data);
            }
            pendings[i].lastSendTime = curr;
            pendings[i].sendCount++;
        }
    }
    checkIfConnectFailed(dels);
    for (auto i = dels.rbegin(); i != dels.rend(); i++)
    {
        pendings.removeAt(*i);
    }
}

void ClientWrapper::checkIfConnectFailed(const QVector<int>& timeouts)
{
    int isFailed = 0;
    for (auto item : timeouts)
    {
        /* 连接服务器失败 */
        if (pendings[item].id == 0)
        {
            isFailed = 1;
            break;
        }
        /* 发送自己名称失败 */
        if (pendings[item].data[6] == '\0')
        {
            isFailed = 2;
            break;
        }
    }
    if (isFailed == 2)
    {
        client->disconnectFromHost();
    }
    if (isFailed != 0)
    {
        emit connectStatusChanged(false);
    }
}

void ClientWrapper::socketError()
{
    auto found = std::find_if(pendings.begin(), pendings.end(),
        [](const Message& msg) { return msg.id == 0; });
    /* 是否处于连接服务器状态,不处于则触发信号 */
    if (found == pendings.end())
    {
        QTimer::singleShot(1, [this]() { emit connectStatusChanged(false); });
        rdata.id = 0;
        rdata.dataLength = -1;
    }
}

void ClientWrapper::socketConnected()
{
    auto found = std::find_if(pendings.begin(), pendings.end(),
        [](const Message& msg) { return msg.id == 0; });
    if (found != pendings.end())
    {
        pendings.erase(found);
    }
    /* 连接服务器后立即发送自己名字,发送成功转换状态为已连接 */
    /* 转换状态信号在@handleResponse函数 */
    sendMyName();
}

void ClientWrapper::socketReadyRead()
{
    rdata.data += client->readAll();
    while (handleReceiveData(rdata)) {}
}

void ClientWrapper::disconnect()
{
    client->disconnectFromHost();
}

void ClientWrapper::connectToHost(const QString& ip, quint16 port)
{
    QString data = QString(u8"%1:%2").arg(ip).arg(port);
    Message msg;
    msg.id = 0;
    msg.sendCount = 1;
    msg.lastSendTime = QDateTime::currentDateTime();
    msg.data = data.toUtf8();
    pendings.push_back(msg);
    client->connectToHost(ip, port);
}

void ClientWrapper::sendData(const QByteArray& data)
{
    int nid = nextId();
    QByteArray package = constructPackage(data, nid);
    Message msg;
    msg.id = nid;
    msg.sendCount = 1;
    msg.lastSendTime = QDateTime::currentDateTime();
    msg.data = package;
    pendings.push_back(msg);
    client->write(package);
}

void ClientWrapper::sendMyName()
{
    int nid = nextId();
    QByteArray package = constructPackage(socketName.toUtf8(), nid, 0);
    Message msg;
    msg.id = nid;
    msg.sendCount = 1;
    msg.lastSendTime = QDateTime::currentDateTime();
    msg.data = package;
    pendings.push_back(msg);
    client->write(package);
}

void ClientWrapper::handleResponse(const RecData& rd)
{
    auto found = std::find_if(pendings.begin(), pendings.end(),
        [rd](const Message& msg) { return msg.id == -rd.id; });
    if (found != pendings.end())
    {
        pendings.erase(found);
    }
    if (rd.type == 0) /*名字发送成功,转换状态*/
    {
        emit connectStatusChanged(true);
    }
}

bool ClientWrapper::handleReceiveData(RecData& rd)
{
    while (rd.data.length() >= 2)
    {
        if (rd.data[0] != '\xef' || rd.data[1] != '\xff')
        {
            rd.data.remove(0, 1);
            continue;
        }
        break;
    }
    if (rd.dataLength < 0 && rd.data.length() >= 11)
    {
        rd.id = 0;
        rd.id |= (rd.data[2] & 0xff) << 24;
        rd.id |= (rd.data[3] & 0xff) << 16;
        rd.id |= (rd.data[4] & 0xff) << 8;
        rd.id |= (rd.data[5] & 0xff);
        rd.type = rd.data[6];
        rd.dataLength = 0;
        rd.dataLength |= (rd.data[7] & 0xff) << 24;
        rd.dataLength |= (rd.data[8] & 0xff) << 16;
        rd.dataLength |= (rd.data[9] & 0xff) << 8;
        rd.dataLength |= (rd.data[10] & 0xff);
    }
    int whole = rd.dataLength + 11;
    if (rd.dataLength >= 0 && rd.data.length() >= whole)
    {
        if (rd.id > 0)
        {
            client->write(constructPackage(QByteArray(), -rd.id, rd.type));
            /* 防止重复收到rd.id的数据 */
            auto found = std::find_if(recIds.begin(), recIds.end(),
                [&rd](const Message& m) { return m.id == rd.id; });
            if (found == recIds.end())
            {
                Message recRecord;
                recRecord.id = rd.id;
                recRecord.lastSendTime = QDateTime::currentDateTime();
                recIds.push_back(recRecord);
                QByteArray onePage = rd.data.mid(11, rd.dataLength);
                emit dataReceived(onePage);
            }
        }
        else
        {
            handleResponse(rd);
        }
        rd.data.remove(0, whole);
        rd.dataLength = -1;
        if (!rd.data.isEmpty())
        {
            return true;
        }
    }
    return false;
}

QByteArray ClientWrapper::constructPackage(const QByteArray& input, int id, char type)
{
    int whole = input.length();
    QByteArray output;
    output += "\xef\xff";
    output += char((id >> 24) & 0xff);
    output += char((id >> 16) & 0xff);
    output += char((id >> 8) & 0xff);
    output += char(id & 0xff);
    output += char(type);
    output += char((whole >> 24) & 0xff);
    output += char((whole >> 16) & 0xff);
    output += char((whole >> 8) & 0xff);
    output += char(whole & 0xff);
    output += input;
    return output;
}

int ClientWrapper::nextId()
{
    int x = uniqueId;
    uniqueId = qMax(uniqueId + 1, 1);
    return x;
}

/////////////////////////////////////////////////////////////////////////////////////////

const int ServerWrapper::retryCount = 3;
const int ServerWrapper::msExpired = 5000;
int ServerWrapper::uniqueId = 1; // [1,+∞)

ServerWrapper::ServerWrapper(QObject* parent) :
    QObject(parent)
{
    server = new QTcpServer(this);
    connect(server, &QTcpServer::newConnection, this, &ServerWrapper::tcpServerNewConnection);
    server->listen(QHostAddress::Any, 8901);
    timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, &ServerWrapper::timerTimeout);
    timer->start(50);
}

void ServerWrapper::timerTimeout()
{
    QVector<int> dels;
    QDateTime curr = QDateTime::currentDateTime();
    int rcount = recIds.size();
    for (int i = 0; i < rcount; i++)
    {
        if (recIds[i].lastSendTime.msecsTo(curr) >= 3 * msExpired) /* 这里是收到数据的时间 */
        {
            dels.push_back(i);
        }
    }
    /* 倒序删除 */
    for (auto i = dels.rbegin(); i != dels.rend(); i++)
    {
        recIds.removeAt(*i);
    }
    dels.clear();
    int pcount = pendings.size();
    for (int i = 0; i < pcount; i++)
    {
        if (pendings[i].lastSendTime.msecsTo(curr) >= msExpired)
        {
            if (pendings[i].sendCount >= retryCount)
            {
                dels.push_back(i);
                continue;
            }
            for (auto them : clients.keys())
            {
                them->write(pendings[i].data);
            }
            pendings[i].lastSendTime = curr;
            pendings[i].sendCount++;
        }
    }
    /*倒序删除*/
    for (auto i = dels.rbegin(); i != dels.rend(); i++)
    {
        pendings.removeAt(*i);
    }
}

void ServerWrapper::tcpServerNewConnection()
{
    QTcpSocket* peer = server->nextPendingConnection();
    using SocketError = QAbstractSocket::SocketError;
    connect(peer, &QTcpSocket::readyRead, this, &ServerWrapper::clientReadyRead);
    connect(peer, QOverload<SocketError>::of(&QTcpSocket::error), this, &ServerWrapper::clientError);
    RecData dataStruct;
    dataStruct.dataLength = -1;
    clients.insert(peer, dataStruct);
}

void ServerWrapper::disconnect(const QString& name)
{
    QList<QTcpSocket*> toDels =
        name.isEmpty() ? nameOfClients.values() : nameOfClients.values(name);
    for (auto item : toDels)
    {
        item->disconnectFromHost();
        clients.remove(item);
        delete item;
    }
    nameOfClients.remove(name);
}

void ServerWrapper::sendData(const QByteArray& data)
{
    int nid = nextId();
    QByteArray package = constructPackage(data, nid);
    Message msg;
    msg.id = nid;
    msg.sendCount = 1;
    msg.lastSendTime = QDateTime::currentDateTime();
    msg.data = package;
    pendings.push_back(msg);
    for (auto them : clients.keys())
    {
        them->write(package);
    }
}

void ServerWrapper::clientReadyRead()
{
    QTcpSocket* src = dynamic_cast<QTcpSocket*>(sender());
    if (!clients.contains(src))
    {
        qDebug() << u8"错误的代码";
        return;
    }
    RecData& rd = clients[src];
    rd.data += src->readAll();
    while (handleReceiveData(src, rd)) {}
}

void ServerWrapper::clientError()
{
    QTcpSocket* src = dynamic_cast<QTcpSocket*>(sender());
    QString name = nameOfClients.key(src);
    nameOfClients.remove(name, src);
    clients.remove(src);
    emit clientDisconnected(name);
}

void ServerWrapper::handleResponse(int id)
{
    auto found = std::find_if(pendings.begin(), pendings.end(),
        [id](const Message& msg) { return msg.id == -id; });
    if (found != pendings.end())
    {
        pendings.erase(found);
    }
}

void ServerWrapper::handleNewPackage(QTcpSocket* socket, const QByteArray& package, char type)
{
    if (type == 0) /* 0为发送客户端名称的包 */
    {
        nameOfClients.insert(package, socket);
        emit newClientConnected(package);
    }
    else
    {
        QString name = nameOfClients.key(socket);
        emit dataReceived(name, package);
    }
}

bool ServerWrapper::handleReceiveData(QTcpSocket* socket, RecData& rd)
{
    while (rd.data.length() >= 2)
    {
        if (rd.data[0] != '\xef' || rd.data[1] != '\xff')
        {
            rd.data.remove(0, 1);
            continue;
        }
        break;
    }
    if (rd.dataLength < 0 && rd.data.length() >= 11)
    {
        rd.id = 0;
        rd.id |= (rd.data[2] & 0xff) << 24;
        rd.id |= (rd.data[3] & 0xff) << 16;
        rd.id |= (rd.data[4] & 0xff) << 8;
        rd.id |= (rd.data[5] & 0xff);
        rd.type = rd.data[6];
        rd.dataLength = 0;
        rd.dataLength |= (rd.data[7] & 0xff) << 24;
        rd.dataLength |= (rd.data[8] & 0xff) << 16;
        rd.dataLength |= (rd.data[9] & 0xff) << 8;
        rd.dataLength |= (rd.data[10] & 0xff);
    }
    int whole = rd.dataLength + 11;
    if (rd.dataLength >= 0 && rd.data.length() >= whole)
    {
        if (rd.id > 0)
        {
            socket->write(constructPackage(QByteArray(), -rd.id, rd.type));
            /* 防止重复收到rd.id的数据 */
            auto found = std::find_if(recIds.begin(), recIds.end(),
                [&rd](const Message& m) { return m.id == rd.id; });
            if (found == recIds.end())
            {
                Message recRecord;
                recRecord.id = rd.id;
                recRecord.lastSendTime = QDateTime::currentDateTime();
                recIds.push_back(recRecord);
                QByteArray onePage = rd.data.mid(11, rd.dataLength);
                handleNewPackage(socket, onePage, rd.type);
            }
        }
        else
        {
            handleResponse(rd.id);
        }
        rd.data.remove(0, whole);
        rd.dataLength = -1;
        if (!rd.data.isEmpty())
        {
            return true;
        }
    }
    return false;
}

QByteArray ServerWrapper::constructPackage(const QByteArray& input, int id, char type)
{
    int whole = input.length();
    QByteArray output;
    output += "\xef\xff";
    output += char((id >> 24) & 0xff);
    output += char((id >> 16) & 0xff);
    output += char((id >> 8) & 0xff);
    output += char(id & 0xff);
    output += char(type);
    output += char((whole >> 24) & 0xff);
    output += char((whole >> 16) & 0xff);
    output += char((whole >> 8) & 0xff);
    output += char(whole & 0xff);
    output += input;
    return output;
}

int ServerWrapper::nextId()
{
    int x = uniqueId;
    uniqueId = qMax(uniqueId + 1, 1);
    return x;
}

客户端在建立TCP连接之后发送自己的名称的好处是可以通过名称控制客户端。这比通过IP地址识别客户端要好得多,因为IP可以改变。ClientWrapper类是客户端;ServerWrapper类是服务器。他们公开的成员和信号并不多很容易使用。在客户端和服务器内部都实现了对数据的超时自动重发功能,直到收到对方回复的确认包,但最多重发3次。为了防止自动重发导致的同一个包被多次收到,他们还会记录一定时间内收到的所有数据包的ID,如果短时间内收到多次,则只触发一次dataReceived信号。

这两个类的使用方式简单。只需要创建对象,然后连接对应的信号槽。发送数据只需在点击按钮的槽函数中调用sendData函数就行。我测试是在同一个进程测试的,但完全可以把客户端和服务器放到不同的进程中。下面是我写的测试Demo的截图,客户端的名字是“客户端的名字”:

QT modem通讯 qt tcp通信代码_其他