欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C# 常用协议实现模版及FixedSizeReceiveFilter示例(SuperSocket入门)

程序员文章站 2022-09-04 10:18:54
socket里面的协议解析是socket通讯程序设计中最复杂的地方,如果你的应用层协议设计或实现不佳,socket通讯中常见的粘包,分包就难以避免。supersocket内...

socket里面的协议解析是socket通讯程序设计中最复杂的地方,如果你的应用层协议设计或实现不佳,socket通讯中常见的粘包,分包就难以避免。supersocket内置了命令行格式的协议commandlineprotocol,如果你使用了其它格式的协议,就必须自行实现自定义协议customprotocol。看了一篇文档之后, 你可能会觉得用 supersocket 来实现你的自定义协议并不简单。 为了让这件事变得更容易一些, supersocket 提供了一些通用的协议解析工具, 你可以用他们简单而且快速的实现你自己的通信协议:

  • terminatorreceivefilter (supersocket.socketbase.protocol.terminatorreceivefilter, supersocket.socketbase) ---结束符协议
  • countspliterreceivefilter (supersocket.facility.protocol.countspliterreceivefilter, supersocket.facility)---固定数量分隔符协议
  • fixedsizereceivefilter (supersocket.facility.protocol.fixedsizereceivefilter, supersocket.facility)---固定请求大小协议
  • beginendmarkreceivefilter (supersocket.facility.protocol.beginendmarkreceivefilter, supersocket.facility)---带起止符协议
  • fixedheaderreceivefilter (supersocket.facility.protocol.fixedheaderreceivefilter, supersocket.facility)---头部格式固定并包含内容长度协议

1、terminatorreceivefilter结束符协议

结束符协议和命令行协议类似,一些协议用结束符来确定一个请求.例如, 一个协议使用两个字符 "##" 作为结束符, 于是你可以使用类 "terminatorreceivefilterfactory":

结束符协议terminatorprotocolserver :

public class terminatorprotocolserver : appserver
{ 
 public terminatorprotocolserver()
  : base(new terminatorreceivefilterfactory("##"))
 {
 }
}

基于terminatorreceivefilter实现你的接收过滤器(receivefilter):

public class yourreceivefilter : terminatorreceivefilter<yourrequestinfo>
{
 //more code
}

实现你的接收过滤器工厂(receivefilterfactory)用于创建接受过滤器实例:

public class yourreceivefilterfactory : ireceivefilterfactory<yourrequestinfo>
{
 //more code
}

2、countspliterreceivefilter 固定数量分隔符协议

有些协议定义了像这样格式的请求 "#part1#part2#part3#part4#part5#part6#part7#". 每个请求有7个由 '#' 分隔的部分. 这种协议的实现非常简单:

/// <summary>
/// 请求格式:#part1#part2#part3#part4#part5#part6#part7#
/// </summary>
public class countspliterappserver : appserver
{
 public countspliterappserver()
  : base(new countspliterreceivefilterfactory((byte)'#', 8)) //8个分隔符,7个参数。除使用默认的过滤工厂,还可以参照上一个实例定制协议
 {
 }
}

3、fixedsizereceivefilter 固定请求大小协议

在这种协议之中, 所有请求的大小都是相同的。如果你的每个请求都是有8个字符组成的字符串,如"huang li", 你应该做的事就是想如下代码这样实现一个接收过滤器(receivefilter):

class myreceivefilter : fixedsizereceivefilter<stringrequestinfo>
{
 public myreceivefilter()
  : base(8) //传入固定的请求大小
 {
 }
 protected override stringrequestinfo processmatchedrequest(byte[] buffer, int offset, int length, bool tobecopied)
 {
  //todo: 通过解析到的数据来构造请求实例,并返回
 }
}

然后在你的 appserver 类中使用这个接受过滤器 (receivefilter):

public class myappserver : appserver
{
 public myappserver()
  : base(new defaultreceivefilterfactory<myreceivefilter, stringrequestinfo>()) //使用默认的接受过滤器工厂 (defaultreceivefilterfactory)
 {
 }
} 

4、beginendmarkreceivefilter 带起止符协议

