Socket开发之通讯协议及处理(解决粘包问题)

在Socket应用开发中,还有一个话题是讨论的比较多的,那就是数据接收后如何处理的问题。这也是一个令刚接触Socket开发的人很头疼的问题。


因为Socket的TCP通讯中有一个“粘包”的现象,既:大多数时候发送端多次发送的小数据包会被连在一起被接收端同时接收到,多个小包被组成一个大包被接收。有时候一个大数据包又会被拆成多个小数据包发送。这样就存在一个将数据包拆分和重新组合的问题。那么如何去处理这个问题呢?这就是我今天要讲的通讯协议。
 
所谓的协议就是通讯双方协商并制定好要传送的数据的结构与格式。并按制定好的格式去组合与分析数据。从而使数据得以被准确的理解和处理。
 
那么我们如何去制定通讯协议呢?很简单,就是指定数据中各个字节所代表的意义。比如说:第一位代表封包头,第二位代表封类型,第三、四位代表封包的数据长度。然后后面是实际的数据内容。
 
如下面这个例子:

 

01

01

0600

01 0f ef 87 56 34

协议类型

协议代码

数据长度

实际数据

前面三部分称之为封包头,它的长度是固定的,第四部分是封包数据,它的长度是不固定的,由第三部分标识其长度。因为我们的协议将用在TCP中,所以我没有加入校验位。原因是TCP可以保证数据的完整性。校验位是没有必要存在的。 

接下来我们要为这个数据封包声明一个类来封装它:

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public  class  Message
    {
        private  byte  _class;
        private  byte  _flag;
        private  int  _size;
        private  byte [] _content;
 
        public  byte [] Content
        {
            get {  return  _content; }
            set { _content = value; }
        }
 
        public  int  Size
        {
            get {  return  _size; }
            set { _size = value; }
        }
 
        public  byte  Flag
        {
            get {  return  _flag; }
            set { _flag = value; }
        }
 
        public  byte  Class
        {
            get {  return  _class; }
            set { _class = value; }
        }
 
        public  Message()
        {
 
        }
 
        public  Message( byte  @class byte  flag,  byte [] content)
        {
            _class =  @class ;
            _flag = flag;
            _size = content.Length;
            _content = content;
        }
 
        public  byte [] ToBytes()
        {
            byte [] _byte;
            using (MemoryStream mem =  new  MemoryStream())
            {
                BinaryWriter writer =  new  BinaryWriter(mem);
                writer.Write(_class);
                writer.Write(_flag);
                writer.Write(_size);
                if  (_size >  0 )
                {
                    writer.Write(_content);
                }
                _byte = mem.ToArray();
                writer.Close();
            }
            return  _byte;
        }
 
        public  static  Message FromBytes( byte [] Buffer)
        {
            Message message =  new  Message();
            using (MemoryStream mem =  new  MemoryStream(Buffer))
            {
                BinaryReader reader =  new  BinaryReader(mem);
                message._class = reader.ReadByte();
                message._flag = reader.ReadByte();
                message._size = reader.ReadInt32();
                if  (message._size >  0 )
                {
                    message._content = reader.ReadBytes(message._size);
                }
                reader.Close();
            }
            return  message;
        }
 
    }

 

我们可以用Tobytes()和FromBytes()将封包转换成二进制数组和从二进制数组转换回来。
 
事情看起来已经解决了,但……真的是这样子吗?不然,我们知道,TCP数据是以流的形式被传送的,我们并不知道一个数据包是否被传送完毕,也不知道我们接收回来的数据包中是否有多个数据包,如果直接使用FromBytes()来转换的话,很可能会因为数据不完整而出现异常,也有可能会因为数据中含有多个数据包而导致数据丢失(因为你并不知道这些数据中含有多少个数据包)。那我们该怎么办?这也不难,我们先把接收回来的数据写入一个流中。然后分析其中是否有完整的数据包,如果有,将其从流中取出,并将这部分数据从流中清除。直到流中没有完整的数据为止,以后接收回来的数据就将其写入流的结尾处,并从头继续分析。直到结束。
 
