Socket TCP协议 实时通信的粘包处理之Java与C++实现

原理:

详细内容请阅读 http://blog.csdn.net/zhangxinrun/article/details/6721495

场景:

此项目是处理实时监测数据,一旦tcp socket建立连接,会不间断实时发送数据,峰值输数据量在3M/秒,这样的数据量必然会造成数据粘包。

目的:

TCP连接面向流,读取网络的一包数据不一定正好是协议里定义的完整的一包,有可能是多包,有可能是半包,也有可能是一包半,现在要将每次读取的数据进行分包,也就是粘包处理,提取出完整的一包数据供上层使用,上层需要将完整的一包数据里的数据根据协议定义的格式提取出来。

实现:

将收到的数据copy到缓存区,在缓存区里循环从起始位按照协议找出完整的一包数据提取出来。 关键点在于根据协议找出完整一包数据的长度

  1. 从网络读取数据后拷贝到缓存区
  2. 判断:缓存区里数据占位,长度小于某个值n,return再次读取网络数据。这个值n长度的数据内要能解析出单个完整包的长度,以便后续处理
  3. 循环:如果缓存区长度大于解析出来的完整一包的长度
  4. 执行: 取出完整一包数据后,然后剔除这包,将缓存区剩余数据放置起始位
  5. 循环里再次判断: 长度小于某个值n,return再次读取网络数据。 这个值n长度的数据内要能解析出单个完整包的长度,以便后续处理

如果协议定义了帧头,可以在取包的长度之前校验帧头,确保数据正确。

这里说明定义缓冲区buffer的长度大小:必须要大于可能收到的最大数据包的长度加上read读取一次网络最大数据长度 原因是缓冲区里可能剩下不到一包数据,下一次读取网络数据后要将数据copy至缓冲区,如果超过缓冲区大小就无法进行处理。可在copy时加一层判断,如果超过缓存区,就直接返回,断开连接。代表这种数据包不能进行处理。如果缓存区设计合理,不会出现此种情况。read读取一次网络最大数据长度是在read到的buffer定义的长度。缓冲区的buffer不要设置过大,占用太多内存。

数据源说明:第一位固定#。第二位表示之后有几位代表了之后的数据的长度,比如第一条数据的第二位4,代表之后的四位3350是从0:开始共有3350个字节长度的数据。之后的数据跟业务相关。

主要代码:

Java实现: 不可用于生产环境,理解思想后根据业务数据处理粘包

private static int MAXDATALEN = 500000; //处理数据缓冲池的长度
private static int RECEIVEDATALEN = 200000;//读取网络数据包最大长度
private int SiglePackageLen = 0;//提取出包的长度
private int SequenceLen = 0;//当前缓冲区内数据长度
private byte BuffSequencePackage[] = new byte[MAXDATALEN];//数据缓冲池
public void readData() {
    //读取网络数据长度
        int RecvLen;
        //缓存区
        byte ReceiveData[] = new byte[RECEIVEDATALEN];
        try {
            while (AdapterManager.getInstance().isFlag()) {
                if (mSocket.isConnected()) {
                    if (!mSocket.isInputShutdown()) {
                        if ((RecvLen = inputstream.read(ReceiveData)) != 0) {
                            NetNum++;
// Log.i("Read", ">>>>>>>>>>>第" + NetNum + "次读取网络数据 共" + RecvLen + "字节<<<<<<<<<<<<<<<");
                            if (RecvLen <= 0) {
                                ToastUtils.showShortSafe(ERROR_Device);
                                Log.i(TAG, "设备断开连接,RecvLen: " + RecvLen);
                                return;
                            }
                            ReceivedPackage(RecvLen,ReceiveData);
                        }
                    }
                }
            }
        } catch (SocketTimeoutException e) {
            e.printStackTrace();
            Log.i(TAG, "网络异常断开,收取数据超时");
            ToastUtils.showShortSafe(ERROR_NetBreak);
        } catch (SocketException e) {
            e.printStackTrace();
            Log.i(TAG, "停止任务,断开连接");
        } catch (Exception e) {
            e.printStackTrace();
            Log.i(TAG, "读取数据异常");
        }
    }
     /**
     *  粘包处理
     * */
    public void ReceivedPackage(int RecvLen,byte[] ReceiveData) throws Exception {
        pakageNum = 0;
        //将收到的数据copy至缓冲区
        System.arraycopy(ReceiveData, 0, BuffSequencePackage, SequenceLen, RecvLen);
        //记录缓冲区usedlength
        SequenceLen += RecvLen;
        //这一步保证后续操作能解析出这一包数据的长度
        if (SequenceLen <10) {
            return;
        }
        int lentemp = BuffSequencePackage[1] - '0';
        SiglePackageLen = Integer.parseInt(new String(Arrays.copyOfRange(BuffSequencePackage, 2, lentemp + 2))) + 2 + lentemp;
        //缓存区长度大于等于下一包的长度,说明缓冲区里还有完整的一包数据
        while (SiglePackageLen <= SequenceLen) {
            //判断任务是否结束。如果网络不佳或程序没有及时处理数据导致网络中的数据缓存过多,一次read读取数据就会将read的ReceiveData沾满,如果read的ReceiveData长度定义很大,将这些数据copy至缓存区,则这个循环会执行一段时间, 如果没有这层判断,即使任务结束,这个循环还在执行。
            if (!AdapterManager.getInstance().isFlag()) {
                return;
            }
            //取出第一包数据分析
            byte SiglePackage[] = Arrays.copyOfRange(BuffSequencePackage, 2 + lentemp, SiglePackageLen);
            AnalyseReceivedPackage(SiglePackage);
            //剔除缓冲区第一包数据
            byte temp[] = Arrays.copyOfRange(BuffSequencePackage, SiglePackageLen, SequenceLen);
            //记录缓冲区usedlength
            SequenceLen = SequenceLen - SiglePackageLen;
            //清空缓存区
            resumeSequence();
            //将剩余数据copy至缓冲区,从头部开始
            System.arraycopy(temp, 0, BuffSequencePackage, 0, temp.length);
            //再次进行验证,剩余的长度是否能提取出下一包数据的长度
            if (SequenceLen < 10) {
                return;
            }
            //提取下一包数据长度
            lentemp = BuffSequencePackage[1] - '0';
            SiglePackageLen = Integer.parseInt(new String(Arrays.copyOfRange(BuffSequencePackage, 2, lentemp + 2))) + 2 + lentemp;
        }
    }

