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. #region Connect to remote host
  100.  
  101. /// <summary>
  102. /// 是否连接状态
  103. /// </summary>
  104. /// <see cref="Socket.Connected"/>
  105. public bool Connected
  106. {
  107. get { return _socket != null && _socket.Connected; }
  108. }
  109.  
  110. /// <summary>
  111. /// 连接指定的远程地址
  112. /// </summary>
  113. /// <param name="host">远程地址</param>
  114. /// <param name="port">端口</param>
  115. public void Connect(string host, int port)
  116. {
  117. _socket.BeginConnect(host, port, OnConnectCallBack, this);
  118. }
  119.  
  120. /// <summary>
  121. /// 连接指定的远程地址
  122. /// </summary>
  123. /// <param name="ipAddress">目标网络协议ip地址</param>
  124. /// <param name="port">目标端口</param>
  125. /// 查看:<see cref="IPAddress"/>
  126. public void Connect(IPAddress ipAddress, int port)
  127. {
  128. _socket.BeginConnect(ipAddress, port, OnConnectCallBack, this);
  129. }
  130.  
  131. /// <summary>
  132. /// 连接端点
  133. /// </summary>
  134. /// <param name="endPoint">端点, 标识网络地址</param>
  135. /// 查看:<see cref="EndPoint"/>
  136. public void Connect(EndPoint endPoint)
  137. {
  138. _socket.BeginConnect(endPoint, OnConnectCallBack, this);
  139. }
  140.  
  141. /// <summary>
  142. /// 连接的回调函数
  143. /// </summary>
  144. /// <param name="ar"></param>
  145. private void OnConnectCallBack(IAsyncResult ar)
  146. {
  147. if (!_socket.Connected) return;
  148. _socket.EndConnect(ar);
  149. StartReceive();
  150. }
  151.  
  152. #endregion
  153.  
  154. #region Send Message
  155.  
  156. /// <summary>
  157. /// 编码时长度描述数字的字节长度[default = 2 => 65535字节]
  158. /// </summary>
  159. private readonly int _encoderLengthFieldLength = ;
  160.  
  161. /// <summary>
  162. /// 发送消息
  163. /// </summary>
  164. /// <param name="data">要传递的消息内容[字节数组]</param>
  165. public void OnSendMessage(byte[] data)
  166. {
  167. var stream = new MemoryStream();
  168. switch (_encoderLengthFieldLength)
  169. {
  170. case :
  171. stream.Write(new[] { (byte)data.Length }, , );
  172. break;
  173. case :
  174. stream.Write(EndianBitConverter.Big.GetBytes((short)data.Length), , );
  175. break;
  176. case :
  177. stream.Write(EndianBitConverter.Big.GetBytes(data.Length), , );
  178. break;
  179. case :
  180. stream.Write(EndianBitConverter.Big.GetBytes((long)data.Length), , );
  181. break;
  182. default:
  183. throw new Exception("unsupported decoderLengthFieldLength: " + _encoderLengthFieldLength + " (expected: 1, 2, 3, 4, or 8)");
  184. }
  185. stream.Write(data, , data.Length);
  186. var all = stream.ToArray();
  187. stream.Close();
  188. _socket.BeginSend(all, , all.Length, SocketFlags.None, OnSendMessageComplete, all);
  189. }
  190.  
  191. /// <summary>
  192. /// 发送消息完成的回调函数
  193. /// </summary>
  194. /// <param name="ar"></param>
  195. private void OnSendMessageComplete(IAsyncResult ar)
  196. {
  197. SocketError socketError;
  198. _socket.EndSend(ar, out socketError);
  199. if (socketError != SocketError.Success)
  200. {
  201. _socket.Disconnect(false);
  202. throw new SocketException((int)socketError);
  203. }
  204. //Debug.Log("Send message successful !");
  205. }
  206.  
  207. #endregion
  208.  
  209. #region Receive Message
  210.  
  211. /// <summary>
  212. /// the length of the length field. 长度字段的字节长度, 用于长度解码
  213. /// </summary>
  214. private readonly int _decoderLengthFieldLength = ;
  215.  
  216. /// <summary>
  217. /// 事件消息接收完成
  218. /// </summary>
  219. public event EventHandler ReceiveMessageCompleted;
  220.  
  221. /// <summary>
  222. /// 开始接收消息
  223. /// </summary>
  224. private void StartReceive()
  225. {
  226. if (!_socket.Connected) return;
  227. var buffer = new byte[_decoderLengthFieldLength];
  228. _socket.BeginReceive(buffer, , _decoderLengthFieldLength, SocketFlags.None, OnReceiveFrameLengthComplete, buffer);
  229. }
  230.  
  231. /// <summary>
  232. /// 实现帧长度解码.避免粘包等问题
  233. /// </summary>
  234. private void OnReceiveFrameLengthComplete(IAsyncResult ar)
  235. {
  236. var frameLength = (byte[]) ar.AsyncState;
  237. // 帧长度
  238. var length = EndianBitConverter.Big.ToInt32(frameLength, );
  239. var data = new byte[length];
  240. _socket.BeginReceive(data, , length, SocketFlags.None, OnReceiveDataComplete, data);
  241. }
  242.  
  243. /// <summary>
  244. /// 数据接收完成的回调函数
  245. /// </summary>
  246. private void OnReceiveDataComplete(IAsyncResult ar)
  247. {
  248. _socket.EndReceive(ar);
  249. var data = ar.AsyncState as byte[];
  250. // 触发接收消息事件
  251. if (ReceiveMessageCompleted != null)
  252. {
  253. ReceiveMessageCompleted(this, new ReceiveMessageCompletedEvent(data));
  254. }
  255. StartReceive();
  256. }
  257.  
  258. #endregion
  259.  
  260. #region Protocol Buffers Utility
  261.  
  262. /// <summary>
  263. /// 发送消息
  264. /// </summary>
  265. /// <typeparam name="T">IMessageLite的子类</typeparam>
  266. /// <param name="generatedExtensionLite">消息的扩展信息</param>
  267. /// <param name="messageLite">消息</param>
  268. public void OnSendMessage<T>(GeneratedExtensionLite<ServerMessage, T> generatedExtensionLite, T messageLite)
  269. where T : IMessageLite
  270. {
  271. var data = ConvertMessageToByteArray(generatedExtensionLite, messageLite);
  272. OnSendMessage(data);
  273. }
  274.  
  275. /// <summary>
  276. /// Message转换为byte[]
  277. /// </summary>
  278. /// <typeparam name="T"></typeparam>
  279. /// <param name="generatedExtensionLite"></param>
  280. /// <param name="messageLite"></param>
  281. /// <returns></returns>
  282. public static byte[] ConvertMessageToByteArray<T>(GeneratedExtensionLite<ServerMessage, T> generatedExtensionLite, T messageLite) where T : IMessageLite
  283. {
  284. ServerMessage.Builder builder = ServerMessage.CreateBuilder();
  285. builder.SetMsgId("" + generatedExtensionLite.Number);
  286. builder.SetExtension(generatedExtensionLite, messageLite);
  287. ServerMessage serverMessage = builder.Build();
  288. return serverMessage.ToByteArray();
  289. }
  290.  
  291. /// <summary>
  292. /// byte[]转换为Message
  293. /// </summary>
  294. /// <typeparam name="T"></typeparam>
  295. /// <param name="data"></param>
  296. /// <param name="generatedExtensionLite"></param>
  297. /// <returns></returns>
  298. public static IMessageLite ConvertByteArrayToMessage<T>(byte[] data, GeneratedExtensionLite<ServerMessage, T> generatedExtensionLite) where T : IMessageLite
  299. {
  300. ExtensionRegistry extensionRegistry = ExtensionRegistry.CreateInstance();
  301. extensionRegistry.Add(ProtobufMsgEnterGame.MsgEnterGame);
  302. extensionRegistry.Add(ProtobufMsgLogin.MsgLogin);
  303. extensionRegistry.Add(MsgBuyItem.msgBuyItem);
  304.  
  305. ServerMessage serverMessage = ServerMessage.ParseFrom(data, extensionRegistry);
  306. return serverMessage.HasExtension(generatedExtensionLite)
  307. ? serverMessage.GetExtension(generatedExtensionLite)
  308. : default(T);
  309. }
  310.  
  311. #endregion
  312. }
  313.  
  314. #region Event
  315.  
  316. /// <summary>
  317. /// 消息接收完成事件
  318. /// </summary>
  319. public class ReceiveMessageCompletedEvent : EventArgs
  320. {
  321. /// <summary>
  322. /// 接收到的数据
  323. /// </summary>
  324. private readonly object _data;
  325.  
  326. public ReceiveMessageCompletedEvent(object data)
  327. {
  328. _data = data;
  329. }
  330.  
  331. /// <summary>
  332. /// 消息数据
  333. /// </summary>
  334. public object MessageData
  335. {
  336. get { return _data; }
  337. }
  338. }
  339.  
  340. #endregion
  341. }