在这类协议的每个请求之中 都有固定的开始和结束标记。例如, 我有个协议,它的所有消息都遵循这种格式 "&xxxxxxxxxxxxxx#"。因此,在这种情况下, "&" 是开始标记, "#" 是结束标记,于是你的接受过滤器可以定义成这样:

class myreceivefilter : beginendmarkreceivefilter<stringrequestinfo>
{
 //开始和结束标记也可以是两个或两个以上的字节
 private readonly static byte[] beginmark = new byte[] { (byte)'&' };
 private readonly static byte[] endmark = new byte[] { (byte)'#' };

 public myreceivefilter()
  : base(beginmark, endmark) //传入开始标记和结束标记
 {
 }
 protected override stringrequestinfo processmatchedrequest(byte[] readbuffer, int offset, int length)
 {
  //todo: 通过解析到的数据来构造请求实例,并返回
 }
}

然后在你的 appserver 类中使用这个接受过滤器 (receivefilter):

public class myappserver : appserver
{
 public myappserver()
  : base(new defaultreceivefilterfactory<myreceivefilter, stringrequestinfo>()) //使用默认的接受过滤器工厂 (defaultreceivefilterfactory)
 {
 }
}

5、fixedheaderreceivefilter 头部格式固定并包含内容长度协议

这种协议将一个请求定义为两大部分, 第一部分定义了包含第二部分长度等等基础信息. 我们通常称第一部分为头部.

例如, 我们有一个这样的协议: 头部包含 6 个字节, 前 4 个字节用于存储请求的名字, 后两个字节用于代表请求体的长度:

/// +-------+---+-------------------------------+
/// |request| l |                               |
/// | name  | e |    request body               |
/// |  (4)  | n |                               |
/// |       |(2)|                               |
/// +-------+---+-------------------------------+
使用 supersocket, 你可以非常方便的实现这种协议:

class myreceivefilter : fixedheaderreceivefilter<binaryrequestinfo>
{
 public myreceivefilter()
  : base(6)
 {
 }
 protected override int getbodylengthfromheader(byte[] header, int offset, int length)
 {
  return (int)header[offset + 4] * 256 + (int)header[offset + 5];
 }
 protected override binaryrequestinfo resolverequestinfo(arraysegment<byte> header, byte[] bodybuffer, int offset, int length)
 {
  return new binaryrequestinfo(encoding.utf8.getstring(header.array, header.offset, 4), bodybuffer.clonerange(offset, length));
 }
}

你需要基于类fixedheaderreceivefilter实现你自己的接收过滤器.

  • 传入父类构造函数的 6 表示头部的长度;
  • 方法"getbodylengthfromheader(...)" 应该根据接收到的头部返回请求体的长度;
  • 方法 resolverequestinfo(....)" 应该根据你接收到的请求头部和请求体返回你的请求类型的实例.

实际使用场景:

到这里五种协议的模板你都已经了解了一遍,并且知道了相关的格式处理。接下来看一个网络示例:

通讯协议格式:

C# 常用协议实现模版及FixedSizeReceiveFilter示例(SuperSocket入门)

在看到上图协议是在纠结客户端发送16进制,服务器怎么接收,16进制的报文如下:

26 01 00 19 4e 4a 30 31 31 01 44 41 31 31 32 00 07 00 00 00 00 00 00 34 23

16进制也好,10进制也好,其他的进制也好,最终都是转换成byte[],其实在处理数据时,发送过去的数据都是可以转换成为byte[]的,所以服务的只要解析byte[]数组就行了。按照协议来解析就能得到想要的数据。下面使用fixedsizereceivefilter的例子,代码如下:

根据上面的通讯协议,开始来实现解析:

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

