在Socket應用開發中,還有一個話題是討論的比較多的,那就是數據接收後如何處理的問題。這也是一個令剛接觸Socket開發的人很頭疼的問題。
因爲Socket的TCP通訊中有一個“粘包”的現象,既:大多數時候發送端多次發送的小數據包會被連在一起被接收端同時接收到,多個小包被組成一個大包被接收。有時候一個大數據包又會被拆成多個小數據包發送。這樣就存在一個將數據包拆分和重新組合的問題。那麼如何去處理這個問題呢?這就是我今天要講的通訊協議。
所謂的協議就是通訊雙方協商並制定好要傳送的數據的結構與格式。並按制定好的格式去組合與分析數據。從而使數據得以被準確的理解和處理。
那麼我們如何去制定通訊協議呢?很簡單,就是指定數據中各個字節所代表的意義。比如說:第一位代表封包頭,第二位代表封類型,第三、四位代表封包的數據長度。然後後面是實際的數據內容。
如下面這個例子:
01 |
01 |
06 00 |
01 0f ef 87 56 34 |
協議類別 |
協議代碼 |
數據長度 |
實際數據 |
前面三部分稱之爲封包頭,它的長度是固定的,第四部分是封包數據,它的長度是不固定的,由第三部分標識其長度。因爲我們的協議將用在TCP中,所以我沒有加入校驗位。原因是TCP可以保證數據的完整性。校驗位是沒有必要存在的。
接下來我們要爲這個數據封包聲明一個類來封裝它:
我們可以用Tobytes()和FromBytes()將封包轉換成二進制數組和從二進制數組轉換回來。
事情看起來已經解決了,但……真的是這樣子嗎?不然,我們知道,TCP數據是以流的形式被傳送的,我們並不知道一個數據包是否被傳送完畢,也不知道我們接收回來的數據包中是否有多個數據包,如果直接使用FromBytes()來轉換的話,很可能會因爲數據不完整而出現異常,也有可能會因爲數據中含有多個數據包而導致數據丟失(因爲你並不知道這些數據中含有多少個數據包)。那我們該怎麼辦?這也不難,我們先把接收回來的數據寫入一個流中。然後分析其中是否有完整的數據包,如果有,將其從流中取出,並將這部分數據從流中清除。直到流中沒有完整的數據爲止,以後接收回來的數據就將其寫入流的結尾處,並從頭繼續分析。直到結束。
讓我們來看看這部分的代碼:
1 public class MessageStream
2 {
3 private byte[] _buffer;
4 private int _position;
5 private int _length;
6 private int _capacity;
7
8 public MessageStream()
9 {
10 _buffer = new byte[0];
11 _position = 0;
12 _length = 0;
13 _capacity = 0;
14 }
15
16 private byte ReadByte()
17 {
18 if (this._position >= this._length)
19 {
20 return 0;
21 }
22 return this._buffer[this._position++];
23 }
24
25 private int ReadInt()
26 {
27 int num = this._position += 4;
28 if (num > this._length)
29 {
30 this._position = this._length;
31 return -1;
32 }
33 return (((this._buffer[num - 4] | (this._buffer[num - 3] << 8)) | (this._buffer[num - 2] << 0x10)) | (this._buffer[num - 1] << 0x18));
34 }
35
36 private byte[] ReadBytes(int count)
37 {
38 int num = this._length - this._position;
39 if (num > count)
40 {
41 num = count;
42 }
43 if (num <= 0)
44 {
45 return null;
46 }
47 byte[] buffer = new byte[num];
48 if (num <= 8)
49 {
50 int num2 = num;
51 while (--num2 >= 0)
52 {
53 buffer[num2] = this._buffer[this._position + num2];
54 }
55 }
56 else
57 {
58 Buffer.BlockCopy(this._buffer, this._position, buffer, 0, num);
59 }
60 this._position += num;
61 return buffer;
62 }
63
64 public bool Read(out Message message)
65 {
66 message = null;
67 _position = 0;
68 if (_length > 6)
69 {
70 message = new Message();
71 message.Class = ReadByte();
72 message.Flag = ReadByte();
73 message.Size = ReadInt();
74 if (message.Size <= 0 || message.Size <= _length - _position)
75 {
76 if (message.Size > 0)
77 {
78 message.Content = ReadBytes(message.Size);
79 }
80 Remove(message.Size + 6);
81 return true;
82 }
83 else
84 {
85 message = null;
86 return false;
87 }
88 }
89 else
90 {
91 return false;
92 }
93 }
94
95 private void EnsureCapacity(int value)
96 {
97 if (value <= this._capacity)
98 return;
99 int num1 = value;
100 if (num1 < 0x100)
101 num1 = 0x100;
102 if (num1 < (this._capacity * 2))
103 num1 = this._capacity * 2;
104 byte[] buffer1 = new byte[num1];
105 if (this._length > 0)
106 Buffer.BlockCopy(this._buffer, 0, buffer1, 0, this._length);
107 this._buffer = buffer1;
108 this._capacity = num1;
109 }
110
111 public void Write(byte[] buffer, int offset, int count)
112 {
113 if (buffer.Length - offset < count)
114 {
115 count = buffer.Length - offset;
116 }
117 EnsureCapacity(buffer.Length + count);
118 Array.Clear(_buffer, _length, _capacity - _length);
119 Buffer.BlockCopy(buffer, offset, _buffer, _length, count);
120 _length += count;
121 }
122
123 private void Remove(int count)
124 {
125 if (_length >= count)
126 {
127 Buffer.BlockCopy(_buffer, count, _buffer, 0, _length - count);
128 _length -= count;
129 Array.Clear(_buffer, _length, _capacity - _length);
130 }
131 else
132 {
133 _length = 0;
134 Array.Clear(_buffer, 0, _capacity);
135 }
136 }
137 }
這個類的使用非常簡單,你只要用Write(byte[] buffer, int offset, int count)將接收到的數據寫入數據流中,並用bool Read(out Message message)將數據中的第一個數據包取出,如果函數返回True,就說明取回一個封包成功,如果返回False,則說明流中已經沒有完整的封包,你需要繼續接收後面的數據以組成一個完整的封包。
這們我們的數據分析就會變得非常簡單。我們可以在ReceiveCallBack回調函數中將接收到的數據寫入到流中並通知線程池中的工作者線程分析數據流並處理數據。我在前面的關於Socket異步操作的文章中的Analyzer函數就是用這兩個類來分析處理數據的。這樣的好處理就是,Socket工作線程只需要負責數據的接收,並將其寫入流,其它的事情由其它的線程這處理,就不會因爲處理的時間過長而導致接收操作被阻塞。從而影響Socket的性能。
本文所述方法只是協議處理的多種方法中的其中一種,而且可能並不是很優秀的方法,如果誰有更好的方法,還希望您能和我多多交流。好了,今天就到這裏了,關於Socket的文章到這裏可能就告一段落了,我現在在研究VS2008裏面的新東西,如果有什麼必得的話,我會繼續寫出來的。謝謝大家的支持。
1 public class Message
2 {
3 private byte _class;
4 private byte _flag;
5 private int _size;
6 private byte[] _content;
7
8 public byte[] Content
9 {
10 get { return _content; }
11 set { _content = value; }
12 }
13
14 public int Size
15 {
16 get { return _size; }
17 set { _size = value; }
18 }
19
20 public byte Flag
21 {
22 get { return _flag; }
23 set { _flag = value; }
24 }
25
26 public byte Class
27 {
28 get { return _class; }
29 set { _class = value; }
30 }
31
32 public Message()
33 {
34
35 }
36
37 public Message(byte @class, byte flag, byte[] content)
38 {
39 _class = @class;
40 _flag = flag;
41 _size = content.Length;
42 _content = content;
43 }
44
45 public byte[] ToBytes()
46 {
47 byte[] _byte;
48 using (MemoryStream mem = new MemoryStream())
49 {
50 BinaryWriter writer = new BinaryWriter(mem);
51 writer.Write(_class);
52 writer.Write(_flag);
53 writer.Write(_size);
54 if (_size > 0)
55 {
56 writer.Write(_content);
57 }
58 _byte = mem.ToArray();
59 writer.Close();
60 }
61 return _byte;
62 }
63
64 public static Message FromBytes(byte[] Buffer)
65 {
66 Message message = new Message();
67 using (MemoryStream mem = new MemoryStream(Buffer))
68 {
69 BinaryReader reader = new BinaryReader(mem);
70 message._class = reader.ReadByte();
71 message._flag = reader.ReadByte();
72 message._size = reader.ReadInt32();
73 if (message._size > 0)
74 {
75 message._content = reader.ReadBytes(message._size);
76 }
77 reader.Close();
78 }
79 return message;
80 }
81
82 }