让我们来看看这部分的代码:

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
public  class  MessageStream
    {
        private  byte [] _buffer;
        private  int  _position;
        private  int  _length;
        private  int  _capacity;
 
        public  MessageStream()
        {
            _buffer =  new  byte [ 0 ];
            _position =  0 ;
            _length =  0 ;
            _capacity =  0 ;
        }
 
        private  byte  ReadByte()
        {
            if  ( this ._position >=  this ._length)
            {
                return  0 ;
            }
            return  this ._buffer[ this ._position++];
        }
 
        private  int  ReadInt()
        {
            int  num =  this ._position +=  4 ;
            if  (num >  this ._length)
            {
                this ._position =  this ._length;
                return  - 1 ;
            }
            return  ((( this ._buffer[num -  4 ] | ( this ._buffer[num -  3 ] <<  8 )) | ( this ._buffer[num -  2 ] <<  0x10 )) | ( this ._buffer[num -  1 ] <<  0x18 ));
        }
 
        private  byte [] ReadBytes( int  count)
        {
            int  num =  this ._length -  this ._position;
            if  (num > count)
            {
                num = count;
            }
            if  (num <=  0 )
            {
                return  null ;
            }
            byte [] buffer =  new  byte [num];
            if  (num <=  8 )
            {
                int  num2 = num;
                while  (--num2 >=  0 )
                {
                    buffer[num2] =  this ._buffer[ this ._position + num2];
                }
            }
            else
            {
                Buffer.BlockCopy( this ._buffer,  this ._position, buffer,  0 , num);
            }
            this ._position += num;
            return  buffer;
        }
 
        public  bool Read(out Message message)
        {
            message =  null ;
            _position =  0 ;
            if  (_length >  6 )
            {
                message =  new  Message();
                message.Class = ReadByte();
                message.Flag = ReadByte();
                message.Size = ReadInt();
                if  (message.Size <=  0  || message.Size <= _length - _position)
                {
                    if  (message.Size >  0 )
                    {
                        message.Content = ReadBytes(message.Size);
                    }
                    Remove(message.Size +  6 );
                    return  true ;
                }
                else
                {
                    message =  null ;
                    return  false ;
                }
            }
            else
            {
                return  false ;
            }
        }
 
        private  void  EnsureCapacity( int  value)
        {
            if  (value <=  this ._capacity)
                return ;
            int  num1 = value;
            if  (num1 <  0x100 )
                num1 =  0x100 ;
            if  (num1 < ( this ._capacity *  2 ))
                num1 =  this ._capacity *  2 ;
            byte [] buffer1 =  new  byte [num1];
            if  ( this ._length >  0 )
                Buffer.BlockCopy( this ._buffer,  0 , buffer1,  0 this ._length);
            this ._buffer = buffer1;
            this ._capacity = num1;
        }
 
        public  void  Write( byte [] buffer,  int  offset,  int  count)
        {
            if  (buffer.Length - offset < count)
            {
                count = buffer.Length - offset;
            }
            EnsureCapacity(buffer.Length + count);
            Array.Clear(_buffer, _length, _capacity - _length);
            Buffer.BlockCopy(buffer, offset, _buffer, _length, count);
            _length += count;
        }
 
        private  void  Remove( int  count)
        {
            if  (_length >= count)
            {
                Buffer.BlockCopy(_buffer, count, _buffer,  0 , _length - count);
                _length -= count;
                Array.Clear(_buffer, _length, _capacity - _length);
            }
            else
            {
                _length =  0 ;
                Array.Clear(_buffer,  0 , _capacity);
            }
        }
    }


这个类的使用非常简单,你只要用Write(byte[] buffer, int offset, int count)将接收到的数据写入数据流中,并用bool Read(out Message message)将数据中的第一个数据包取出,如果函数返回True,就说明取回一个封包成功,如果返回False,则说明流中已经没有完整的封包,你需要继续接收后面的数据以组成一个完整的封包。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值