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

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

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 <typename T>
    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 <typename T>
    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 <QBuffer>

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 <QTcpSocket>
#include <QHostInfo>
#include "qteclientmessage.h"
#include <QTimer>
#include <QThread>
#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 <QCoreApplication>
#include "qteclient.h"

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

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

    return a.exec();
}
时间: 2024-11-10 01:02:20

Qt CS架构 客户端代码编写技巧 QTcpSocket的相关文章

14条最佳JS代码编写技巧

http://gaohaixian.blog.163.com/blog/static/123260105201142645458315/写任何编程代码,不同的开发者都会有不同的见解.但参考一下总是好的,下面是来自Javascript Toolbox发布的14条最佳JS代码编写技巧,Sofish翻译(1,2). 1. 总是使用 ‘var’ 在javascript中,变量不是全局范围的就是函数范围的,使用”var”关键词将是保持变量简洁明了的关键.当声明一个或者是全局或者是函数级(function-

高效的jQuery代码编写技巧大盘点

jQuery在Web前端开发中至关重要,好的jQuery代码会带来速度的提升,快速渲染和响应意味着更好的用户体验. 开发者在脑子的意识:jQuery就是javascript.这意味着我们应该采取相同的编码惯例,风格指南和最佳实践. 当你准备使用jQuery,我强烈建议你遵循下面这些指南: 缓存变量 DOM遍历是昂贵的,所以尽量将会重用的元素缓存. // 糟糕 h = $('#element').height();$('#element').css('height',h-20); // 建议 $e

高效的jQuery代码编写技巧总结

缓存变量 DOM遍历是昂贵的,所以尽量将会重用的元素缓存. // 糟糕 h = $('#element').height(); $('#element').css('height',h-20); // 建议 $element = $('#element'); h = $element.height(); $element.css('height',h-20); 避免全局变量 jQuery与javascript一样,一般来说,最好确保你的变量在函数作用域内. // 糟糕 $element = $(

T语言代码编写技巧

控件事件 控件 控件是对数据和方法的封装.控件可以有自己的属性和方法.属性是控件数据的简单访问者.方法则是控件 的一些简单而可见的功能. 概述 1.控件应用 使用现成的控件来开发应用程序时,控件工作在两种模式下:设计时态和运行时态. 在设计时态下,控件显示在开发环境下的一个窗体中.设计时态下控件的方法不能被调用,控件不能与最终用户直接进行交互操作,也不需要实现控件的全部功能. 在 运行状态下,控件工作在一个确实已经运行的应用程序中.控件必须正确地将自身表示出来,它需要对方法的调用进行处理并实现与

css 代码编写技巧

1.  CSS中,子元素自动继承父元素的属性值,象颜色. 字体等,已经在父元素中定义过的,在子元素中可以直接继承,不需要重复定义. 2.  一个标签可以同时定义多个class.例如:我们先定义两个样式, 第一个样式背景为#666:第二个样式有10px的边框. 3.  当你写给一个元素定义class或者id,你可以省略前面的元素限定, 因为ID在一个页面里是唯一的,而class可以在页面中多次使用

.NET应用架构设计—表模块模式与事务脚本模式的代码编写

阅读目录: 1.背景介绍 2.简单介绍表模块模式.事务脚本模式 3.正确的编写表模块模式.事务脚本模式的代码 4.总结 1.背景介绍 要想正确的设计系统架构就必须能正确的搞懂每个架构模式的用意,而不是胡子眉毛一把抓.现在有一个现象是什么呢,项目的结构从表面上看是很不错,层分的很合理,其实对业务系统来说也就那么几种层设计方法,但是现在很多项目的逻辑架构的设计不是理想,有很多概念大家并不是很了解,当然也许每个人对技术的追求不同罢了.不管你追求不追求,事实我们还是要去往正确的方向努力才对的. 很多人包

七牛云存储android客户端及java服务端代码编写

前一篇博客提到让我很伤心的c应用,由于是一款供用户上传图片的应用,因此必须解决图片存储问题,如果直接将图片存储至服务器,当用户上传图片较多,服务器空间将很快吃紧,同时也没有那么大的带宽,现实中我买的阿里云服务器是最低配置,数据盘才20G,带宽才1M,如果用这样配置的服务器做图片存储,那实在太扯了.于是很自然的想到用图片云存储服务器,通过不断查找资料,最后将目标定位在七牛云和又拍云.在做选择时,主要对比了两者之间的价格及技术优势,也看了很多相关话题讨论,个人认为这两者无论从技术方案还是产品价格,都

Qt入门学习——Qt快速入门(vim纯代码编写)

写代码前,先需搭建环境,详情请看:<Qt 5.4.2 ubuntu环境搭建>. 一个简单空白窗口 打开终端,通过vim first_qt.cpp新建文件,由于Qt代码为C++代码,所以,新建文件的后缀为.cpp. 代码内容如下: #include <QApplication> #include <QWidget> int main(int argc, char *argv[]) { QApplication app(argc, argv); //初始化 QWidget

爱创课堂推荐6个编写优质干净代码的技巧,开发者必看!

划重点 作为一名开发者,编写一手干净的代码很重要,所以在本文中作者先列举出编写干净代码的一些好处,再提出6个技巧用于编写干净代码,供开发者进行参考学习. 编写干净的代码并不是一件容易的事情,这需要尝试不同的技巧和实践.问题是,在这个问题上有太多的实践和技巧,因此开发人员很难进行选择,所以要把这个问题简化一下.在本文中,将首先讨论编写干净代码的一些好处,然后将讨论6个技巧或者实践,用于编写最常用的干净代码. 以下是本文重点: 编写干净代码的好处 1. 更容易开始和继续一个项目 2.有利于团队新员工