c++实现:

//s_plocalStationData是从队列里申请的缓存区,length是缓存区有效数据的长度,MessageHead是帧头结构体
    LocalStationDataInfo* info = pContext->m_Array->s_plocalStationData;
    //将数据copy到缓存区
    memcpy(info->pdata+info->length , pOverlapBuff->GetBuffer(),nSize);
    info->length =nSize + info->length;
    if(info->length<sizeof(MessageHead)){
        return;
    }
    unsigned long singlePakageLength = FindPackagetLength(info->pdata);
    while(singlePakageLength <= (info->length)){
        //将完整的一包提取出来分析
        AnalyseReceivedPackage(info->pdata,singlePakageLength);
        info->length =info->length - singlePakageLength;
        //剔除缓存区第一包数据
        memmove(info->pdata,info->pdata+singlePakageLength,info->length);
        //确保缓存区剩余数据长度可以提取出下一包数据的包长
        if(info->length < sizeof(MessageHead)){ 
            return;
        }
        singlePakageLength = FindPackagetLength(info->pdata);
    }

原文发布于微信公众号 - Android机动车(JsAndroidClub)

原文发表时间:2017-12-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏醒者呆

Controller:EOS区块链核心控制器

命名空间namespace定义了一个范围,这个范围本身可作为额外的信息,类似于地址,或者位置。如果有两个名字相同的变量或者函数,例如foshan::linshu...

19130
来自专栏乐沙弥的世界

mongoDB简介及关键特性

10610
来自专栏iKcamp

手把手教你撸一个 Webpack Loader

文:小 boy(沪江网校Web前端工程师) 本文原创,转载请注明作者及出处 ? 经常逛 webpack 官网的同学应该会很眼熟上面的图。正如它宣传的一样,w...

50040
来自专栏有趣的django

35.Django2.0文档

第四章 模板  1.标签 (1)if/else {% if %} 标签检查(evaluate)一个变量,如果这个变量为真(即,变量存在,非空,不是布尔值假),系...

599100
来自专栏蓝天

redis的一些简介

Redis是Remote Dictionary Server的缩写,他本质上一个Key/Value数据库,与Memcached类似的NoSQL型数据库。

11910
来自专栏顶级程序员

Linux中高效编写Bash脚本的10个技巧

Linux开源社区(微信号:cn_linux) 英文:Aaron Kili,翻译:Linux中国/ch-cn 链接:linux.cn/article-8618...

35650
来自专栏FreeBuf

渗透测试中利用基于时间差反馈的远程代码执行漏洞(Timed Based RCE)进行数据获取

在最近的渗透测试项目中,为了进一步验证漏洞的可用性和危害性,我们遇到了这样一种情形:构造基于时间差反馈的系统注入命令(OS command injection ...

23090
来自专栏转载gongluck的CSDN博客

python笔记:#004#注释

注释 目标 注释的作用 单行注释(行注释) 多行注释(块注释) 01. 注释的作用 使用用自己熟悉的语言,在程序中对某些代码进行标注说明,增强程序的可读性 ...

33170
来自专栏C++

python笔记:#002#第一个python程序

13640
来自专栏叁金大数据

自学Python八 爬虫大坑之网页乱码

  Bug有时候破坏的你的兴致,阻挠了保持到现在的渴望。可是,自己又非常明白,它是一种激励,是注定要被你踩在脚下的垫脚石!

41910

扫码关注云+社区

领取腾讯云代金券