admin管理员组文章数量:1794759
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 <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_Hqteclientmessage.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_Hqteclient.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(); }版权声明:本文标题:Qt CS架构 客户端代码编写技巧 QTcpSocket 内容由林淑君副主任自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.xiehuijuan.com/baike/1686867035a112328.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论