Qt CS架构 客户端代码编写技巧 QTcpSocket

  • QT网络编程解说
  • QTcpSocket网络编程细节

这里是更新版本的 Qt 网络编程解说

QT网络编程解说

很多人在编写网络代码的时候,客户端代码编写的功能总不能胜任所需要的功能能力,现在我将编写网络代码所需要遵循的规范输出出来,帮助别的人梳理对网络的认识。

连接网络和服务器通信的过程:连接,断开属于开关的时间。中途所有的数据消息都遵循如下过程:
打包数据,
打包消息,
发送消息,
接收消息,
解包消息,
解包数据。
应用对数据进行发送前和接收后的处理。

QTcpSocket网络编程细节

qteclientmessage负责将服务器协议上的数据进行解包和打包;qteclientdata负责将协议内的数据段进行打包和解包;qteclient负责网络的连接和断开和封装网络传动功能接口;

这个例程用心跳来说明了网络传输的过程,例程的终点在reayReadData,在这个函数里,处理了粘包,数据不足的情况等

qteclientmessage.h

#ifndef QTEMESSAGE_H
#define QTEMESSAGE_H

#include "QTEDefine.h"

#define _TCPCMD_TAGHEAD                   0xEECC
#define _TCPCMD_TAGTAIL                   0xCCEE

#define _TCPCMD_HEART                     0x0000
#define _TCPCMD_HEART_RSP                     0x8000

#define _TCP_BLOCKDATA_SIZE                    0x400
#define _TCP_RECVBUFF_SIZE                      0x800


typedef struct tagHeartBeat
{
    quint8 m_tipJ;
    QString m_content;
}QTEHeartBeat;

typedef struct tagHeartBeatRsp
{
    quint16 m_tipJJ;
    QString m_content;
}QTEHeartBeatRsp;

class QTEClientMessage : public QObject
{
    Q_OBJECT
public:
    explicit QTEClientMessage(QObject *parent = 0);

    const quint16& head() const { return m_Head; }
    void setHead(quint16 head) { m_Head = head; }
    const quint16& size() const { return m_Size; }
    void setSize(quint16 size) { m_Size = size; }
    const quint16& cmd() const { return m_Cmd; }
    void setCmd(quint16 cmd) { m_Cmd = cmd; }
    const QByteArray& data() const { return m_Data; }
    void setData(QByteArray& data) { m_Data = data; }
    const quint16& sum() const { return m_Sum; }
    void setSum(quint16 sum) { m_Sum = sum; }
    const quint32& tail() const { return m_Tail; }
    void setTail(quint32 tail) { m_Tail = tail; }
    void translate();

signals:

public slots:

private:
    quint16 m_Head;
    quint16 m_Size;
    quint16 m_Cmd;
    QByteArray m_Data;
    quint16 m_Sum;
    quint32 m_Tail;
};

QDebug operator<< (QDebug dbg, const QTEClientMessage &c);

class QTEClientParser : public QObject
{
public:
    explicit QTEClientParser(QObject *parent = 0) : QObject(parent) {}

    static quint16 parseBlockSize(const QByteArray &netData);
    static void parse(QTEClientMessage& getter, const QByteArray &netData);
    static void pack(QByteArray& netData, const QTEClientMessage& setter);

private:
};

class QTEClientData : public QObject
{
    Q_OBJECT
public:
    explicit QTEClientData(QObject *parent = 0) : QObject(parent) {}

    template 
    static void pack(QByteArray& l, quint16 cmd, const T& t)
    {
        switch(cmd)
        {
        case _TCPCMD_HEART:
            packHeartBeatData(l, (QTEHeartBeat&)t);
            break;
        default:
            pline() << "pack unknown data" << hex << cmd;
            break;
        }
    }

    template 
    static void parse(T& t, quint16 cmd, const QByteArray& l)
    {
        switch(cmd)
        {
        case _TCPCMD_HEART_RSP:
            parseHeartBeatRspData((QTEHeartBeatRsp&)t, l);
            break;
        default:
            pline() << "parse unknown data" << hex << cmd;
            break;
        }
    }

