簡單的異步Socket實現——SimpleSocket_V1.1


 簡單的異步Socket實現——SimpleSocket_V1.1

 

  筆者在前段時間的博客中分享了一段簡單的異步.net的Socket實現。由於是筆者自己測試使用的。寫的很粗糙。很簡陋。於是花了點時間自己去完善了一下

  舊版本的SimpleSocket大致實現了異步socket的全部功能。但是代碼擴展性較差。對很多事件都沒有做出相對應的處理。在1.1版本進行了相對應的維護和更新。

  

  SimpleSocket(簡稱:SS)是一個簡單的.net原生的Socket簡單封裝。實現了異步操作。SS利用長度的解碼器來解決和避免粘包等網絡問題。

   新增特性:

    1.增加對.net原生的小端存儲支持. 通過define是否是大端編碼及可以切換存儲形式

    2.獨立的Protocol Buffers編解碼器工具. 通過define機可以開啟關閉是否需要Protobuf支持

    3.對Socket的部分管理。例如關閉和斷開連接

    4.增加消息發送完成事件,連接建立完成事件,消息接收完成事件

 

下面上代碼: SimpleSocket.cs  1.1版本

  1 // +------------------------+
  2 // |    Author : TinyZ      |
  3 // |   Data : 2014-08-20    |
  4 // |Ma-il : zou90512@126.com|
  5 // |     Version : 1.1      |
  6 // +------------------------+
  7 // 注釋: 筆者這里實現了一個基於長度的解碼器。用於避免粘包等問題。編碼時候的長度描述數字的默認為short類型(長度2字節)。解碼時候的長度描述數字默認為int類型(長度4字節)
  8 
  9 // GOOGLE_PROTOCOL_BUFFERS : 
 10 //      是否支持Google的Protocol Buffers. 作者自己使用的.
 11 //      Define request Google protocol buffers
 12 //      Example: #define GOOGLE_PROTOCOL_BUFFERS
 13 //      相關資料: 
 14 //      [推薦]protobuf-csharp-port:https://code.google.com/p/protobuf-csharp-port/ . PB最好,最完整的C#實現.使用.net 20版本即可以完美支持Unity3D 4.3x以上版本
 15 //      protobuf-net: https://code.google.com/p/protobuf-net/ 
 16 //#define GOOGLE_PROTOCOL_BUFFERS
 17 //
 18 // BIG_ENDIANESS :
 19 //      是否是大端存儲. 是=>使用大端存儲,將使用Misc類庫提供的大端存儲工具EndianBitConverter. 否=>使用.Net提供的BitConverter
 20 //      Define is socket endianess is big-endianess(大端) . If endianess is Big-endianess use EndianBitConverter , else use BitConverter
 21 //      相關資料:
 22 //      Miscellaneous Utility Library類庫官網: http://www.yoda.arachsys.com/csharp/miscutil/
 23 #define BIG_ENDIANESS
 24 
 25 using System;
 26 using System.IO;
 27 using System.Net;
 28 using System.Net.Sockets;
 29 #if BIG_ENDIANESS
 30 using MiscUtil.Conversion;
 31 #endif
 32 #if GOOGLE_PROTOCOL_BUFFERS
 33 using Google.ProtocolBuffers;
 34 using Assets.TinyZ.Socket.Codec;
 35 #endif
 36 
 37 namespace Assets.TinyZ.Socket
 38 {
 39     /// <summary>
 40     /// 簡單的異步Socket實現. 用於Unity3D客戶端與JAVA服務端的數據通信.
 41     /// 
 42     /// <br/><br/>方法:<br/>
 43     /// Connect:用於連接遠程指定端口地址,連接成功后開啟消息接收監聽<br/>
 44     /// OnSendMessage:用於發送字節流消息. 長度不能超過short[65535]的長度<br/>
 45     /// <br/>事件:<br/>
 46     /// ReceiveMessageCompleted: 用於回調. 返回接收到的根據基於長度的解碼器解碼之后獲取的數據[字節流]
 47     /// SendMessageCompleted:   用於回調. 當消息發送完成時
 48     /// ConnectCompleted: 用於回調. 當成功連接到遠程網絡地址后調用
 49     /// 
 50     /// <br/><br/>
 51     /// 服務器為JAVA開發。因此編碼均為 BigEndian編碼
 52     /// 消息的字節流格式如下:<br/>
 53     ///     * +------------+-------------+ <br/>
 54     ///     * |消息程度描述|  內容       | <br/>
 55     ///     * |    0x04    | ABCD        | <br/>
 56     ///     * +------------+-------------+ <br/>
 57     /// 注釋: 消息頭為消息內容長度描述,后面是相應長度的字節內容. 
 58     /// <br/><br/>
 59     /// </summary>
 60     /// <example>
 61     /// <code>
 62     /// // Unity3D客戶端示例代碼如下:
 63     /// var _simpleSocket = new SimpleSocket();
 64     /// _simpleSocket.Connect("127.0.0.1", 9003);
 65     /// _simpleSocket.ReceiveMessageCompleted += (s, e) =>
 66     /// {
 67     ///     var rmc = e as SocketEventArgs;
 68     ///     if (rmc == null) return;
 69     ///     var data = rmc.Data as byte[];
 70     ///     if (data != null)
 71     ///     {
 72     ///         // 在Unity3D控制台輸出接收到的UTF-8格式字符串 
 73     ///         Debug.Log(Encoding.UTF8.GetString(data));
 74     ///     }
 75     //      _count++;
 76     /// };
 77     /// 
 78     /// // Unity3D客戶端發送消息:
 79     /// _simpleSocket.OnSendMessage(Encoding.UTF8.GetBytes("Hello World!"));
 80     /// </code>
 81     /// </example>
 82     public class SimpleSocket
 83     {
 84         #region Construct
 85 
 86         /// <summary>
 87         /// Socket
 88         /// </summary>
 89         private readonly System.Net.Sockets.Socket _socket;
 90 
 91         /// <summary>
 92         /// SimpleSocket的構造函數
 93         /// </summary>
 94         public SimpleSocket()
 95         {
 96             _socket = new System.Net.Sockets.Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 97             _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
 98             //_socket.Blocking = false; // ?
 99         }
100 
101         /// <summary>
102         /// 初始化Socket, 並設置幀長度
103         /// </summary>
104         /// <param name="encoderLengthFieldLength">編碼是消息長度數字的字節數長度. 1:表示1byte  2:表示2byte[Short類型] 4:表示4byte[int類型] 8:表示8byte[long類型]</param>
105         /// <param name="decoderLengthFieldLength">解碼時消息長度數字的字節數長度. 1:表示1byte  2:表示2byte[Short類型] 4:表示4byte[int類型] 8:表示8byte[long類型]</param>
106         public SimpleSocket(int encoderLengthFieldLength, int decoderLengthFieldLength) : this()
107         {
108             _encoderLengthFieldLength = encoderLengthFieldLength;
109             _decoderLengthFieldLength = decoderLengthFieldLength;
110         }
111 
112         #endregion
113 
114 
115         #region Connect to remote host
116 
117         /// <summary>
118         /// 連接遠程地址完成事件
119         /// </summary>
120         public event EventHandler<SocketEventArgs> ConnectCompleted;
121 
122         /// <summary>
123         /// 是否連接狀態
124         /// </summary>
125         /// <see cref="Socket.Connected"/>
126         public bool Connected
127         {
128             get { return _socket != null && _socket.Connected; }
129         }
130 
131         /// <summary>
132         /// 連接指定的遠程地址
133         /// </summary>
134         /// <param name="host">遠程地址</param>
135         /// <param name="port">端口</param>
136         public void Connect(string host, int port)
137         {
138             _socket.BeginConnect(host, port, OnConnectCallBack, this);
139         }
140 
141         /// <summary>
142         /// 連接指定的遠程地址
143         /// </summary>
144         /// <param name="ipAddress">目標網絡協議ip地址</param>
145         /// <param name="port">目標端口</param>
146         /// 查看:<see cref="IPAddress"/>
147         public void Connect(IPAddress ipAddress, int port)
148         {
149             _socket.BeginConnect(ipAddress, port, OnConnectCallBack, this);
150         }
151 
152         /// <summary>
153         /// 連接端點
154         /// </summary>
155         /// <param name="endPoint">端點, 標識網絡地址</param>
156         /// 查看:<see cref="EndPoint"/>
157         public void Connect(EndPoint endPoint)
158         {
159             _socket.BeginConnect(endPoint, OnConnectCallBack, this);
160         }
161 
162         /// <summary>
163         /// 連接的回調函數
164         /// </summary>
165         /// <param name="ar"></param>
166         private void OnConnectCallBack(IAsyncResult ar)
167         {
168             if (!_socket.Connected) return;
169             _socket.EndConnect(ar);
170             if (ConnectCompleted != null)
171             {
172                 ConnectCompleted(this, new SocketEventArgs());
173             }
174             StartReceive();
175         }
176 
177         #endregion
178 
179 
180         #region Send Message
181 
182         /// <summary>
183         /// 發送消息完成
184         /// </summary>
185         public event EventHandler<SocketEventArgs> SendMessageCompleted;
186 
187         /// <summary>
188         /// 編碼時長度描述數字的字節長度[default = 2 => 65535字節]
189         /// </summary>
190         private readonly int _encoderLengthFieldLength = 2;
191 
192         /// <summary>
193         /// 發送消息
194         /// </summary>
195         /// <param name="data">要傳遞的消息內容[字節數組]</param>
196         public void OnSendMessage(byte[] data)
197         {
198             var stream = new MemoryStream();
199             switch (_encoderLengthFieldLength)
200             {
201                 case 1:
202                     stream.Write(new[] {(byte) data.Length}, 0, 1);
203                     break;
204 #if BIG_ENDIANESS
205                 case 2:
206                     stream.Write(EndianBitConverter.Big.GetBytes((short) data.Length), 0, 2);
207                     break;
208                 case 4:
209                     stream.Write(EndianBitConverter.Big.GetBytes(data.Length), 0, 4);
210                     break;
211                 case 8:
212                     stream.Write(EndianBitConverter.Big.GetBytes((long) data.Length), 0, 8);
213                     break;
214 #else
215                 case 2:
216                     stream.Write(BitConverter.GetBytes((short) data.Length), 0, 2);
217                     break;
218                 case 4:
219                     stream.Write(BitConverter.GetBytes(data.Length), 0, 4);
220                     break;
221                 case 8:
222                     stream.Write(BitConverter.GetBytes((long) data.Length), 0, 8);
223                     break;
224 #endif
225 
226                 default:
227                     throw new Exception("unsupported decoderLengthFieldLength: " + _encoderLengthFieldLength +
228                                         " (expected: 1, 2, 3, 4, or 8)");
229             }
230             stream.Write(data, 0, data.Length);
231             var all = stream.ToArray();
232             stream.Close();
233             _socket.BeginSend(all, 0, all.Length, SocketFlags.None, OnSendMessageComplete, all);
234         }
235 
236         /// <summary>
237         /// 發送消息完成的回調函數
238         /// </summary>
239         /// <param name="ar"></param>
240         private void OnSendMessageComplete(IAsyncResult ar)
241         {
242             var data = ar.AsyncState as byte[];
243             SocketError socketError;
244             _socket.EndSend(ar, out socketError);
245             if (socketError != SocketError.Success)
246             {
247                 _socket.Disconnect(false);
248                 throw new SocketException((int)socketError);
249             }
250             if (SendMessageCompleted != null)
251             {
252                 SendMessageCompleted(this, new SocketEventArgs(data));
253             }
254             //Debug.Log("Send message successful !");
255         }
256 
257         #endregion
258 
259 
260         #region Receive Message
261 
262         /// <summary>
263         /// the length of the length field. 長度字段的字節長度, 用於長度解碼 
264         /// </summary>
265         private readonly int _decoderLengthFieldLength = 4;
266 
267         /// <summary>
268         /// 事件消息接收完成
269         /// </summary>
270         public event EventHandler<SocketEventArgs> ReceiveMessageCompleted;
271 
272         /// <summary>
273         /// 開始接收消息
274         /// </summary>
275         private void StartReceive()
276         {
277             if (!_socket.Connected) return;
278             var buffer = new byte[_decoderLengthFieldLength];
279             _socket.BeginReceive(buffer, 0, _decoderLengthFieldLength, SocketFlags.None, OnReceiveFrameLengthComplete, buffer);
280         }
281 
282         /// <summary>
283         /// 實現幀長度解碼.避免粘包等問題
284         /// </summary>
285         private void OnReceiveFrameLengthComplete(IAsyncResult ar)
286         {
287             var frameLength = (byte[]) ar.AsyncState;
288             // 幀長度 
289 #if BIG_ENDIANESS
290             var length = EndianBitConverter.Big.ToInt32(frameLength, 0);
291 #else
292             var length = BitConverter.ToInt32(frameLength, 0);
293 #endif
294             var data = new byte[length];
295             _socket.BeginReceive(data, 0, length, SocketFlags.None, OnReceiveDataComplete, data);
296         }
297 
298         /// <summary>
299         /// 數據接收完成的回調函數
300         /// </summary>
301         private void OnReceiveDataComplete(IAsyncResult ar)
302         {
303             _socket.EndReceive(ar);
304             var data = ar.AsyncState as byte[];
305             // 觸發接收消息事件
306             if (ReceiveMessageCompleted != null)
307             {
308                 ReceiveMessageCompleted(this, new SocketEventArgs(data));
309             }
310             StartReceive();
311         }
312 
313         #endregion
314 
315 
316         #region Close and Disconnect
317 
318         /// <summary>
319         /// 關閉 <see cref="Socket"/> 連接並釋放所有關聯的資源
320         /// </summary>
321         public void Close()
322         {
323             _socket.Close();
324         }
325 
326         /// <summary>
327         /// 關閉套接字連接並允許重用套接字。
328         /// </summary>
329         /// <param name="reuseSocket">如果關閉當前連接后可以重用此套接字,則為 true;否則為 false。 </param>
330         public void Disconnect(bool reuseSocket)
331         {
332             _socket.Disconnect(reuseSocket);
333         }
334 
335         #endregion
336 
337 
338         #region Protocol Buffers Utility [Request : Google Protocol Buffers version 2.5]
339 
340 #if GOOGLE_PROTOCOL_BUFFERS
341 
342         /// <summary>
343         /// 發送消息
344         /// </summary>
345         /// <typeparam name="T">IMessageLite的子類</typeparam>
346         /// <param name="generatedExtensionLite">消息的擴展信息</param>
347         /// <param name="messageLite">消息</param>
348         public void OnSendMessage<T>(GeneratedExtensionLite<ServerMessage, T> generatedExtensionLite, T messageLite)
349             where T : IMessageLite
350         {
351             var data = ProtobufEncoder.ConvertMessageToByteArray(generatedExtensionLite, messageLite);
352             OnSendMessage(data);
353         }
354 
355 #endif
356 
357         #endregion
358     }
359 
360     #region Event
361 
362     /// <summary>
363     /// Simple socket event args
364     /// </summary>
365     public class SocketEventArgs : EventArgs
366     {
367 
368         public SocketEventArgs()
369         {
370         }
371 
372         public SocketEventArgs(byte[] data) : this()
373         {
374             Data = data;
375         }
376 
377         /// <summary>
378         /// 相關的數據
379         /// </summary>
380         public byte[] Data { get; private set; }
381 
382     }
383 
384     #endregion
385 
386 }

 

 下面是筆者自己使用的Protocol Buffers的編解碼器。類庫需求:protobuf-csharp-port 523版本。有興趣的可以自己試試。

  解碼器是通用的解碼器.是仿照Netty的ProtobufDecoder解碼器C#實現。

 1 //     +------------------------+
 2 //     |    Author : TinyZ      |
 3 //     |   Data : 2014-08-20    |
 4 //     |Ma-il : zou90512@126.com|
 5 //     +------------------------+
 6 //      引用資料: 
 7 //      [推薦]protobuf-csharp-port:https://code.google.com/p/protobuf-csharp-port/ . PB最好,最完整的C#實現.使用.net 20版本即可以完美支持Unity3D 4.3x以上版本
 8 //      protobuf-net: https://code.google.com/p/protobuf-net/ 
 9 