using system;
using system.collections.generic;
using system.linq;
using system.text;
using system.threading.tasks;
/****************************************************************
* 作者:黄昏前黎明后
* clr版本:4.0.30319.42000
* 创建时间:2017-01-23 21:12:30
* 2017
* 描述说明:协议数据包
*
* 修改历史:
*
*
*****************************************************************/
namespace supersocketdemo
{
 public class hldata
 {
  /// <summary>
  /// 开始符号
  /// </summary>
  public char head { get; set; }
  /// <summary>
  /// 协议包数据
  /// </summary>
  public byte ping { get; set; }
  /// <summary>
  /// 数据长度
  /// </summary>
  public ushort lenght { get; set; }
  /// <summary>
  /// 终端id
  /// </summary>
  public uint fid { get; set; }
  /// <summary>
  /// 目标类型
  /// </summary>
  public byte type { get; set; }
  /// <summary>
  /// 转发终端id
  /// </summary>
  public uint sid { get; set; }
  /// <summary>
  /// 发送计数
  /// </summary>
  public ushort sendcount { get; set; }
  /// <summary>
  /// 保留字段
  /// </summary>
  public byte[] retain { get; set; }
  /// <summary>
  /// 异或校验
  /// </summary>
  public byte check { get; set; }
  /// <summary>
  /// 结束符号
  /// </summary>
  public char end { get; set; }
  public override string tostring()
  {
   return string.format("开始符号:{0},包数据:{1},数据长度:{2},终端id:{3},目标类型:{4},转发终端id:{5},发送包计数:{6},保留字段:{7},异或校验:{8},结束符号:{9}",
    head, ping, lenght, fid, type, sid, sendcount, retain, check, end);
  }
 }
}
hldata

第二步、建立一个requestinfo来给server数据接收

using system;
using system.collections.generic;
using system.linq;
using system.text;
using system.threading.tasks;
using supersocket.socketbase.protocol;
/****************************************************************
* 作者:黄昏前黎明后
* clr版本:4.0.30319.42000
* 创建时间:2017-01-22 21:03:31
* 2017
* 描述说明:数据请求
*
* 修改历史:
*
*
*****************************************************************/
namespace supersocketdemo
{
 public class hlprotocolrequestinfo : requestinfo<hldata>
 {
  public hlprotocolrequestinfo(hldata hldata)
  {
   //如果需要使用命令行协议的话,那么命令类名称hldata相同
   initialize("hldata", hldata);
  }
 }
}
hlprotocolrequestinfo 类

第三步、fixedsize协议解析

using system;
using system.collections.generic;
using system.linq;
using system.text;
using system.threading.tasks;
using supersocket.socketbase.protocol;
using supersocket.facility.protocol;
using supersocket.common;
/****************************************************************
* 作者:黄昏前黎明后
* clr版本:4.0.30319.42000
* 创建时间:2017-01-22 21:06:01
* 2017
* 描述说明:协议解析类,固定请求大小的协议
*
* 修改历史:
*
*
*****************************************************************/
namespace supersocketdemo
{
 /// <summary>
 /// 固定请求大小的协议,(帧格式为hlprotocolrequestinfo)
 /// </summary>
 public class hlprotocolreceivefilter : fixedsizereceivefilter<hlprotocolrequestinfo>
 {
  public hlprotocolreceivefilter() : base(25)//总的字节长度 1+1+2+5+1+5+2+6+1+1 = 25
  {
  }
  protected override hlprotocolrequestinfo processmatchedrequest(byte[] buffer, int offset, int length, bool tobecopied)
  {
   var hldata = new hldata();
   hldata.head = (char)buffer[offset];//开始标识的解析,1个字节
   hldata.ping = buffer[offset + 1];//数据,从第2位起,只有1个字节
   hldata.lenght = bitconverter.touint16(buffer, offset + 2);//数据长度,从第3位开始,2个字节
   hldata.fid = bitconverter.touint32(buffer, offset + 4);//本终端id,从第5位开始,5个字节
   hldata.type = buffer[offset + 9];//目标类型,从第10位开始,1个字节
   hldata.sid = bitconverter.touint32(buffer, offset + 10);//转发终端id,从第11位开始,5个字节
   hldata.sendcount = bitconverter.touint16(buffer, offset + 15);//发送包计数,从第16位开始,2个字节
   hldata.retain = buffer.clonerange(offset + 17, 6);//保留字段,从18位开始,6个字节
   hldata.check = buffer[offset + 23];//异或校验,从24位开始,1个字节
   hldata.end = (char)buffer[offset + 24];//结束符号,从第25位开始,一个字节
   return new hlprotocolrequestinfo(hldata);
  }
 }
}
hlprotocolreceivefilter类

第四步、建立协议工厂hlreceivefilterfactory