    static void packHeartBeatData(QByteArray& l, const QTEHeartBeat& t);
    static void parseHeartBeatRspData(QTEHeartBeatRsp& t, const QByteArray& l);


signals:
public slots:
protected:
private:
};

#endif // QTEMESSAGE_H

qteclientmessage.cpp

#include "qteclientmessage.h"
#include "QTEDefine.h"
#include 

QTEClientMessage::QTEClientMessage(QObject *parent) :
    QObject(parent)
{
    m_Head = _TCPCMD_TAGHEAD;
    m_Size = m_Cmd = m_Sum = 0;
    m_Data.clear();;
    m_Tail = _TCPCMD_TAGTAIL;
}

void QTEClientMessage::translate()
{
    m_Size = m_Data.length() + 0x10;
    QByteArray qbaVerify;
    qbaVerify << m_Size << m_Cmd << m_Data;
    m_Sum = 0;
    // 校验码等错误 会导致服务器不回复消息
    // 如果不添加quint8 0x0112+0x0088=0x009a 单字节到二字节进位的位置看不懂
    for(int i = 0; i < qbaVerify.length(); i++)
        m_Sum += quint8(qbaVerify.at(i));
    //real verify
    //m_Sum = qChecksum(qbaVerify.data(), qbaVerify.length());
}

QDebug operator<<(QDebug dbg, const QTEClientMessage &c)
{
    dbg.nospace() << hex << c.head() << "|" <<
                     hex << c.size() << "|" <<
                     hex << c.cmd() << "|" <<
                     dec << c.data().size() << "|" <<
                     hex << c.sum() << "|" <<
                     hex << c.tail();
    return dbg.space();
}

quint16 QTEClientParser::parseBlockSize(const QByteArray &netData)
{
    QByteArray l = netData.left(4);
    quint16 b0 = 0, b1 = 0;
    l >> b0 >> b1;
    return b1;
}

void QTEClientParser::parse(QTEClientMessage &getter, const QByteArray &netData)
{
    QByteArray l = netData;
    quint16 b0 = 0, b1 = 0, b2 = 0, b5 = 0;
    quint32 b6 = 0;
    QByteArray b4;
    l >> b0 >> b1 >> b2;
    b4.resize(b1-0x10);
    l >> b4 >> b5 >> b6;
    getter.setHead(b0);
    getter.setSize(b1);
    getter.setCmd(b2);
    getter.setData(b4);
    getter.setSum(b5);
    getter.setTail(b6);
}

void QTEClientParser::pack(QByteArray &netData, const QTEClientMessage &setter)
{
    netData << setter.head();
    netData << setter.size();
    netData << setter.cmd();
    netData << setter.data();
    netData << setter.sum();
    netData << setter.tail();
}

void QTEClientData::packHeartBeatData(QByteArray &l, const QTEHeartBeat &t)
{
    l << t.m_tipJ;
    l += t.m_content.toAscii();
}

void QTEClientData::parseHeartBeatRspData(QTEHeartBeatRsp &t, const QByteArray &l)
{
    QByteArray _l = l;
    _l >> t.m_tipJJ;


    QVariant v(_l);
    QString str = v.toString();

    t.m_content = str;

    pline() << t.m_tipJJ << t.m_content;

}

qteclient.h

/**************************************************
 **************************************************/
#ifndef QTECLIENT_H
#define QTECLIENT_H

#include 
#include 
#include "qteclientmessage.h"
#include 
#include 
#include "QTEDefine.h"

#define QTE_Q_TCP_SOCKET 0
#define QTE_Q_SOCKET 1
#define QTE_Q_THREAD 0

class QTEClient : public QTcpSocket
{
    Q_OBJECT
public:
    explicit QTEClient(QObject *parent = 0);
    virtual ~QTEClient();

