Maison  >  Article  >  développement back-end  >  Introduction détaillée aux modèles d'implémentation de protocole commun C# et exemple de code de FixedSizeReceiveFilter

Introduction détaillée aux modèles d'implémentation de protocole commun C# et exemple de code de FixedSizeReceiveFilter

黄舟
黄舟original
2017-03-27 11:16:112867parcourir

Cet article présente principalement des modèles d'implémentation de protocole courants et des exemples de FixedSizeReceiveFilter. Il a une très bonne valeur de référence, jetons-y un coup d'œil avec l'éditeur ci-dessous

L'analyse du protocole dans Socket est la partie la plus compliquée de la programmation de communication Socket. Si votre protocole de couche application est mal conçu ou mal implémenté, Les paquets collants sont courants dans la communication Socket et le sous-packaging est inévitable. SuperSocket dispose d'un protocole de format de ligne de commande intégré CommandLineProtocol Si vous utilisez un protocole dans d'autres formats, vous devez implémenter vous-même le protocole personnalisé CustomProtocol. Après avoir lu un document, vous aurez peut-être l'impression qu'il n'est pas simple d'implémenter votre protocole personnalisé avec SuperSocket. Pour faciliter cela, SuperSocket fournit des outils généraux d'analyse de protocole, vous pouvez les utiliser pour implémenter votre propre protocole de communication simplement et rapidement :

  • TerminatorReceiveFilter (SuperSocket.SocketBase .Protocol.TerminatorReceiveFilter, SuperSocket.SocketBase) --- Protocole Terminator

  • CountSpliterReceiveFilter (SuperSocket.Facility.Protocol.CountSpliterReceiveFilter , SuperSocket.Facility)---Corrigé protocole de délimiteur de nombre

  • FixedSizeReceiveFilter (SuperSocket.Facility.Protocol.FixedSizeReceiveFilter, SuperSocket.Facility) --- Protocole de taille de requête fixe

  • BeginEndMarkReceiveFilter (SuperSocket.Facility.Protocol.BeginEndMarkReceiveFilter, SuperSocket.Facility)---Protocole avec caractères de début et de fin

  • FixedHeaderReceiveFilter (SuperSocket.Facility.Protocol.FixedHeaderReceiveFilter, SuperSocket.Facility)---Le format d'en-tête est fixe et contient le protocole de longueur du contenu

1. Protocole de terminaison TerminatorReceiveFilter

Le protocole de terminaison est similaire au protocole de ligne de commande. Certains protocoles utilisent des terminateurs pour déterminer une requête. Par exemple, un protocole utilise deux caractères "##" comme terminateur, vous pouvez donc utiliser la classe. "TerminatorReceiveFilterFactory":

Terminator Protocol TerminatorProtocolServer:

public class TerminatorProtocolServer : AppServer
{ 
 public TerminatorProtocolServer()
  : base(new TerminatorReceiveFilterFactory("##"))
 {
 }
}

pour implémenter votre filtre de réception (ReceiveFilter):

basé sur TerminatorReceiveFilter
public class YourReceiveFilter : TerminatorReceiveFilter<YourRequestInfo>
{
 //More code
}

Implémentez votre usine de filtres de réception (ReceiveFilterFactory) pour créer des instances de filtre d'acceptation :

public class YourReceiveFilterFactory : IReceiveFilterFactory<YourRequestInfo>
{
 //More code
}

2. Protocole de séparateur de nombre fixe CountSpliterReceiveFilter

Certains protocoles définissent des requêtes au format " #part1#part2#part3#part4#part5#part6#part7#". Chaque requête comporte 7 parties séparées par '#'. La mise en œuvre de ce protocole est très Simple :

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

3 .FixedSizeReceiveFilter protocole de taille de requête fixe

Dans ce protocole, la taille de toutes les requêtes est la même. Si chacune de vos requêtes est une chaîne de 8 caractères, telle que "HUANG LI", ce que vous devez faire est d'implémenter un filtre de réception (ReceiveFilter) avec le code suivant :