10 
11 using System;
12 using Google.ProtocolBuffers;
13 
14 namespace Assets.TinyZ.Socket.Codec
15 {
16     /// <summary>
17     /// Protocol Buffers 解碼器
18     /// </summary>
19     public class ProtobufDecoder
20     {
21         private readonly IMessageLite _prototype;
22 
23         /// <summary>
24         /// 擴展消息注冊
25         /// </summary>
26         private readonly ExtensionRegistry _extensionRegistry;
27 
28         public ProtobufDecoder(IMessageLite prototype)
29         {
30             _prototype = prototype.WeakDefaultInstanceForType;
31         }
32 
33         public ProtobufDecoder(IMessageLite prototype, ExtensionRegistry extensionRegistry)
34             : this(prototype)
35         {
36             _extensionRegistry = extensionRegistry;
37         }
38 
39         /// <summary>
40         /// 注冊擴展
41         /// </summary>
42         /// <param name="extension">protobuf擴展消息</param>
43         public void RegisterExtension(IGeneratedExtensionLite extension)
44         {
45             if (_extensionRegistry == null)
46             {
47                 throw new Exception("ExtensionRegistry must using InitProtobufDecoder method to initialize. ");
48             }
49             _extensionRegistry.Add(extension);
50         }
51 
52         /// <summary>
53         /// 解碼
54         /// </summary>
55         /// <param name="data">protobuf編碼字節數組</param>
56         /// <returns>返回解碼之后的消息</returns>
57         public IMessageLite Decode(byte[] data)
58         {
59             if (_prototype == null)
60             {
61                 throw new Exception("_prototype must using InitProtobufDecoder method to initialize.");
62             }
63             IMessageLite message;
64             if (_extensionRegistry == null)
65             {
66                 message = (_prototype.WeakCreateBuilderForType().WeakMergeFrom(ByteString.CopyFrom(data))).WeakBuild();
67             }
68             else
69             {
70                 message =
71                     (_prototype.WeakCreateBuilderForType().WeakMergeFrom(ByteString.CopyFrom(data), _extensionRegistry))
72                         .WeakBuild();
73             }
74             if (message == null)
75             {
76                 throw new Exception("Decode message failed");
77             }
78             return message;
79         }
80     }
81 }

 

  編碼器。方法ConvertMessageToByteArray是筆者自己寫來測試使用的。大家可以無視之

  

 1 //     +------------------------+
 2 //     |    Author : TinyZ      |
 3 //     |   Data : 2014-08-20    |
 4 //     |Ma-il : zou90512@126.com|
 5 //     +------------------------+
 6 //      引用資料: 
 7 //      [推薦]protobuf-csharp-port:https://code.google.com/p/protobuf-csharp-port/ . PB最好,最完整的C#實現.使用.net 20版本即可以完美支持Unity3D 4.3x以上版本
 8 //      protobuf-net: https://code.google.com/p/protobuf-net/ 
 9 
