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