Unity3D中簡單的C#異步Socket實現


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 }
View Code

 

 

ps:

由於當初寫這個代碼的時候比較粗糙。筆者覺得新開一章發布版本1.1的。優化了一下以前的代碼。推薦使用新的

直接上連接:

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

 

 

 

 

 

 

--------------------------------------------------------------分割線-- 打個小廣告-----------------------------------------------------------

女裝飾品店:http://aoleitaisen.taobao.com 

 

歡迎轉載,轉載必須保留

我的郵箱:zou90512@126.com 博客地址: http://www.cnblogs.com/zou90512

否則視為侵權

 


免責聲明!

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



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