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


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

     

  • 相关阅读:
    必须要狠狠的喷一把苹果
    机械键盘四种轴试用体验
    linux终端快捷键
    挥别我在软件开发的第一个公司
    mysql 查询优化
    oracle 用户创建这个挺靠谱
    重置了下系统好多关于mysql密码的文章都很渣拷分好的备用
    关于WebView的复习
    Bpmx实施经验
    使用Nexus私服代理其他maven仓库(jitpack、jcenter)
  • 原文地址:https://www.cnblogs.com/zou90512/p/3924427.html
Copyright © 2020-2023  润新知