c# Socket通信:数据接收处理方案

基础知识:

TCP/IP 网络协议栈分为应用层(Application)、传输层(Transport)、网络层(Network)和链路层(Link)四层

通信过程中,每层协议都要加上一个数据首部(header),称为封装(Encapsulation),如下图所示

什么是粘包,分包:

如图:

è¿éåå¾çæè¿°

产生原因:

数据发送端发送数据给缓冲区Buffer太大,导致一个完整的数据包被分为几部分发送给Buffer,然而缓冲buffer等到数据满了以后会自动把数据发送的数据链路层去,这样就导致分包了。 

TCP协议定义有一个选项叫做最大报文段长度(MSS,Maximum Segment Size),该选项用于在TCP连接建立时,收发双方协商通信时每一个报文段所能承载的最大数据长度。在一定程度上MSS应该能尽可能多地承载用户数据,用于在传输通路上又可能避免分片,但是在复杂的网络环境下确定这个长度值非常困难,那么在这样的情况下在传输过程中产生分包,粘包就很常见了


数据帧的有效载荷(payload)比以太网的最大传输单元(MTU)大的时候,进行了IP分片

解决方案:

1.消息定长,比如定一个100,那么读取端每次读取数据就截取100个长度的数据,然后交给业务成去做解析 
2.在消息的尾部加一些特殊字符,那么在读取数据的时候,只要读到这个特殊字符,就认为已经可以截取一个完整的数据包了,这种情况在一定的业务情况下实用。 
3.读取缓存的数据是不定长的,所以我们把读取到的数据添加到我们自己的一个byte[]数组中,然后根据我们的业务逻辑来找到指定的特殊协议头部,协议长度,协议尾部,然后从我们的byte[]中获取一个完整的数据包,然后再对数据包进行业务解析就可以得到正确结果

大多数的应用中,你说是定长消息,不太实用。而特殊字符就要根据客户的需求书要求了,还有行业标准,想来想去还是用第三种方案:

我同事说,为什么不一个字节一个字节读取,读到协议头部就开始组装,根据协议读取整体长度还有包尾,读不到我需要的包头我就全部扔掉,一开始我觉得挺好,因为感觉socket连接报文不是很频繁,不会出现太大量的数据,应该不会出现粘包,分包的情况,大部分会每次读取正好是一条完整的数据,但是根据墨菲定律来说:凡是可能会出错的就一定会出错。

第一步,定义一个缓存

 public  byte[] Buffer { set; get; }
        public  int DataCount { set; get; }
   

      public  MsgPackProcess(int buffSize) {
            Buffer = new byte[buffSize];
            DataCount = 0;
            }

第二步,缓存的写入方法

        /// <summary>
        /// 获取剩余字节数
        /// </summary>
        /// <returns></returns>
        public int GetReserveCount() {
            return Buffer.Length - DataCount;
        }

        /// <summary>
        /// 写入缓存。判断缓存的大小
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="offset"></param>
        /// <param name="count"></param>
        public void WriteBuffer(byte[] buffer, int offset, int count) {
            if (GetReserveCount() >= count)
            {
                Array.Copy(buffer, offset, Buffer, DataCount, count);//
            }
            else {
                int totalSize = Buffer.Length + count;//- GetReserveCount()总大小-空余大小
                byte[] tempBuffer = new byte[totalSize];
                Array.Copy(Buffer, 0, tempBuffer, 0, DataCount);//复制以前的数据
                Array.Copy(buffer, offset, tempBuffer, DataCount, count);//复制新写入的数据
                //DataCount = DataCount + count;
                Buffer = tempBuffer;//替换
            }
            DataCount = DataCount + count;
        }

        //写入缓存数据
        public void WriteBuffer(byte[] buffer){
            WriteBuffer(buffer, 0, buffer.Length);
        }
第三步,缓存的读取,因为socket我这是长连接,所以只要有数据传输,我就会写入缓存,同样只要是缓存有数量在协议内的数据我就会读取,所以,轮询读取:

