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 @classbyte 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, 0this._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,则说明流中已经没有完整的封包,你需要继续接收后面的数据以组成一个完整的封包。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值