using system;
using system.collections.generic;
using system.linq;
using system.text;
using system.threading.tasks;
using supersocket.socketbase;
using supersocket.socketbase.protocol;
using system.net;
/****************************************************************
* 作者:黄昏前黎明后
* clr版本:4.0.30319.42000
* 创建时间:2017-01-23 :22:01:25
* 2017
* 描述说明:协议工厂
*
* 修改历史:
*
*
*****************************************************************/
namespace supersocketdemo
{
 public class hlreceivefilterfactory: ireceivefilterfactory<hlprotocolrequestinfo>
 {
  public ireceivefilter<hlprotocolrequestinfo> createfilter(iappserver appserver, iappsession appsession, ipendpoint remoteendpoint)
  {
   return new hlbeginendmarkreceivefilter();
  }
 }
}

hlreceivefilterfactory类

第五步、自定义hlprotocolsession继承appsession

using supersocket.socketbase;
using supersocket.socketbase.protocol;
using system;
/****************************************************************
* 作者:黄昏前黎明后
* clr版本:4.0.30319.42000
* 创建时间:2017-01-22 21:15:11
* 2017
* 描述说明:自定义hlprotocolsession
*
* 修改历史:
*
*
*****************************************************************/
namespace supersocketdemo
{
 public class hlprotocolsession : appsession<hlprotocolsession, hlprotocolrequestinfo>
 {
  protected override void handleexception(exception e)
  {

  }

 }
}

hlprotocolsession类

第六步、自定义hlprotocolserver继承appserver

using system;
using system.collections.generic;
using system.linq;
using system.text;
using system.threading.tasks;
using supersocket.socketbase;
using supersocket.socketbase.protocol;
/****************************************************************
*  作者:黄昏前黎明后
*  clr版本:4.0.30319.42000
*  创建时间:2017-01-22 21:16:57
*  2017
*  描述说明:自定义server
*
*  修改历史:
*
*
*****************************************************************/
namespace supersocketdemo
{
 public class hlprotocolserver : appserver<hlprotocolsession, hlprotocolrequestinfo>
  {
    /// <summary>
    /// 使用自定义协议工厂
    /// </summary>
    public hlprotocolserver()
      : base(new hlreceivefilterfactory()) 
    {
    }
  }
}

hlprotocolserver类

第七步、加上起止符协议hlbeginendmarkreceivefilter

using system;
using system.collections.generic;
using system.linq;
using system.text;
using system.threading.tasks;
using supersocket.common;
using supersocket.facility.protocol;
/****************************************************************
*  作者:黄昏前黎明后
*  clr版本:4.0.30319.42000
*  创建时间:2017-01-23 22:07:03
*  2017
*  描述说明:带起止符的协议, "&" 是开始标记, "#" 是结束标记,开始结束标记由自己定义
*
*  修改历史:
*
*
*****************************************************************/
namespace supersocketdemo
{
  public class hlbeginendmarkreceivefilter : beginendmarkreceivefilter<hlprotocolrequestinfo>
  {
    private readonly static char strbegin = '&';
    private readonly static char strend = '#';
    //开始和结束标记也可以是两个或两个以上的字节
    private readonly static byte[] beginmark = new byte[] { (byte)strbegin };
    private readonly static byte[] endmark = new byte[] { (byte)strend };

    public hlbeginendmarkreceivefilter() : base(beginmark, endmark)
    {
    }
    /// <summary>
    /// 这里解析的到的数据是会把头和尾部都给去掉的
    /// </summary>
    /// <param name="readbuffer"></param>
    /// <param name="offset"></param>
    /// <param name="length"></param>
    /// <returns></returns>
    protected override hlprotocolrequestinfo processmatchedrequest(byte[] readbuffer, int offset, int length)
    {
      var hldata = new hldata();
      hldata.head = strbegin;//自己定义开始符号
      hldata.ping = readbuffer[offset];//数据,从第1位起,只有1个字节
      hldata.lenght = bitconverter.touint16(readbuffer, offset + 1);//数据长度,从第2位开始,2个字节
      hldata.fid = bitconverter.touint32(readbuffer, offset + 3);//本终端id,从第4位开始,5个字节
      hldata.type = readbuffer[offset + 8];//目标类型,从第9位开始,1个字节
      hldata.sid = bitconverter.touint32(readbuffer, offset + 9);//转发终端id,从第10位开始,5个字节
      hldata.sendcount = bitconverter.touint16(readbuffer, offset + 14);//发送包计数,从第15位开始,2个字节
      hldata.retain = readbuffer.clonerange(offset + 16, 6);//保留字段,从17位开始,6个字节
      hldata.check = readbuffer[offset + 22];//异或校验,从23位开始,1个字节
      hldata.end = strend;//结束符号,自己定义
      return new hlprotocolrequestinfo(hldata);
    }
  }
}