while (true)
                {
                    lock (client.revobj) //因为是对同一个缓存操作,为了避免数据混乱,使用线程锁
                    {
                        if (client.mp.GetDataCount() >= 6)  长度达到标准开始执行
                        {
                            tmp = client.mp.Buffer;

                            for (int i = 0; i < tmp.Length; i++)  找到包头的位置
                            {
                                if (tmp[i] == 0xeb)
                                {
                                    index = i;
                                    break;
                                }
                            }
                            if (index == -1)                           这一步,在考虑要不要删掉,我想每次取完一个完整的信息包,如果缓存里不
                            {                                                 存在包头,就是信息混乱,把他初始化,然后继续轮询
                                client.mp.Buffer = new byte[0];
                                client.mp.DataCount = 0;
                                continue;
                            }
                            try {
                                bytes = tmp.Skip(index).Take(1).ToArray();          根据协议取包头
                                header = nt.ByteArrayToHexString(bytes);

                                bytes = tmp.Skip(index + 1).Take(1).ToArray();   取类型
                                msgType = nt.ByteArrayToHexString(bytes);
                                if (msgType != "31" && msgType != "32" && msgType != "33" && msgType != "34" && msgType != "35" && msgType != "36")            //判断类型是否在协议内 如果不在去除上一个包头,继续轮询下一个包头位置
                                {
                                    byte[] ot = new byte[client.mp.GetDataCount() - 1];
                                    Buffer.BlockCopy(tmp, index + 1, ot, 0, ot.Length);
                                    client.mp.Buffer = ot;
                                    client.mp.DataCount = client.mp.Buffer.Length;
                                    continue;
                                }
                                bytes = tmp.Skip(index + 2).Take(1).ToArray();
                                msn = nt.ByteArrayToHexString(bytes);
                                bytes = tmp.Skip(index + 3).Take(2).ToArray();
                                dataLen = nt.ByteArrayToHexString(bytes);         获取包的长度
                                string tmpdl = "00";
                                //包长度
                                tmpdl = nt.hexToDec(dataLen);
                                //去0转化
                                int dl = nt.removZero(tmpdl);

                                if (client.mp.GetDataCount() < (dl + 6))              判断缓存的长度是否和报文的长度匹配。如果小于则跳出等                                                                                                              待
                                {
                                    flag++;
                                    //等待两秒 超过时间重启socket 并清空缓存
                                    if (flag > 3)                                                      如果等待三次6秒仍然无信息输入,不符合要求,则清空缓                                                                                                               存,重新连接socket
                                    {
                                        flag = 0;
                                        client.mp.Clear();

                                       reConnectionSocket();
                                    }
                                    Thread.Sleep(2000);
                                    continue;
                                }
                                bytes = tmp.Skip(index + 5).Take(dl).ToArray();              //余下的都是读取报文内容的

                                data = nt.ByteArrayToHexString(bytes);
                                bytes = tmp.Skip(index + 5 + dl).Take(1).ToArray();
                                end = nt.ByteArrayToHexString(bytes);
                                result = header + msgType + msn + dataLen + data + end;                   组装好争取报文
                                AppLog.Info(LoggerEnum.LogType.RECV.ToString(), result);
                                byte[] bty = new byte[client.mp.GetDataCount() - dl - 6];                         根据协议将取出来的报文从缓存内清
                                Buffer.BlockCopy(tmp, (index + 5 + dl + 1), bty, 0, bty.Length);               除
                                client.mp.Buffer = bty;
                                client.mp.DataCount = client.mp.Buffer.Length;
                                ReciveMsgPro(result);                                                                               处理正确的报文信息

                            } catch (Exception ex) {
                                AppLog.Info(LoggerEnum.LogType.OTHER.ToString(), "解析缓存失败 " + ex.Message.ToString());
                            }
                            
                        }
                    }
                    Thread.Sleep(200);
                }

大体是这个意思,能力有限,有待提高!不过,将不会的学会了一点仍然很开心,每天进步一丢丢,就感觉今天没有虚度


 

 

 

  • 4
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值