C#下利用封包、拆包原理解決Socket粘包、半包問題(新手篇)


介於網絡上充斥着大量的含糊其辭的Socket初級教程,擾亂着新手的學習方向,我來扼要的教一下新手應該怎么合理的處理Socket這個玩意兒。

一般來說,教你C#下Socket編程的老師,很少會教你如何解決Socket粘包、半包問題。

甚至,某些師德有問題的老師,根本就沒跟你說過Socket的粘包、半包問題是什么玩意兒。

直到有一天,你的Socket程序在傳輸信息時出現了你預期之外的結果(多於的信息、不完整的信息、亂碼、Bug等等)。

任你喊了一萬遍“我擦”,依舊是不知道問題出在哪兒!

好了,不說廢話了,進入正題,包教包會,學不會,送路費。

 

如果你讀到這篇文章了,想必你已經遇到了以上問題,情況再理想一點兒,其實你在原理上已經知道怎么解決這個問題了,只是不知道怎么動手。

那么,首先,你需要新建一個【消息協議類】,這個類我們暫定由5大屬性組成,分別是:

【(①=1個byte)(②=1個byte])(③=1個int)(④=1個byte[])(⑤=1個byte[])】

解釋一下這個【消息協議類】:

(①=1個byte):這個屬性占1個字節,可以放一個0到254的整數,我稱作1號標志;

(②=1個byte):這個屬性占1個字節,可以放一個0到254的整數,我稱作2號標志。那么1號標志和2號標志就有多達255×255個組合,我們用它來自定義這個消息的標志,比如,0-0表示登錄請求消息,1-1表示物理攻擊,1-2表示魔法攻擊,3-3表示坐標移動;

(③=1個int):這個屬性占4個字節,可以放一個0到2147483647的整數,它表示(④=1個byte[])的長度;

(④=1個byte[]):這個屬性存着你要發送的全部消息體字節,所以,你的消息體需要被轉化為字節數組才可存放進去;

(⑤=1個byte[]):這個屬性存着【多余的消息體字節】。那么問題來了,什么是【多余的消息體字節】?休息一下繼續往下看。

再解釋一下這個【消息協議類】具體怎么用。

1,【消息發送方】先定義【消息協議類】①②屬性,也就是隨便寫2個數;

2,【消息發送方】再將消息“裝入”【消息協議類】的④屬性,那么③屬性就有了;

3,【消息發送方】將封裝好的【消息協議類】轉為byte[],發送給【消息接收方】,我們把這道工序稱作【封包】;

4,【消息接收方】接收到【消息發送方】發來的byte[]時,先判斷這個byte[]長度是否大於6,即是否大於①屬性+②屬性+③屬性的長度和,如果byte[]長度小於6,【消息接收方】就循環繼續接收;

5,【消息接收方】接收到【消息發送方】發來的byte[]長度大於等於6了!!則將byte[]還原為【消息協議類】,為了區別,我們暫時把它為【新消息協議類】;

6,循環判斷【消息發送方】發來的byte[]長度減去6之后的值是否大於等於【新消息協議類】的③的值。這個可以理解為byte[]是否為一個完整的【消息協議類】,如果是就把【新消息協議類】的④屬性拆出來,就得到了一個剛剛好完整的消息,不多也不少。那么,我們就把這道工序稱作【拆包】;

7,相信你已經反應過來⑤這個【多余的消息體字節】是干嘛用的了。上一步當中,如果byte[]信息剛好是一個完整長度自然用不到⑤了,但是在網絡傳輸中byte[]自然不會永遠那么剛好了,所以當byte[]長度大於一個完整消息時,就把多於的byte放入⑤當中,和下次新接收的byte[]組合在一起,再次進行這樣的循環,保證數據的完整性和獨立性。

8,好了,以上過程就是利用封包、拆包原理解決Socket粘包、半包問題,接下來你可以在發送方以非常頻繁的發送頻率來發送,接收方依然會規規矩矩完完整整的以正確的姿勢來接收消息了。最下面是要點代碼,相信聰明的你一定學會了,如果還沒學會,可以加我QQ:119945778,包教包會,不然我還得送路費不是...

下面是源碼時間:

  1     /// <summary>