    void setServHostPort(QString ip, quint32 p);
    void SendConnectMessage();
    void SendDisConnectFromHost();

signals:
    void signalConnectSucc();
    void signalConnectFail();//

public slots:
    //服务器需要解析收到的命令,而此处不需要,所以客户端和服务器代码分开编写。
    void sendHeatBeatMessage();

private:
    qint8 m_heartCount;
    QString m_host;
    quint32 m_PORT;
    QTimer* timer;
    //TODO:public for debug
    template <typename T>
    void sendMessage(quint16 cmd, const T &t)
    {
        QTEClientMessage qMsg;
        qMsg.setCmd(cmd);
        QByteArray d;
        QTEClientData::pack(d, cmd, t);
        qMsg.setData(d);
        qMsg.translate();
        pline() << qMsg;
        QByteArray b;
        QTEClientParser::pack(b, qMsg);
        write(b);
    }
private slots:
    void domainHostFound();
    void socketStateChanged(QAbstractSocket::SocketState);
    void socketErrorOccured(QAbstractSocket::SocketError);
    void socketConnected();
    void socketDisconnect();
    void updateProgress(qint64);
private slots:
    void readyReadData();
    void dispatchRecvedMessage(QByteArray& blockOnNet);
    void recvHeatBeatResultMessage(QTEClientMessage&);
};


#endif // QTECLIENT_H

qteclient.cpp

#include "qteclient.h"
#include "QTEDefine.h"
#include "qteclientmessage.h"

#define MAX_HEARDBEAT 10

QTEClient::QTEClient(QObject *parent) :
    QTcpSocket(parent)
{
    connect(this, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
            this, SLOT(socketStateChanged(QAbstractSocket::SocketState)) );
    // connected
    connect(this, SIGNAL(connected()), this, SLOT(socketConnected()) );
    // disconnected
    connect(this, SIGNAL(disconnected()), this, SLOT(socketDisconnect()) );
    // domain
    connect(this, SIGNAL(hostFound()), this, SLOT(domainHostFound()));
    // error
    connect(this, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketErrorOccured(QAbstractSocket::SocketError)) );

    connect(this, SIGNAL(readyRead()), this, SLOT(readyReadData()));

    connect(this, SIGNAL(bytesWritten(qint64)), this, SLOT(updateProgress(qint64)));





    setSocketOption(QAbstractSocket::LowDelayOption, 0);
    setSocketOption(QAbstractSocket::KeepAliveOption, 0);
    setReadBufferSize(_TCP_RECVBUFF_SIZE);

    m_heartCount = 0;

    m_PORT = 0;

    timer = new QTimer(this);
    timer->setSingleShot(false);
    connect(timer, SIGNAL(timeout()), this, SLOT(sendHeatBeatMessage()));
}

QTEClient::~QTEClient()
{
}

void QTEClient::setServHostPort(QString ip, quint32 p)
{
    m_host = ip;
    m_PORT = p;
}

void QTEClient::SendConnectMessage()
{
    pline() << isValid() << isOpen();
    if(isValid())
        return;

    connectToHost(QHostAddress(m_host), m_PORT);
}


void QTEClient::SendDisConnectFromHost()
{
    if(!isValid())
        return;


    disconnectFromHost();
    close();
}

void QTEClient::domainHostFound()
{
    pline();
}



void QTEClient::socketStateChanged(QAbstractSocket::SocketState eSocketState)
{
    pline() << eSocketState;
    switch(eSocketState)
    {
    case QAbstractSocket::HostLookupState:
    case QAbstractSocket::ConnectingState:
        break;
    case QAbstractSocket::ConnectedState:
        break;
    case QAbstractSocket::ClosingState:
        break;
    case QAbstractSocket::UnconnectedState:

        break;
    default:
        break;
    }
}


void QTEClient::socketErrorOccured(QAbstractSocket::SocketError e)
{
    //在错误状态下重新连接其他热点,直到确定连接类型,写入配置文件
    pline() << e;
    switch(e)
    {
    case QAbstractSocket::RemoteHostClosedError:
        break;
    case QAbstractSocket::HostNotFoundError:
        emit signalConnectFail();
        break;
    default:
        break;
    }
}


void QTEClient::socketConnected()
{
    pline() << peerName() << peerAddress().toString() << peerPort();
    //这个步骤,socket重建,资源重新开始
    m_heartCount = 0;    
    //TODO:心跳检测重连会不会引发这条消息?
    //如果连接还未成功开始发送心跳包,
    //QNativeSocketEngine::write() was not called in QAbstractSocket::ConnectedState
    timer->start(30 * 1000);
    emit signalConnectSucc();

}

