簡單的異步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哈哈~