2 /// 【消息協議】=【協議一級標志】+【協議二級標志】+【實際消息長度】+【實際消息內容】+【多於消息內容】
3 /// </summary>
4 public class MessageXieYi
5 {
6 #region 自定義
7 #region 協議一級標志,值 = (0 至 254 )
8 private byte xieYiFirstFlag;
9 /// <summary>
10 /// 協議類別,值 = ( 0 直 254 )
11 /// </summary>
12 public byte XieYiFirstFlag
13 {
14 get { return xieYiFirstFlag; }
15 set { xieYiFirstFlag = value; }
16 }
17 #endregion
18
19 #region 協議二級標志,值 = (0 至 254 )
20 private byte xieYiSecondFlag;
21 /// <summary>
22 /// 協議二級標志,值 = (0 至 254 )
23 /// </summary>
24 public byte XieYiSecondFlag
25 {
26 get { return xieYiSecondFlag; }
27 set { xieYiSecondFlag = value; }
28 }
29 #endregion
30
31 #region 實際消息長度
32 private int messageContentLength;
33 /// <summary>
34 /// 實際消息長度
35 /// </summary>
36 public int MessageContentLength
37 {
38 get { return messageContentLength; }
39 set { messageContentLength = value; }
40 }
41 #endregion
42
43 #region 實際消息內容
44 private byte[] messageContent = new byte[] { };
45 /// <summary>
46 /// 實際消息內容
47 /// </summary>
48 public byte[] MessageContent
49 {
50 get { return messageContent; }
51 set { messageContent = value; }
52 }
53 #endregion
54
55 #region 多余的Bytes
56 private byte[] duoYvBytes;
57 /// <summary>
58 /// 多余的Bytes
59 /// </summary>
60 public byte[] DuoYvBytes
61 {
62 get { return duoYvBytes; }
63 set { duoYvBytes = value; }
64 }
65
66 #endregion
67 #endregion
68
69 #region 構造函數兩個
70 public MessageXieYi()
71 {
72 //
73 }
74
75 public MessageXieYi(byte _xieYiFirstFlage, byte _xieYiSecondFlage, byte[] _messageContent)
76 {
77 xieYiFirstFlag = _xieYiFirstFlage;
78 xieYiFirstFlag = _xieYiSecondFlage;
79 messageContentLength = _messageContent.Length;
80 messageContent = _messageContent;
81 }
82 #endregion
83
84 #region MessageXieYi 轉換為 byte[]
85 /// <summary>
86 /// MessageXieYi 轉換為 byte[]
87 /// </summary>
88 /// <returns></returns>
89 public byte[] ToBytes()
90 {
91 byte[] _bytes; //自定義字節數組,用以裝載消息協議
92
93 using (MemoryStream memoryStream = new MemoryStream()) //創建內存流
94 {
95 BinaryWriter binaryWriter = new BinaryWriter(memoryStream); //以二進制寫入器往這個流里寫內容
96
97 binaryWriter.Write(xieYiFirstFlag); //寫入協議一級標志,占1個字節
98 binaryWriter.Write(xieYiSecondFlag); //寫入協議二級標志,占1個字節
99 binaryWriter.Write(messageContentLength); //寫入實際消息長度,占4個字節
100
101 if (messageContentLength > 0)
102 {
103 binaryWriter.Write(messageContent); //寫入實際消息內容
104 }
105
106 _bytes = memoryStream.ToArray(); //將流內容寫入自定義字節數組
107
108 binaryWriter.Close(); //關閉寫入器釋放資源
109 }
110
111 return _bytes; //返回填充好消息協議對象的自定義字節數組
112 }
113 #endregion
114
115 #region byte[] 轉換為 MessageXieYi
116 /// <summary>
117 /// byte[] 轉換為 MessageXieYi
118 /// </summary>
119 /// <param name="buffer">字節數組緩沖器。</param>
120 /// <returns></returns>
121 public static MessageXieYi FromBytes(byte[] buffer)
122 {
123 int bufferLength = buffer.Length;
124
125 MessageXieYi messageXieYi = new MessageXieYi();
126
127 using (MemoryStream memoryStream = new MemoryStream(buffer)) //將字節數組填充至內存流
128 {
129 BinaryReader binaryReader = new BinaryReader(memoryStream); //以二進制讀取器讀取該流內容
130
131 messageXieYi.xieYiFirstFlag = binaryReader.ReadByte(); //讀取協議一級標志,讀1個字節
132 messageXieYi.xieYiSecondFlag = binaryReader.ReadByte(); //讀取協議二級標志,讀1個字節
133 messageXieYi.messageContentLength = binaryReader.ReadInt32(); //讀取實際消息長度,讀4個字節
134
135 //如果【進來的Bytes長度】大於【一個完整的MessageXieYi長度】
136 if ((bufferLength - 6) > messageXieYi.messageContentLength)
137 {
138 messageXieYi.messageContent = binaryReader.ReadBytes(messageXieYi.messageContentLength); //讀取實際消息內容,從第7個字節開始讀
139 messageXieYi.duoYvBytes = binaryReader.ReadBytes(bufferLength - 6 - messageXieYi.messageContentLength);
140 }
141
142 //如果【進來的Bytes長度】等於【一個完整的MessageXieYi長度】
143 if ((bufferLength - 6) == messageXieYi.messageContentLength)
144 {
145 messageXieYi.messageContent = binaryReader.ReadBytes(messageXieYi.messageContentLength); //讀取實際消息內容,從第7個字節開始讀
146 }
147
148 binaryReader.Close(); //關閉二進制讀取器,是否資源
149 }
150
151 return messageXieYi; //返回消息協議對象
152 }
153 #endregion
154 }
 1     /// <summary>
