QT TCP分包在项目中的运用

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;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值