Unity3D中簡單的C#異步Socket實現
簡單的異步Socket實現。.net框架自身提供了很完善的Socket底層。筆者在做Unity3D小東西的時候需要使用到Socket網絡通信。於是決定自己研究研究。
經過不懈努力。。O(∩_∩)O哈哈~。。自我誇獎一下。終於搞定了。SimpleSocket.cs
由於筆者本身並不是專業的C#程序員。O(∩_∩)O哈哈~。大神就可以直接忽視這篇文章了。顧名思義。哈哈簡單的Socket。給那些沒接觸的盆友參考借鑒下吧。服務社會了
注釋一: 本例在編碼上使用的是大端存貯,這個和C#本身是沖突的. 需要小端存儲的朋友可以將MiscUtil的EndianBitConverter修改成.net提供的BitConverter
注釋二: 筆者這里使用了Protobuf協議. 所以寫了一個工具在這里做轉換使用. 大家可以直接刪除Protobuf的那部分代碼.不會對本例產生任何影響
注釋三:筆者這里實現了一個基於長度的解碼器。用於避免粘包等問題。編碼時候的長度描述數字的默認為short類型(長度2字節)。解碼時候的長度描述數字默認為int類型(長度4字節)
上源碼:注釋的比較詳細了。不明白的可以問我。