2 /// 按照先后順序合並字節數組類
3 /// </summary>
4 public class CombineBytes
5 {
6 /// <summary>
7 /// 按照先后順序合並字節數組,並返回合並后的字節數組。
8 /// </summary>
9 /// <param name="firstBytes">第一個字節數組</param>
10 /// <param name="firstIndex">第一個字節數組的開始截取索引</param>
11 /// <param name="firstLength">第一個字節數組的截取長度</param>
12 /// <param name="secondBytes">第二個字節數組</param>
13 /// <param name="secondIndex">第二個字節數組的開始截取索引</param>
14 /// <param name="secondLength">第二個字節數組的截取長度</param>
15 /// <returns></returns>
16 public static byte[] ToArray(byte[] firstBytes, int firstIndex, int firstLength, byte[] secondBytes, int secondIndex, int secondLength)
17 {
18 using (MemoryStream ms = new MemoryStream())
19 {
20 BinaryWriter bw = new BinaryWriter(ms);
21 bw.Write(firstBytes, firstIndex, firstLength);
22 bw.Write(secondBytes, secondIndex, secondLength);
23
24 bw.Close();
25 bw.Dispose();
26
27 return ms.ToArray();
28 }
29 }
30 }
1 byte[] msgBytes = Encoding.Unicode.GetBytes("要發送的消息");
2 MessageXieYi msgXY = new MessageXieYi(0, 0, msgBytes);
3 networkStream.Write(msgXY.ToBytes(), 0, msgXY.ToBytes().Length);
 1         #region 線程執行體,接收消息
2 /// <summary>
3 /// 線程執行體,接收消息
4 /// </summary>
5 /// <param name="obj">傳遞給線程執行體的用戶名,用以與用戶通信</param>
6 private void ThreadReceive(object obj)
7 {
8 //通過用戶名找出已經保存在哈希表里的Socket
9 Socket savedSocket = hashtable_UserNameToSocket[obj] as Socket;
10
11 MessageXieYi msgXY = new MessageXieYi();
12
13 byte[] buffer = new byte[64];//定義一個大小為64的緩沖區
14 //byte[] receivedBytes = new byte[] { };
15 byte[] newBuffer = new byte[] { };//大小可變的緩存器
16
17 int receivedLength;
18 int availableLength;//沒什么實際意義,就是為了方便理解Socket傳輸機制
19
20 while (true)
21 {
22 try
23 {
24 buffer = new byte[64];
25
26 for (int i = 0; i < 10; i++)
27 {
28 availableLength = savedSocket.Available;
29
30 Console.WriteLine("【循環判斷有多少可讀Bytes】savedSocket.Available[" + i + "]=" + availableLength);//沒實際意義,就是來個直觀感受Socket的原理
31 }
32
33 Console.WriteLine("【可變緩存器大小】newBuffer.Length=" + newBuffer.Length);
34
35 receivedLength = savedSocket.Receive(buffer);
36
37 Console.WriteLine("【接收到數據】buffer.Length=" + receivedLength);
38
39 newBuffer = CombineBytes.ToArray(newBuffer, 0, newBuffer.Length, buffer, 0, receivedLength);
40
41 Console.WriteLine("【將接收到的數據追加在newBuffer后】newBuffer.Length=" + newBuffer.Length);
42
43 if (newBuffer.Length < 6)
44 {
45 Console.WriteLine("newBuffer.Length=" + newBuffer.Length + "< 6 \t -> \t continue");
46 continue;
47 }
48 else //newBuffer.Length >= 6
49 {
50 //取msgXY包頭部分
51 msgXY = MessageXieYi.FromBytes(newBuffer);
52 int firstFlag = msgXY.XieYiFirstFlag;
53 int secondFlag = msgXY.XieYiSecondFlag;
54 int msgContentLength = msgXY.MessageContentLength;
55
56
57 //判斷去掉msgXY包頭剩下的長度是否達到可以取包實質內容
58 while ((newBuffer.Length - 6) >= msgContentLength)
59 {
60 Console.WriteLine("【newBuffer去掉包頭的長度=" + (newBuffer.Length - 6) + "】>=【" + "包實質內容長度=" + msgContentLength + "");
61 msgXY = null;
62 msgXY = MessageXieYi.FromBytes(newBuffer);
63 Console.WriteLine("\n【拆包】=" + Encoding.Unicode.GetString(msgXY.MessageContent) + "\n");
64
65 newBuffer = msgXY.DuoYvBytes;
66 Console.WriteLine("【剩余的newBuffer】newBuffer.Length=" + newBuffer.Length);
67
68 if (newBuffer.Length >= 6)
69 {
70 msgXY = MessageXieYi.FromBytes(newBuffer);
71 firstFlag = msgXY.XieYiFirstFlag;
72 secondFlag = msgXY.XieYiSecondFlag;
73 msgContentLength = msgXY.MessageContentLength;
74 continue;
75 }
76 else
77 {
78 break;
79 }
80 }
81 }
82
83 availableLength = savedSocket.Available;
84 Console.WriteLine("savedSocket.Available=" + availableLength + "\n\n\n\n");
85
86 continue;
87 }
88 catch
89 {
90 //異常處理
91 }
92 }
93 }
94 #endregion

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com