10 using Google.ProtocolBuffers;
11 
12 namespace Assets.TinyZ.Socket.Codec
13 {
14     /// <summary>
15     /// Protocol Buffers 編碼器
16     /// </summary>
17     public class ProtobufEncoder
18     {
19         /// <summary>
20         /// [自用]Message轉換為byte[]
21         /// </summary>
22         /// <typeparam name="T"></typeparam>
23         /// <param name="generatedExtensionLite"></param>
24         /// <param name="messageLite"></param>
25         /// <returns></returns>
26         public static byte[] ConvertMessageToByteArray<T>(GeneratedExtensionLite<ServerMessage, T> generatedExtensionLite, T messageLite) where T : IMessageLite
27         {
28             ServerMessage.Builder builder = ServerMessage.CreateBuilder();
29             builder.SetMsgId("" + generatedExtensionLite.Number);
30             builder.SetExtension(generatedExtensionLite, messageLite);
31             ServerMessage serverMessage = builder.Build();
32             return serverMessage.ToByteArray();
33         }
34 
35         public static byte[] Encode(IMessageLite messageLite)
36         {
37             return messageLite.ToByteArray();
38         }
39 
40         public static byte[] Encode(IBuilder builder)
41         {
42             return builder.WeakBuild().ToByteArray();
43         }
44     }
45 }

 

 源碼下載地址:http://pan.baidu.com/s/1pJz7Tv9   

ps:因為筆者最近使用Unity3D。所以示例源碼是Unity3D的。假如你沒有安裝過Unity3D。也沒關系。筆者同時提供了zip壓縮包。包含cs源文件

 

作者:TinyZ
出處:http://www.cnblogs.com/zou90512/
關於作者:努力學習,天天向上。不斷探索學習,提升自身價值。記錄經驗分享。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接
如有問題,可以通過 zou90512@126.com 聯系我,非常感謝。
筆者網店: http://aoleitaisen.taobao.com. 歡迎圍觀。O(∩_∩)O哈哈~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM