这里是更新版本的 Qt 网络编程解说
很多人在编写网络代码的时候,客户端代码编写的功能总不能胜任所需要的功能能力,现在我将编写网络代码所需要遵循的规范输出出来,帮助别的人梳理对网络的认识。
连接网络和服务器通信的过程:连接,断开属于开关的时间。中途所有的数据消息都遵循如下过程:
打包数据,
打包消息,
发送消息,
接收消息,
解包消息,
解包数据。
应用对数据进行发送前和接收后的处理。
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();
}