tcp协议与粘包现象【转http://www.cnblogs.com/wzd24/archive/2007/12/24/1011932.html】
Socket開發之通訊協議及處理
在Socket應用開發中,還有一個話題是討論的比較多的,那就是數據接收后如何處理的問題。這也是一個令剛接觸Socket開發的人很頭疼的問題。因為Socket的TCP通訊中有一個“粘包”的現象,既:大多數時候發送端多次發送的小數據包會被連在一起被接收端同時接收到,多個小包被組成一個大包被接收。有時候一個大數據包又會被拆成多個小數據包發送。這樣就存在一個將數據包拆分和重新組合的問題。那么如何去處理這個問題呢?這就是我今天要講的通訊協議。
所謂的協議就是通訊雙方協商并制定好要傳送的數據的結構與格式。并按制定好的格式去組合與分析數據。從而使數據得以被準確的理解和處理。
那么我們如何去制定通訊協議呢?很簡單,就是指定數據中各個字節所代表的意義。比如說:第一位代表封包頭,第二位代表封類型,第三、四位代表封包的數據長度。然后后面是實際的數據內容。
如下面這個例子:
| 01 | 01 | 06 00 | 01 0f ef 87 56 34 |
| 協議類別 | 協議代碼 | 數據長度 | 實際數據 |
前面三部分稱之為封包頭,它的長度是固定的,第四部分是封包數據,它的長度是不固定的,由第三部分標識其長度。因為我們的協議將用在TCP中,所以我沒有加入校驗位。原因是TCP可以保證數據的完整性。校驗位是沒有必要存在的。
接下來我們要為這個數據封包聲明一個類來封裝它:
?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????} ?
我們可以用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里面的新東西,如果有什么必得的話,我會繼續寫出來的。謝謝大家的支持。轉載于:https://www.cnblogs.com/songtzu/archive/2013/05/09/3068560.html
總結
以上是生活随笔為你收集整理的tcp协议与粘包现象【转http://www.cnblogs.com/wzd24/archive/2007/12/24/1011932.html】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HDOJ 2673 shǎ崽 OrOrO
- 下一篇: Photon——Setup and Co