为什么你要使用自定义协议?

通信协议用于将接收到的二进制数据转化成您的应用程序可以理解的请求。 SuperSocket提供了一个内置的通信协议“命令行协议”定义每个请求都必须以回车换行"\r\n"结尾。

但是一些应用程序无法使用命令行协议由于不同的原因。 这种情况下,你需要使用下面的工具来实现你的自定义协议:

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。
1 2 3 4 5 * data  class * RequestInfo * ReceiveFilter * ReceiveFilterFactory * AppServer and AppSession

第一步、定义一个和协议合适的数据结构

数据结构是通信数据存储的结构形式,也是自定义协议的基础,每个客户端发送数据最后都解析到此数据结构(使用"简体中文(GB2312)",Encoding.GetEncoding("gb2312"编码)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public  class  MyData //20字节 {      /// <summary>      /// 开始符号6字节, "!Start"      /// </summary>      public  string  Start {  get set ; }      /// <summary>      /// 消息类型,1字节      /// </summary>      public  byte  key {  get set ; }      /// <summary>      /// 主体消息数据包长度,4字节      /// </summary>      public  uint  Lenght {  get set ; }      /// <summary>      /// 4字节唯一设备识别符(Unique Device Identifier)      /// </summary>      public  uint  DeviceUDID {  get set ; }      /// <summary>      /// 目标命令类型1字节      /// </summary>      public  byte  Type {  get set ; }         /// <summary>      /// 主体消息      /// </summary>      public  byte [] Body {  get set ; }             /// <summary>      /// 结束符号4字节, "$End" ,      /// </summary>      public  string  End {  get set ; }        public  override  string  ToString()      {          return  string .Format( "开始符号:{0},消息类型:{1},数据包长度:{2},唯一设备识别符:{3},目标命令类型:{4},主体消息:{5},结束符号:{6}" ,              Start, key, Lenght, Lenght, DeviceUDID, Type, Body, End);      } }

  第二步、请求(RequestInfo)

RequestInfo 是表示来自客户端请求的实体类。 每个来自客户端的请求都能应该被实例化为 RequestInfo 类型。 RequestInfo 类必须实现接口 IRequestInfo,该接口只有一个名为"Key"的字符串类型的属性:

根据你的应用程序的需要来定义你自己的请求类型。 例如:

1 2 3 4 5 6 7 8 public  class  MyRequestInfo : RequestInfo<MyData>      {          public  MyRequestInfo( string  key, MyData myData)          {              //如果需要使用命令行协议的话,那么key与命令类名称myData相同              Initialize(key, myData);          }      }

  

第三步、接收过滤器(ReceiveFilter)

接收过滤器(ReceiveFilter)用于将接收到的二进制数据转化成请求实例(RequestInfo)。

实现一个接收过滤器(ReceiveFilter), 你需要实现接口 IReceiveFilter:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public  class  MyReceiveFilter : IReceiveFilter<MyRequestInfo>      {          public  Encoding encoding = Encoding.GetEncoding( "gb2312" );          /// <summary>          /// Gets the size of the left buffer.          /// </summary>          /// <value>          /// The size of the left buffer.          /// </value>          public  int  LeftBufferSize {  get ; }          /// <summary>          /// Gets the next receive filter.          /// </summary>          public  IReceiveFilter<MyRequestInfo> NextReceiveFilter {  get ; }            public  FilterState State {  get private  set ; }          /// <summary>          /// 该方法将会在 SuperSocket 收到一块二进制数据时被执行,接收到的数据在 readBuffer 中从 offset 开始, 长度为 length 的部分。          /// </summary>          /// <param name="readBuffer">接收缓冲区, 接收到的数据存放在此数组里</param>          /// <param name="offset">接收到的数据在接收缓冲区的起始位置</param>          /// <param name="length">本轮接收到的数据的长度</param>          /// <param name="toBeCopied">表示当你想缓存接收到的数据时,是否需要为接收到的数据重新创建一个备份而不是直接使用接收缓冲区</param>          /// <param name="rest">这是一个输出参数, 它应该被设置为当解析到一个为政的请求后,接收缓冲区还剩余多少数据未被解析</param>          /// <returns></returns>          /// 当你在接收缓冲区中找到一条完整的请求时,你必须返回一个你的请求类型的实例.          /// 当你在接收缓冲区中没有找到一个完整的请求时, 你需要返回 NULL.          /// 当你在接收缓冲区中找到一条完整的请求, 但接收到的数据并不仅仅包含一个请求时,设置剩余数据的长度到输出变量 "rest". SuperSocket 将会检查这个输出参数 "rest", 如果它大于 0, 此 Filter 方法 将会被再次执行, 参数 "offset" 和 "length" 会被调整为合适的值.          public  MyRequestInfo Filter( byte [] readBuffer,  int  offset,  int  length,  bool  toBeCopied,  out  int  rest)          {              rest = 0;              if  (length < 21) //没有数据                  return  null ;              byte [] data =  new  byte [length];              Buffer.BlockCopy(readBuffer, offset, data, 0, length);                         var  str = encoding.GetString(data);              MyData myData= new  MyData();              myData.Start= encoding.GetString(data,0,6); //6字节              myData.key= data[6]; //1字节              myData.Lenght = BitConverter.ToUInt32(data, 7); //4字节  6 + 1              if  (length < myData.Lenght + 20)                  return  null ;              myData.DeviceUDID= BitConverter.ToUInt32(data, 11); //4字节 6 + 1+4              myData.Type = data[15]; //1字节 6+1+4+4                myData.Body =  new  byte [myData.Lenght]; //myData.Lenght字节              Buffer.BlockCopy(data, 16, myData.Body, 0, ( int )myData.Lenght);                myData.End= encoding.GetString(data, ( int )(16+ myData.Lenght), 4); //4字节              if  (myData.Start !=  "!Start"  || myData.End !=  "$End" )                  return  null ;              rest =( int )(length-(20+ myData.Lenght)); //未处理数据              return  new  MyRequestInfo(myData.key.ToString(),myData);          }

public void Reset()
{

}

  

  • TRequestInfo: 类型参数 "TRequestInfo" 是你要在程序中使用的请求类型(RequestInfo);
  • LeftBufferSize: 该接收过滤器已缓存数据的长度;
  • NextReceiveFilter: 当下一块数据收到时,用于处理数据的接收过滤器实例;
  • Reset(): 重设接收过滤器实例到初始状态;
  • Filter(....): 该方法将会在 SuperSocket 收到一块二进制数据时被执行,接收到的数据在 readBuffer 中从 offset 开始, 长度为 length 的部分。

    TRequestInfo Filter(byte[] readBuffer, int offset, int length, bool toBeCopied, out int rest); 
    • readBuffer: 接收缓冲区, 接收到的数据存放在此数组里
    • offset: 接收到的数据在接收缓冲区的起始位置
    • length: 本轮接收到的数据的长度
    • toBeCopied: 表示当你想缓存接收到的数据时,是否需要为接收到的数据重新创建一个备份而不是直接使用接收缓冲区
    • rest: 这是一个输出参数, 它应该被设置为当解析到一个为政的请求后,接收缓冲区还剩余多少数据未被解析

这儿有很多种情况需要你处理:

  • 当你在接收缓冲区中找到一条完整的请求时,你必须返回一个你的请求类型的实例.
  • 当你在接收缓冲区中没有找到一个完整的请求时, 你需要返回 NULL.
  • 当你在接收缓冲区中找到一条完整的请求, 但接收到的数据并不仅仅包含一个请求时,设置剩余数据的长度到输出变量 "rest". SuperSocket 将会检查这个输出参数 "rest", 如果它大于 0, 此 Filter 方法 将会被再次执行, 参数 "offset" 和 "length" 会被调整为合适的值.

第四步、接收过滤器工厂(ReceiveFilterFactory)

接收过滤器工厂(ReceiveFilterFactory)用于为每个会话创建接收过滤器. 定义一个过滤器工厂(ReceiveFilterFactory)类型, 你必须实现接口 IReceiveFilterFactory. 类型参数 "TRequestInfo" 是你要在整个程序中使用的请求类型

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 /// <summary>      /// Receive filter factory interface      /// </summary>      /// <typeparam name="TRequestInfo">The type of the request info.</typeparam>      public  class  MyReceiveFilterFactory : IReceiveFilterFactory<MyRequestInfo>      {          /// <summary>          /// Creates the receive filter.          /// </summary>          /// <param name="appServer">The app server.</param>          /// <param name="appSession">The app session.</param>          /// <param name="remoteEndPoint">The remote end point.</param>          /// <returns>          /// the new created request filer assosiated with this socketSession          /// </returns>          //MyReceiveFilter<MyRequestInfo> CreateFilter(IAppServer appServer, IAppSession appSession, IPEndPoint remoteEndPoint);          public  IReceiveFilter<MyRequestInfo> CreateFilter(IAppServer appServer, IAppSession appSession, IPEndPoint remoteEndPoint)          {              return  new  MyReceiveFilter();          }      }

  第五步、和 AppSession,AppServer 配合工作

现在, 你已经有了 RequestInfo, ReceiveFilter 和 ReceiveFilterFactory, 但是你还没有正式使用它们. 如果你想让他们在你的程序里面可用, 你需要定义你们的 AppSession 和 AppServer 来使用他们.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public  class  MySession : AppSession<MySession, MyRequestInfo>     {         public  uint  DeviceUDID;         protected  override  void  HandleException(Exception e)         {           }     }       public  class  MyServer : AppServer<MySession, MyRequestInfo>     {         /// <summary>         /// 使用自定义协议工厂         /// </summary>         public  MyServer()             base ( new  MyReceiveFilterFactory())         {         }     }

  第六步、服务端使用实例

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 MyServer myServer =  new  MyServer();          Encoding encoding = Encoding.GetEncoding( "gb2312" );   //Setup the appServer              if  (!myServer.Setup(1990))  //Setup with listening port              {                  MessageBox.Show( "Failed to setup!" );                  return ;              }              //Try to start the appServer              if  (!myServer.Start())              {                  MessageBox.Show( "Failed to start!" );                  return ;              }              myServer.NewSessionConnected += MyServer_NewSessionConnected;              myServer.NewRequestReceived += MyServer_NewRequestReceived;

  

1 2 3 4 5 6 7 8 9 private  void  MyServer_NewRequestReceived(MySession session, MyRequestInfo requestInfo)          {              var  msg=encoding.GetString(requestInfo.Body.Body);          }            private  void  MyServer_NewSessionConnected(MySession session)          {              session.Send( "Welcome to SuperSocket Telnet Server" );          }

  参考:http://docs.supersocket.net/v1-6/zh-CN/Implement-Your-Own-Communication-Protocol-with-IRequestInfo,-IReceiveFilter-and-etc

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