ps:

由于当初写这个代码的时候比较粗糙。笔者觉得新开一章发布版本1.1的。优化了一下以前的代码。推荐使用新的

直接上连接:

简单的异步Socket实现——SimpleSocket_V1.1

--------------------------------------------------------------分割线-- 打个小广告-----------------------------------------------------------

女装饰品店:http://aoleitaisen.taobao.com

欢迎转载,转载必须保留

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

否则视为侵权

Unity3D中简单的C#异步Socket实现的更多相关文章

  1. 【Unity Shaders】Reflecting Your World —— Unity3D中简单的Cubemap反射

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  2. GJM :异步Socket [转载]

    原帖地址:http://blog.csdn.net/awinye/article/details/537264 原文作者:Awinye 目录(?)[-] 转载请原作者联系 Overview of So ...

  3. Unity3D中的常用方法

    备注:文中所使用的this均指脚本所依附的对象 1.移动(用Translate方法进行移动) ; //移动速度 this.transform.Translate(Vector3.down * Time ...

  4. 简单的异步Socket实现——SimpleSocket_V1.1

    简单的异步Socket实现——SimpleSocket_V1.1 笔者在前段时间的博客中分享了一段简单的异步.net的Socket实现.由于是笔者自己测试使用的.写的很粗糙.很简陋.于是花了点时间自己 ...

  5. unity3D中使用Socket进行数据通信(一)

    公司今年3D产品的工作中心主要集中在提高产品深度上,通过对竞争产品的分析,发现我们的缺陷在于多人在线与后台管理部分,多人在线使用unity自带的Network能够搞定,后台部分前段时间主要研究了下Sq ...

  6. [Unity Socket]在Unity中如何实现异步Socket通信技术

    在刚刚开发Unity项目的过程中,需要用到即时通信功能来完成服务器与客户端自定义的数据结构封装. 现在将部分主要功能的实现代码抽取出来实现了可以异步Socket请求的技术Demo. 客户端脚本Clie ...

  7. 《Unity 3D游戏客户端基础框架》多线程异步 Socket 框架构建

    引言: 之前写过一个 demo 案例大致讲解了 Socket 通信的过程,并和自建的服务器完成连接和简单的数据通信,详细的内容可以查看 Unity3D -- Socket通信(C#).但是在实际项目应 ...

  8. Python简易聊天工具-基于异步Socket通信

    继续学习Python中,最近看书<Python基础教程>中的虚拟茶话会项目,觉得很有意思,自己敲了一遍,受益匪浅,同时记录一下. 主要用到异步socket服务客户端和服务器模块asynco ...

  9. 项目笔记---C#异步Socket示例

    概要 在C#领域或者说.net通信领域中有着众多的解决方案,WCF,HttpRequest,WebAPI,Remoting,socket等技术.这些技术都有着自己擅长的领域,或者被合并或者仍然应用于某 ...

随机推荐

  1. scikit-learn入门学习记录

    一加载示例数据集 from sklearn import datasets iris = datasets.load_iris() digits = datasets.load_digits() 数据 ...

  2. JDBC纯驱动方式连接MySQL

    1 新建一个名为MysqlDemo的JavaProject 2 从http://dev.mysql.com/downloads/connector/j/中下载最新的驱动包. 这里有.tar.gz和.z ...

  3. (转)NIO 分散和聚集

    分散和聚集 概述 分散/聚集 I/O 是使用多个而不是单个缓冲区来保存数据的读写方法. 一个分散的读取就像一个常规通道读取,只不过它是将数据读到一个缓冲区数组中而不是读到单个缓冲区中.同样地,一个聚集 ...

  4. Python 操作 ElasticSearch

    Python 操作 ElasticSearch 学习了:https://www.cnblogs.com/shaosks/p/7592229.html 官网:https://elasticsearch- ...

  5. 使用airdrop进行文件共享

    使用airdrop进行文件共享 学习了: https://support.apple.com/zh-cn/HT203106 https://zh.wikihow.com/%E5%9C%A8Mac%E4 ...

  6. Tomcat 没有自动解压webapp下的war项目文件问题

    默认选择的tomcat安装在了C盘下的C:\Program Files下 所以webapp文件也在C盘下 选择启动tomcat时 我选择了 bin下的 Tomcat.exe 显示成功启动 打开项目网站 ...

  7. ActiveMQ订阅模式持久化实现

    实现步骤:1.配置发送xml,applicationContext-send.xml <?xml version="1.0" encoding="UTF-8&quo ...

  8. 关于configure和Makefile

    http://blog.csdn.net/lltaoyy/article/details/7615833 转篇文章,讲的不是很清楚,再附上几个资料连接,来自http://www.linuxdw.com ...

  9. powerdesigner里建物理模型图时choose DBMS为空怎么办?

    RT 出现如下对话框,是因为需要“DBMS”的规则文件夹 点击下图文件图标,浏览,找到安装目录里面PowerDesigner 15\Resource Files\DBMS,就可以了. 在此记录一下,希 ...

  10. 不用一个判断,用JS直接输出勾股数

    说明: 这里勾股数是符合a2+b2=c2的整数,比如32+42=52,52+122=132,怎么把符合条件的勾股数找出来呢?用代数替代的方法可以极大简化程序,直至一个判断都不用. 可以设a=m2-n2 ...