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