1.项目背景
在项目实践中,在进程之间通讯时最开始采用共享内存的方式进行通讯,但是在运行过程中由于C++内存管理不是很好导致有的进程死掉之后重新启动共享内存无效;也有在Linux系统中使用共享内存时,如果多次重启系统发现共享内存失效的情况。在这种情况下就将原有的内存共享修改为了TCP通讯,但是在TCP通讯时发现有数据量大了之后粘包、丢包的问题,因此就引入了TCP拆包、分包的概念。
2.发送数据包
在服务端发送消息时,对数据进行对应数据的拆包发送,并且两端协议严格一致。保证TCP发送数据包的合法性。
int remainCnt = arr.length();
int sendCnt = int(arr.length()/double(maxPackageLen) + 1);
int reqId = Tools::getIncId();
QByteArray byteReqId = Tools::Int2TwoBytes(reqId);//包ID
for(int i=0;i< sendCnt;i++){
int curSendLen = 0;
if(remainCnt > maxPackageLen){
curSendLen = maxPackageLen;
}else{
curSendLen = remainCnt;
}
int startIndex = arr.length() - remainCnt;
QByteArray sendData = arr.mid(startIndex,curSendLen);
remainCnt -= curSendLen;
QByteArray sData;
sData.clear();
sData.append(char(0xAA));
sData.append(char(0xAA));
sData.append(char(0xCC));
QByteArray byteLen = Tools::Int2FourBytes((uint)arr.length());
sData.append(byteLen);
QByteArray byteTotalPk = Tools::Int2TwoBytes(sendCnt);
sData.append(byteTotalPk);
sData.append(byteReqId);
int ipksid = (i%65535) + 1;
QByteArray bytePKSID = Tools::Int2TwoBytes(ipksid);
sData.append(bytePKSID);
QByteArray byteSingleLen = Tools::Int2TwoBytes((uint)curSendLen);
sData.append(byteSingleLen);//单包数据长度2字节
char bcc = Tools::BBCCheck(sendData)&0xFF;
sData.append(bcc);//校验码1字节
sData.append(sendData);
if(m_tcpServer->state() != QAbstractSocket::ConnectedState){
writeLog("server连接断开,无法发送数据");
return false;
}
m_tcpServer->write(sData);
m_tcpServer->flush();
}
3.数据接收端
在数据接收端,通过对数据包进按照协议进行解析。在解析第一个包时能够知道当前是否为完整的包,如果拆包发送,则循环接受直至接收完所有的数据包。
int prefixLen = TcpDataComm::PrefixLen;//16
int available = m_tcpServer->bytesAvailable();
if(available< prefixLen){//数据包长度不足
return;
}
QList<TcpDataPackage> pklist;
pklist.clear();
QString errtip = "";
QByteArray byteData = m_tcpServer->readAll();//读16字节数据及之后数据
m_allByte.append(byteData);
//拆分小包 AAAACC
int i = 0;
while (true) {
int stIndex = TcpDataComm::getPackageStartIndex(m_allByte);
if(stIndex <0){//无合法数据
m_allByte.clear();
break;
}
if(stIndex> 0) m_allByte.remove(0,stIndex);//去除前面多余部分
TcpDataPackage pk;
if(m_allByte.length() < prefixLen){//长度不足
break;
}
QByteArray dataHead = m_allByte.mid(0,prefixLen);
if(m_allByte.mid(0,3).toHex().toUpper() != "AAAACC"){
m_allByte.clear();
writeLog("数据非法协议头:" + QString(dataHead.toHex().toUpper()));
break;
}
bool b = pk.analysisHead(dataHead,errtip);
if(!b){
m_allByte.clear();
writeLog("Tcpserver Head<"+ QString::number(i+1) + "> Failed:" + errtip);
break;
}
if(m_allByte.length() < prefixLen + pk.packageLen){//part还有未读完数据
break;
}else{
QByteArray dataPart = m_allByte.mid(prefixLen,pk.packageLen);
m_allByte.remove(0,prefixLen + pk.packageLen);//删除前段包内容
writeLog("读HeadAll<"+ QString::number(i+1) + ">:");
if(!pk.analysisBody(dataPart,errtip)){
writeLog("HeadAll Body<"+ QString::number(i+1) + "> Failed" + errtip);
i++;
continue;
}
pklist.append(pk);
i++;
}
}
//一个包 datapk
if(pklist.count() <=0){
return;
}
for(int i=0;i<pklist.count();i++){
TcpDataPackage datapk = pklist[i];
//判断是否分包
if(datapk.curPackage == 0){//不分包
byteContentData = datapk.data;
QString result = QString::number(qint64(m_socketDescriptor))+m_remoteIpLastAddress;
emit signal_recvBuffer(int(result.toUInt()),byteContentData);
}else{
//入包,判断包是否完整
bool b = m_multiByte.addPackage(datapk,errtip);
if(!b){
m_multiByte.clear();
writeLog("Tcpserver 多包解析异常:" + errtip);
continue;
}
b = m_multiByte.getAllData(byteContentData,errtip);
if(!b && errtip.length() == 0){
continue;//包未完成接收
}
if(!b && errtip.length()>0){
m_multiByte.clear();
writeLog("Tcpserver 多包数据获取异常:" + errtip);
continue;
}
m_multiByte.clear();//获取成功
writeLog(byteContentData.toUpper());
}
}
return;