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

    否则视为侵权

  • 相关阅读:
    XAML学习笔记之Layout(五)——ViewBox
    XAML学习笔记——Layout(三)
    XAML学习笔记——Layout(二)
    XAML学习笔记——Layout(一)
    从0开始搭建SQL Server 2012 AlwaysOn 第三篇(安装数据,配置AlwaysOn)
    从0开始搭建SQL Server 2012 AlwaysOn 第二篇(配置故障转移集群)
    从0开始搭建SQL Server 2012 AlwaysOn 第一篇(AD域与DNS)
    Sql Server 2012 事务复制遇到的问题及解决方式
    Sql Server 2008R2升级 Sql Server 2012 问题
    第一次ACM
  • 原文地址:https://www.cnblogs.com/zou90512/p/3907303.html
Copyright © 2011-2022 走看看