1 using System; 2 using System.IO; 3 using System.Net; 4 using System.Net.Sockets; 5 using Google.ProtocolBuffers; 6 using MiscUtil.Conversion; 7 8 // +------------------------+ 9 // | Author : TinyZ | 10 // | Data : 2014-08-12 | 11 // |Ma-il : zou90512@126.com| 12 // +------------------------+ 13 // 注釋一: 本例在編碼上使用的是大端存貯,這個和C#本身是沖突的. 需要小端存儲的朋友可以將MiscUtil的EndianBitConverter修改成.net提供的BitConverter 14 // 注釋二: 筆者這里使用了Protobuf協議. 所以寫了一個工具在這里做轉換使用. 大家可以直接刪除Protobuf的那部分代碼.不會對本例產生任何影響 15 // 注釋三: 筆者這里實現了一個基於長度的解碼器。用於避免粘包等問題。編碼時候的長度描述數字的默認為short類型(長度2字節)。解碼時候的長度描述數字默認為int類型(長度4字節) 16 // 引用資料: 17 // Miscellaneous Utility Library類庫官網: http://www.yoda.arachsys.com/csharp/miscutil/ 18 19 namespace Assets.TinyZ.Class.SimpleNet 20 { 21 /// <summary> 22 /// 簡單的異步Socket實現. 用於Unity3D客戶端與JAVA服務端的數據通信. 23 /// 24 /// <br/><br/>方法:<br/> 25 /// Connect:用於連接遠程指定端口地址,連接成功后開啟消息接收監聽<br/> 26 /// OnSendMessage:用於發送字節流消息. 長度不能超過short[65535]的長度<br/> 27 /// <br/>事件:<br/> 28 /// ReceiveMessageCompleted: 用於回調. 返回接收到的根據基於長度的解碼器解碼之后獲取的數據[字節流] 29 /// 30 /// <br/><br/> 31 /// [*]完全不支持C#等小端(Little Endian)編碼 32 /// <br/><br/> 33 /// 服務器為JAVA開發。因此編碼均為 BigEndian編碼 34 /// 消息的字節流格式如下:<br/> 35 /// * +------------+-------------+ <br/> 36 /// * |消息程度描述| 內容 | <br/> 37 /// * | 0x04 | ABCD | <br/> 38 /// * +------------+-------------+ <br/> 39 /// 注釋: 消息頭為消息內容長度描述,后面是相應長度的字節內容. 40 /// 由於是大端存儲.所以無法使用C#提供的<see cref="BitConverter"/>進行解碼. 41 /// 本例使用的是網絡開源MiscUtil中的大端轉換器<see cref="EndianBitConverter"/> 42 /// <br/><br/> 43 /// </summary> 44 /// <example> 45 /// <code> 46 /// // Unity3D客戶端示例代碼如下: 47 /// var _simpleSocket = new SimpleSocket(); 48 /// _simpleSocket.Connect("127.0.0.1", 9003); 49 /// _simpleSocket.ReceiveMessageCompleted += (s, e) => 50 /// { 51 /// var rmc = e as ReceiveMessageCompletedEvent; 52 /// if (rmc == null) return; 53 /// var data = rmc.MessageData as byte[]; 54 /// if (data != null) 55 /// { 56 /// // 在Unity3D控制台輸出接收到的UTF-8格式字符串 57 /// Debug.Log(Encoding.UTF8.GetString(data)); 58 /// } 59 // _count++; 60 /// }; 61 /// 62 /// // Unity3D客戶端發送消息: 63 /// _simpleSocket.OnSendMessage(Encoding.UTF8.GetBytes("Hello World!")); 64 /// </code> 65 /// </example> 66 public class SimpleSocket 67 { 68 #region Construct 69 70 /// <summary> 71 /// Socket 72 /// </summary> 73 private readonly Socket _socket; 74 75 /// <summary> 76 /// SimpleSocket的構造函數 77 /// </summary> 78 public SimpleSocket() 79 { 80 _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 81 _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); 82 //_socket.Blocking = false; // ? 83 84 } 85 86 /// <summary> 87 /// 初始化Socket, 並設置幀長度 88 /// </summary> 89 /// <param name="encoderLengthFieldLength">編碼是消息長度數字的字節數長度. 1:表示1byte 2:表示2byte[Short類型] 4:表示4byte[int類型] 8:表示8byte[long類型]</param> 90 /// <param name="decoderLengthFieldLength">解碼時消息長度數字的字節數長度. 1:表示1byte 2:表示2byte[Short類型] 4:表示4byte[int類型] 8:表示8byte[long類型]</param> 91 public SimpleSocket(int encoderLengthFieldLength, int decoderLengthFieldLength) : this() 92 { 93 _encoderLengthFieldLength = encoderLengthFieldLength; 94 _decoderLengthFieldLength = decoderLengthFieldLength; 95 } 96 97 #endregion 98 99 100 #region Connect to remote host 101 102 /// <summary> 103 /// 是否連接狀態 104 /// </summary> 105 /// <see cref="Socket.Connected"/> 106 public bool Connected 107 { 108 get { return _socket != null && _socket.Connected; } 109 } 110 111 /// <summary> 112 /// 連接指定的遠程地址 113 /// </summary> 114 /// <param name="host">遠程地址</param> 115 /// <param name="port">端口</param> 116 public void Connect(string host, int port) 117 { 118 _socket.BeginConnect(host, port, OnConnectCallBack, this); 119 } 120 121 /// <summary> 122 /// 連接指定的遠程地址 123 /// </summary> 124 /// <param name="ipAddress">目標網絡協議ip地址</param> 125 /// <param name="port">目標端口</param> 126 /// 查看:<see cref="IPAddress"/> 127 public void Connect(IPAddress ipAddress, int port) 128 { 129 _socket.BeginConnect(ipAddress, port, OnConnectCallBack, this); 130 } 131 132 /// <summary> 133 /// 連接端點 134 /// </summary> 135 /// <param name="endPoint">端點, 標識網絡地址</param> 136 /// 查看:<see cref="EndPoint"/> 137 public void Connect(EndPoint endPoint) 138 { 139 _socket.BeginConnect(endPoint, OnConnectCallBack, this); 140 } 141 142 143 /// <summary> 144 /// 連接的回調函數 145 /// </summary> 146 /// <param name="ar"></param> 147 private void OnConnectCallBack(IAsyncResult ar) 148 { 149 if (!_socket.Connected) return; 150 _socket.EndConnect(ar); 151 StartReceive(); 152 } 153 154 #endregion 155 156 157 #region Send Message 158 159 /// <summary> 160 /// 編碼時長度描述數字的字節長度[default = 2 => 65535字節] 161 /// </summary> 162 private readonly int _encoderLengthFieldLength = 2; 163 164 /// <summary> 165 /// 發送消息 166 /// </summary> 167 /// <param name="data">要傳遞的消息內容[字節數組]</param> 168 public void OnSendMessage(byte[] data) 169 { 170 var stream = new MemoryStream(); 171 switch (_encoderLengthFieldLength) 172 { 173 case 1: 174 stream.Write(new[] { (byte)data.Length }, 0, 1); 175 break; 176 case 2: 177 stream.Write(EndianBitConverter.Big.GetBytes((short)data.Length), 0, 2); 178 break; 179 case 4: 180 stream.Write(EndianBitConverter.Big.GetBytes(data.Length), 0, 4); 181 break; 182 case 8: 183 stream.Write(EndianBitConverter.Big.GetBytes((long)data.Length), 0, 8); 184 break; 185 default: 186 throw new Exception("unsupported decoderLengthFieldLength: " + _encoderLengthFieldLength + " (expected: 1, 2, 3, 4, or 8)"); 187 } 188 stream.Write(data, 0, data.Length); 189 var all = stream.ToArray(); 190 stream.Close(); 191 _socket.BeginSend(all, 0, all.Length, SocketFlags.None, OnSendMessageComplete, all); 192 } 193 194 /// <summary> 195 /// 發送消息完成的回調函數 196 /// </summary> 197 /// <param name="ar"></param> 198 private void OnSendMessageComplete(IAsyncResult ar) 199 { 200 SocketError socketError; 201 _socket.EndSend(ar, out socketError); 202 if (socketError != SocketError.Success) 203 { 204 _socket.Disconnect(false); 205 throw new SocketException((int)socketError); 206 } 207 //Debug.Log("Send message successful !"); 208 } 209 210 211 #endregion 212 213 214 #region Receive Message 215 216 /// <summary> 217 /// the length of the length field. 長度字段的字節長度, 用於長度解碼 218 /// </summary> 219 private readonly int _decoderLengthFieldLength = 4; 220 221 /// <summary> 222 /// 事件消息接收完成 223 /// </summary> 224 public event EventHandler ReceiveMessageCompleted; 225 226 /// <summary> 227 /// 開始接收消息 228 /// </summary> 229 private void StartReceive() 230 { 231 if (!_socket.Connected) return; 232 var buffer = new byte[_decoderLengthFieldLength]; 233 _socket.BeginReceive(buffer, 0, _decoderLengthFieldLength, SocketFlags.None, OnReceiveFrameLengthComplete, buffer); 234 } 235 236 /// <summary> 237 /// 實現幀長度解碼.避免粘包等問題 238 /// </summary> 239 private void OnReceiveFrameLengthComplete(IAsyncResult ar) 240 { 241 var frameLength = (byte[]) ar.AsyncState; 242 // 幀長度 243 var length = EndianBitConverter.Big.ToInt32(frameLength, 0); 244 var data = new byte[length]; 245 _socket.BeginReceive(data, 0, length, SocketFlags.None, OnReceiveDataComplete, data); 246 } 247 248 /// <summary> 249 /// 數據接收完成的回調函數 250 /// </summary> 251 private void OnReceiveDataComplete(IAsyncResult ar) 252 { 253 _socket.EndReceive(ar); 254 var data = ar.AsyncState as byte[]; 255 // 觸發接收消息事件 256 if (ReceiveMessageCompleted != null) 257 { 258 ReceiveMessageCompleted(this, new ReceiveMessageCompletedEvent(data)); 259 } 260 StartReceive(); 261 } 262 263 #endregion 264 265 266 #region Protocol Buffers Utility 267 268 /// <summary> 269 /// 發送消息 270 /// </summary> 271 /// <typeparam name="T">IMessageLite的子類</typeparam> 272 /// <param name="generatedExtensionLite">消息的擴展信息</param> 273 /// <param name="messageLite">消息</param> 274 public void OnSendMessage<T>(GeneratedExtensionLite<ServerMessage, T> generatedExtensionLite, T messageLite) 275 where T : IMessageLite 276 { 277 var data = ConvertMessageToByteArray(generatedExtensionLite, messageLite); 278 OnSendMessage(data); 279 } 280 281 /// <summary> 282 /// Message轉換為byte[] 283 /// </summary> 284 /// <typeparam name="T"></typeparam> 285 /// <param name="generatedExtensionLite"></param> 286 /// <param name="messageLite"></param> 287 /// <returns></returns> 288 public static byte[] ConvertMessageToByteArray<T>(GeneratedExtensionLite<ServerMessage, T> generatedExtensionLite, T messageLite) where T : IMessageLite 289 { 290 ServerMessage.Builder builder = ServerMessage.CreateBuilder(); 291 builder.SetMsgId("" + generatedExtensionLite.Number); 292 builder.SetExtension(generatedExtensionLite, messageLite); 293 ServerMessage serverMessage = builder.Build(); 294 return serverMessage.ToByteArray(); 295 } 296 297 /// <summary> 298 /// byte[]轉換為Message 299 /// </summary> 300 /// <typeparam name="T"></typeparam> 301 /// <param name="data"></param> 302 /// <param name="generatedExtensionLite"></param> 303 /// <returns></returns> 304 public static IMessageLite ConvertByteArrayToMessage<T>(byte[] data, GeneratedExtensionLite<ServerMessage, T> generatedExtensionLite) where T : IMessageLite 305 { 306 ExtensionRegistry extensionRegistry = ExtensionRegistry.CreateInstance(); 307 extensionRegistry.Add(ProtobufMsgEnterGame.MsgEnterGame); 308 extensionRegistry.Add(ProtobufMsgLogin.MsgLogin); 309 extensionRegistry.Add(MsgBuyItem.msgBuyItem); 310 311 ServerMessage serverMessage = ServerMessage.ParseFrom(data, extensionRegistry); 312 return serverMessage.HasExtension(generatedExtensionLite) 313 ? serverMessage.GetExtension(generatedExtensionLite) 314 : default(T); 315 } 316 317 #endregion 318 } 319 320 #region Event 321 322 /// <summary> 323 /// 消息接收完成事件 324 /// </summary> 325 public class ReceiveMessageCompletedEvent : EventArgs 326 { 327 /// <summary> 328 /// 接收到的數據 329 /// </summary> 330 private readonly object _data; 331 332 public ReceiveMessageCompletedEvent(object data) 333 { 334 _data = data; 335 } 336 337 /// <summary> 338 /// 消息數據 339 /// </summary> 340 public object MessageData 341 { 342 get { return _data; } 343 } 344 } 345 346 #endregion 347 }
ps:
由於當初寫這個代碼的時候比較粗糙。筆者覺得新開一章發布版本1.1的。優化了一下以前的代碼。推薦使用新的
直接上連接:
簡單的異步Socket實現——SimpleSocket_V1.1
--------------------------------------------------------------分割線-- 打個小廣告-----------------------------------------------------------
女裝飾品店:http://aoleitaisen.taobao.com
歡迎轉載,轉載必須保留
我的郵箱:zou90512@126.com 博客地址: http://www.cnblogs.com/zou90512
否則視為侵權