class MyReceiveFilter : FixedSizeReceiveFilter<StringRequestInfo>
{
 public MyReceiveFilter()
  : base(8) //传入固定的请求大小
 {
 }
 protected override StringRequestInfo ProcessMatchedRequest(byte[] buffer, int offset, int length, bool toBeCopied)
 {
  //TODO: 通过解析到的数据来构造请求实例,并返回
 }
}

Ensuite, utilisez ce filtre d'acceptation (ReceiveFilter) dans votre classe AppServer :

public class MyAppServer : AppServer
{
 public MyAppServer()
  : base(new DefaultReceiveFilterFactory<MyReceiveFilter, StringRequestInfo>()) //使用默认的接受过滤器工厂 (DefaultReceiveFilterFactory)
 {
 }
}

4 BeginEndMarkReceiveFilter avec le protocole de caractères de début et de fin

Il y a des débuts et des caractères de fin fixes. des marqueurs de fin dans chaque requête de ce type de protocole. Par exemple, j'ai un protocole dans lequel tous les messages suivent le format "&xxxxxxxxxxxxxxx#". Donc, dans ce cas, "&" est la balise d'ouverture et "#" est la balise de fermeture, donc votre filtre d'acceptation peut être défini comme ceci :

class MyReceiveFilter : BeginEndMarkReceiveFilter<StringRequestInfo>
{
 //开始和结束标记也可以是两个或两个以上的字节
 private readonly static byte[] BeginMark = new byte[] { (byte)&#39;&&#39; };
 private readonly static byte[] EndMark = new byte[] { (byte)&#39;#&#39; };

 public MyReceiveFilter()
  : base(BeginMark, EndMark) //传入开始标记和结束标记
 {
 }
 protected override StringRequestInfo ProcessMatchedRequest(byte[] readBuffer, int offset, int length)
 {
  //TODO: 通过解析到的数据来构造请求实例,并返回
 }
}

puis utilisé dans votre classe AppServer. Cette acceptation filter (ReceiveFilter) :

public class MyAppServer : AppServer
{
 public MyAppServer()
  : base(new DefaultReceiveFilterFactory<MyReceiveFilter, StringRequestInfo>()) //使用默认的接受过滤器工厂 (DefaultReceiveFilterFactory)
 {
 }
}

5. Le format d'en-tête FixedHeaderReceiveFilter est fixe et contient le protocole de longueur de contenu

Ce protocole définit une requête comme deux. Principalement, la première partie. définit les informations de base, notamment la longueur de la deuxième partie, etc. Nous appelons généralement la première partie l'en-tête.

Par exemple, nous avons un protocole comme celui-ci : l'en-tête contient 6 octets, avant que 4 octets ne soient utilisés. pour stocker le nom de la requête, et les deux derniers octets sont utilisés pour représenter la longueur du corps de la requête :

/// +-------+---+-------------------------------+
/// |request| l |                               |
/// | name  | e |    request body               |
/// |  (4)  | n |                               |
/// |       |(2)|                               |
/// +-------+---+-------------------------------+

En utilisant SuperSocket, vous pouvez implémenter ce protocole très facilement :

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));
 }
}

Vous devez implémenter votre propre filtre de réception basé sur la classe FixedHeaderReceiveFilter.

  • Le 6 passé dans la classe parentconstructeur représente la longueur de l'en-tête ;

  • La méthode "GetBodyLengthFromHeader(...)" doit renvoyer la longueur du corps de la requête en fonction de l'en-tête reçu ;

  • La méthode ; ResolveRequestInfo(. ...)" devrait renvoyer une instance de votre type de requête en fonction de l'en-tête et du corps de la requête que vous avez reçus.

Utilisation réelle scénarios :

Vous avez déjà compris les modèles des cinq protocoles et connaissez le traitement du format pertinent. Examinons ensuite un exemple de réseau :

Format du protocole de communication :

.

在看到上图协议是在纠结客户端发送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 = &#39;&&#39;;
    private readonly static char strEnd = &#39;#&#39;;
    //开始和结束标记也可以是两个或两个以上的字节
    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("服务启动成功,请按&#39;E&#39;停止服务!");
      while (Console.ReadKey().KeyChar != &#39;E&#39;)
      {
        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进制报文,服务器输出结果:

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn