简单的异步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哈哈~

 

posted @ 2014-08-20 13:50  Tiny&zzh  阅读(5254)  评论(1编辑  收藏  举报