zoukankan      html  css  js  c++  java
  • 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

    否则视为侵权

  • 相关阅读:
    R-时空可视化
    zz温故知新:Tomcat调优&JVM内存性能调优
    《CarbonData》
    《RocketMQ》
    《LinuxTools》
    《为什么说 Prometheus 是足以取代 Zabbix 的监控神器?》
    《Zabbix》
    zz《百度地图商业选址》
    《Dapper》
    BZOJ 1601: [Usaco2008 Oct]灌水 最小生成树_超级源点
  • 原文地址:https://www.cnblogs.com/zou90512/p/3907303.html
Copyright © 2011-2022 走看看