hlbeginendmarkreceivefilter类

第八步、服务启动和停止

using system;
using system.collections.generic;
using system.linq;
using system.text;
using system.threading.tasks;
using supersocket.socketbase;
using supersocket.socketbase.protocol;
using supersocket.socketengine;
/****************************************************************
*  作者:黄昏前黎明后
*  clr版本:4.0.30319.42000
*  创建时间:2017-01-19 00:02:17
*  2017
*  描述说明:服务启动和停止入口 
*
*  修改历史: 2017 -01-19 调整自定义mysession和myserver
*       2017 -01-23 通讯协议解析,直接使用入口注册事件
*
*****************************************************************/
namespace supersocketdemo
{
  class program
  {
    /// <summary>
    /// supersocket服务启动或停止
    /// </summary>
    /// <param name="args"></param>
    static void main(string[] args)
    {
      console.writeline("请按任何键进行启动supersocket服务!");
      console.readkey();
      console.writeline();
      var hlprotocolserver = new hlprotocolserver();
      // 设置端口号
      int port = 2017;
      //启动应用服务端口
      if (!hlprotocolserver.setup(port)) //启动时监听端口2017
      {
        console.writeline("服务端口启动失败!");
        console.readkey();
        return;
      }
      console.writeline();
      //注册连接事件
      hlprotocolserver.newsessionconnected += hlprotocolserver_newsessionconnected;
      //注册请求事件
      hlprotocolserver.newrequestreceived += hlprotocolserver_newrequestreceived;
      //注册session关闭事件
      hlprotocolserver.sessionclosed += hlprotocolserver_sessionclosed;
      //尝试启动应用服务
      if (!hlprotocolserver.start())
      {
        console.writeline("服务启动失败!");
        console.readkey();
        return;
      }
      console.writeline("服务器状态:" + hlprotocolserver.state.tostring());
      console.writeline("服务启动成功,请按'e'停止服务!");
      while (console.readkey().keychar != 'e')
      {
        console.writeline();
        continue;
      }
      //停止服务
      hlprotocolserver.stop();
      console.writeline("服务已停止!");
      console.readkey();
    }
    static void hlprotocolserver_sessionclosed(hlprotocolsession session, supersocket.socketbase.closereason value)
    {
      console.writeline(session.remoteendpoint.tostring() + "连接断开. 断开原因:" + value);
    }
    static void hlprotocolserver_newsessionconnected(hlprotocolsession session)
    {
      console.writeline(session.remoteendpoint.tostring() + " 已连接.");
    }
    /// <summary>
    /// 协议并没有什么太多复杂逻辑,不需要用到命令模式,直接用这种方式就可以了
    /// </summary>
    /// <param name="session"></param>
    /// <param name="requestinfo"></param>
    private static void hlprotocolserver_newrequestreceived(hlprotocolsession session, hlprotocolrequestinfo requestinfo)
    {
      console.writeline();
      console.writeline("数据来源: " + session.remoteendpoint.tostring());
      console.writeline("接收数据内容:"+requestinfo.body);
    }
  }
}

program类

通讯协议需要使用小工具进行调试,本人使用的是tcp/udp端口调试工具sockettool v2.大家可以直接进行下载。使用hex模式进行发送16进制报文,服务器输出结果:

C# 常用协议实现模版及FixedSizeReceiveFilter示例(SuperSocket入门)

C# 常用协议实现模版及FixedSizeReceiveFilter示例(SuperSocket入门)

本文参考官方文档 内置的常用协议实现模版

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!