/**
 * @brief HNClient::socketDisconnect
 * 功能接口
 */
void QTEClient::socketDisconnect()
{
    pline();
    m_heartCount = MAX_HEARDBEAT + 1;
    timer->stop();
}

void QTEClient::updateProgress(qint64 bytes)
{
    //pline() << bytes;
}



void QTEClient::sendHeatBeatMessage()
{
    //断链判断 如果断链 TODO:
    if(m_heartCount > MAX_HEARDBEAT)
    {
#if 1
        //重连策略 30 * 2 s
        static int curp = 0;
        if(curp >= 2)
        {
            curp = 0;
            connectToHost(QHostAddress(m_host), m_PORT);
            return;
        }
        curp++;
#else
        //此处设置重连策略 30s 150s 300s 600s
        static int p[4] = {1, 5, 10, 20};
        static int curp = 0;
        static int curpos = 0;
        if(curp >= p[curpos])
        {
            curp = 0;
            curpos = (curpos + 1) % 4;
            connectToHost(QHostAddress(m_host), m_PORT);
            return;
        }
        curp++;
#endif
        return;
    }
    pline() << "HeartBeat Count:" << m_heartCount;
    m_heartCount++;
#if 1
    QTEHeartBeat t;
    t.m_content = "this is a heartbeat";
    quint16 _tcpcmd = _TCPCMD_HEART;

    QByteArray d;
    QTEClientData::pack(d, _tcpcmd, t);

    QTEClientMessage qMsg;
    qMsg.setCmd(_TCPCMD_HEART);
    qMsg.setData(d);
    qMsg.translate();
    pline() << qMsg;

    QByteArray b;
    QTEClientParser::pack(b, qMsg);
    write(b);
#else
    QTEHeartBeat t;
    t.m_content = "this is a heartbeat";
    quint16 _tcpcmd = _TCPCMD_HEART;
    sendMessage(_tcpcmd, t);
#endif
}

void QTEClient::readyReadData()
{
    static QByteArray m_blockOnNet;
    m_blockOnNet += readAll();
    //TODO:假设具备判断已经接受完全的装备
    do{
        quint16 nBlockLen = QTEClientParser::parseBlockSize(m_blockOnNet);

        pline() << m_blockOnNet.size() << "..." << nBlockLen;

        if(m_blockOnNet.length() < nBlockLen)
        {
            return;
        }
        else if(m_blockOnNet.length() > nBlockLen)
        {
            //还没有处理完毕,数据已经接收到,异步信号处理出现这种异常
            pline() << "Stick package" << m_blockOnNet.length() << nBlockLen;
            QByteArray netData;
            netData.resize(nBlockLen);
            m_blockOnNet >> netData;
            //TODO:如果异步调用这个函数绘出现什么问题?正常情况,同步获取数据,异步处理;检测异步获取并且处理会有什么状况
            dispatchRecvedMessage(netData);
            continue;
        }
        dispatchRecvedMessage(m_blockOnNet);
        break;
    }while(1);

    m_blockOnNet.clear();
}

void QTEClient::dispatchRecvedMessage(QByteArray &blockOnNet)
{
    QTEClientMessage qMsg;
    QTEClientParser::parse(qMsg, blockOnNet);
    pline() << qMsg;
    switch(qMsg.cmd())
    {
    case _TCPCMD_HEART_RSP:
        recvHeatBeatResultMessage(qMsg);
        break;
    default:
        pline() << "receive unknown command:" << hex << qMsg.cmd();
        break;
    }
}

void QTEClient::recvHeatBeatResultMessage(QTEClientMessage &qMsg)
{
    QTEHeartBeatRsp rsp;
    QTEClientData::parse(rsp, qMsg.cmd(), qMsg.data());
    m_heartCount = 0;
    pline() << rsp.m_content;
}

main.cpp

#include 
#include "qteclient.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QTEClient* client = new QTEClient(&a);
    client->SendConnectMessage();
    //连接成功后,会自动进行心跳来回
    //当然服务器端需要允许任何语言编写的心跳代码支援

    return a.exec();
}

你可能感兴趣的:(Qt)