首页 > 其他分享 >Net6/SuperSocket通过命令和命令过滤器实现服务端/客户端双工通信

Net6/SuperSocket通过命令和命令过滤器实现服务端/客户端双工通信

时间:2023-02-01 15:47:41浏览次数:71  
标签:双工 Console SuperSocket 命令 session 过滤器 using public

十年河东,十年河西,莫欺少年穷

学无止境,精益求精

和上一节一致,通信数据结构如下:

        /// +-------+---+-------------------------------+
        /// |request| l |                               |
        /// | type  | e |    request body               |
        /// |  (1)  | n |                               |
        /// |       |(2)|                               |
        /// +-------+---+-------------------------------+

关于superSocket命令及命令过滤器相关的基础知识大家可参考:https://docs.supersocket.net/v2-0/zh-CN/Command-and-Command-Filter

需要说明的是,在使用命令及命令过滤器之前,系统代码其他地方不能使用带有session参数的方法,譬如:

不能使用

.UsePackageHandler(async (session, package) =>

和 

.UseSessionHandler(async (session) =>

之所以不能使用这些方法,是因为session代表通信管道,一个session对象意味着一个客户端,如果系统多处出现session参数,则意味着只有一个session对应客户端,其他session是无效的,如果使用无效的session发送消息给客户端,客户端无法收到

本篇为双工通信,因此分为服务端和客户端

1、服务端构造

1.1、新建控制台应用程序

并引入如下Nuget包

 

 

 1.2、新增配置文件

superSocket默认从appsettings.json中读取配置,这点不清楚可参考:https://docs.supersocket.net/v2-0/zh-CN/Start-SuperSocket-by-Configuration

appsettings.json如下:

{
  "serverOptions": {
    "name": "GameMsgServer",
    "listeners": [
      {
        "ip": "Any",
        "port": "4040"
      },
      {
        "ip": "127.0.0.1",
        "port": "8040"
      }
    ]
  }
}

//配置项目
//name: 服务器的名称;
//maxPackageLength: 此服务器允许的最大的包的大小; 默认4M;
//receiveBufferSize: 接收缓冲区的大小; 默认4k;
//sendBufferSize: 发送缓冲区的大小; 默认4k;
//receiveTimeout: 接收超时时间; 微秒为单位;
//sendTimeout: 发送超时的事件; 微秒为单位;
//listeners: 服务器的监听器;
//listeners/*/ip: 监听IP; Any: 所有 ipv4 地址, IPv6Any: 所有 ipv6 地址, 其它具体的IP地址;
//listeners/*/port: 监听端口;
//listeners/*/backLog: 连接等待队列的最大长度;
//listeners/*/noDelay: 定义 Socket 是否启用 Nagle 算法;
//listeners/*/security: None/Ssl3/Tls11/Tls12/Tls13; 传输层加密所使用的TLS协议版本号;
//listeners/*/certificateOptions: 用于TLS加密/揭秘的证书的配置项目;
View Code

1.3、新增包类型及包类型管道过滤器

需要注意的是,在使用命令进行通信的包类型必须继承自IKeyedPackageInfo<T>

包类型如下:

    public class MyPackage: IKeyedPackageInfo<int>
    {
        public int Key { get; set; }
        public int BodyLength { get; set; }
        public string Body { get; set; } 
    }

这里面的Key值对应命令指定的key值,下面还会强调这一点

包类型管道过滤器如下:

using MySupersocket.Package;
using SuperSocket.ProtoBase;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MySupersocket.Filters
{ 

    /// <summary>
    /// 过滤器
    /// </summary>
    public class MyPackageFilter : FixedHeaderPipelineFilter<MyPackage>
    {
        /// +-------+---+-------------------------------+
        /// |request| l |                               |
        /// | type  | e |    request body               |
        /// |  (1)  | n |                               |
        /// |       |(2)|                               |
        /// +-------+---+-------------------------------+
        public MyPackageFilter() : base(3)// // 包头的大小是3字节,所以将3传如基类的构造方法中去
        {

        }
        // 从数据包的头部返回包体的大小
        protected override int GetBodyLengthFromHeader(ref ReadOnlySequence<byte> buffer)
        {
            var reader = new SequenceReader<byte>(buffer);
            reader.Advance(1); // skip the first byte
            reader.TryReadBigEndian(out short len);
            return len;
        }
        // 将数据包解析成 MyPackage 的实例
        // 将数据包解析成 MyPackage 的实例
        protected override MyPackage DecodePackage(ref ReadOnlySequence<byte> buffer)
        {
            var package = new MyPackage();

            var reader = new SequenceReader<byte>(buffer);

            reader.TryRead(out byte packageKey);
            package.Key = packageKey;
            reader.Advance(2); // skip the length
                               // 
            var sas = buffer.ToArray();
            package.BodyLength = BitConverter.ToUInt16(sas.Skip(1).Take(2).Reverse().ToArray());
            var body = string.Empty;//功能码
            foreach (var item in sas)
            {
                body += item.ToString("x2");
            }
            body = body.ToLower();
            package.Body = body;
            return package;
        }
    }
}
View Code

包类型过滤器的主要作用是用于接收到包的解析,其提供了一些重载方法,也可以自定义自己的方法,这里使用的是固定包头长度类型

1.4、定义命令及命令过滤器

using MySupersocket.Package;
using SuperSocket;
using SuperSocket.Command;
using SuperSocket.ProtoBase;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MySupersocket.Command
{
    /// <summary>
    /// 命令基类
    /// </summary>
    public abstract class BaseCommand : IAsyncCommand<MyPackage>
    {
        /// <summary>
        /// 进行适配的命令后,会立即执行该方法
        /// </summary>
        /// <param name="session"></param>
        /// <param name="package"></param>
        /// <returns></returns>
        public async ValueTask ExecuteAsync(IAppSession session, MyPackage package)
        {  
            var result = GetResult(package);

            //发送消息给客户端
            await session.SendAsync(result);
        } 
        /// <summary>
        /// 抽象方法,用于重写
        /// </summary>
        /// <param name="package"></param>
        /// <returns></returns>
        public abstract byte[] GetResult(MyPackage package);
    }

    /// <summary>
    /// 接收key值为0x01的
    /// </summary>
    [SendKey01CommandFilter]
    [Command(Key = 0x01, Name = "SendKey01")]
    public class SendKey01 : BaseCommand
    {
        public override byte[] GetResult(MyPackage package)
        {
            return new byte[] { 0x01, 0x00, 0x03, 0x1a, 0x1b, 0x1c };
        }
      
    }
    [SendKey02CommandFilter]
    [Command(Key = 0x02, Name = "SendKey02")]
    public class SendKey02 : BaseCommand
    {
        public override byte[] GetResult(MyPackage package)
        {
            return new byte[] { 0x02, 0x00, 0x03, 0x2a, 0x2b, 0x2c };
        }
    }
    [SendKey03CommandFilter]
    [Command(Key = 0x03, Name = "SendKey03")]
    public class SendKey03 : BaseCommand
    {
        public override byte[] GetResult(MyPackage package)
        {
            return new byte[] { 0x03, 0x00, 0x03, 0x3a, 0x3b, 0x3c };
        }
    }

    //
    public class SendKey01CommandFilterAttribute : CommandFilterAttribute
    {
        public override bool OnCommandExecuting(CommandExecutingContext commandContext)
        {
            Console.WriteLine("SendKey01命令过滤器开始执行");
            Console.WriteLine("进入SendKey01方法之前执行...");

            return true;
        }
        public override void OnCommandExecuted(CommandExecutingContext commandContext)
        {
            Console.WriteLine("进入SendKey01方法之后执行...");
            Console.WriteLine("SendKey01命令过滤器执行结束");
        }

    }

    //
    public class SendKey02CommandFilterAttribute : CommandFilterAttribute
    {
        public override bool OnCommandExecuting(CommandExecutingContext commandContext)
        {
            Console.WriteLine("SendKey02命令过滤器开始执行");
            Console.WriteLine("进入SendKey02方法之前执行...");

            return true;
        }
        public override void OnCommandExecuted(CommandExecutingContext commandContext)
        {
            Console.WriteLine("进入SendKey02方法之后执行...");
            Console.WriteLine("SendKey02命令过滤器执行结束");
        } 
    }

    //
    public class SendKey03CommandFilterAttribute : CommandFilterAttribute
    {
        public override bool OnCommandExecuting(CommandExecutingContext commandContext)
        {
            Console.WriteLine("SendKey03命令过滤器开始执行");
            Console.WriteLine("进入SendKey03方法之前执行...");

            return true;
        }
        public override void OnCommandExecuted(CommandExecutingContext commandContext)
        {
            Console.WriteLine("进入SendKey03方法之后执行...");
            Console.WriteLine("SendKey03命令过滤器执行结束");
        } 
    }

    /// <summary>
    /// 全局命令过滤器
    /// </summary>
    public class GlobalCommandFilterAttribute : CommandFilterAttribute
    {
        public override bool OnCommandExecuting(CommandExecutingContext commandContext)
        {
            Console.WriteLine("全局命令过滤器开始执行");

            return true;
        }
        public override void OnCommandExecuted(CommandExecutingContext commandContext)
        { 
            Console.WriteLine("全局命令过滤器结束执行");
        }
    }
}
View Code

这里需要解读的是:

    [SendKey01CommandFilter]
    [Command(Key = 0x01, Name = "SendKey01")]

SendKey01CommandFilter 为命令过滤器,必须继承自:CommandFilterAttribute,其提供两个方法

OnCommandExecuting  为进入命令之前执行

OnCommandExecuted   为命令执行完后执行

[Command(Key = 0x03, Name = "SendKey03")] 为命令执行key值,这里指定的key值为十六进制0x03,其对应包类型MyPackage中的key,MyPackage中key定义为int是因为C#会将byte自动转为int,当然,你可以将命令和包类型的key值都是用字符串,在包类型中将byte类型的key值转为字符串即可。

1.5、Program.cs代码如下:

using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MySupersocket.Command;
using MySupersocket.Filters;
using MySupersocket.Package;
using Newtonsoft.Json;
using SuperSocket;
using SuperSocket.Command;
using SuperSocket.ProtoBase;

namespace MySupersocket // Note: actual namespace depends on the project name.
{
    internal class Program
    {
        static async Task Main(string[] args)
        { 
            var host = SuperSocketHostBuilder
     .Create<MyPackage, MyPackageFilter>()
     //.UsePackageHandler(async (session, package) =>
     //{
     //    byte[] result = new byte[1];
     //    var GnmCode = string.Empty;//功能码
     //    foreach (var item in new List<byte>() { package.Key })
     //    {
     //        GnmCode += item.ToString("x2");
     //    }
     //    switch (GnmCode.ToLower())
     //    {
     //        case ("01"):
     //            result = new byte[] { 0x01, 0x00, 0x03, 0x1a, 0x1b, 0x1c };
     //            break;

     //        case ("02"):
     //            result = new byte[] { 0x02, 0x00, 0x03, 0x2a, 0x2b, 0x2c };
     //            break;

     //        case ("03"):
     //            result = new byte[] { 0x03, 0x00, 0x03, 0x3a, 0x3b, 0x3c };
     //            break;
     //    }

     //    await session.SendAsync(new ReadOnlyMemory<byte>(result));
     //})
     //.UseSessionHandler(async (session) =>
     //{
     //    Console.WriteLine($"session connected: {session.RemoteEndPoint}");

     //    //发送消息给客户端
     //    var msg = $@"Welcome to TelnetServer: {session.RemoteEndPoint}";
     //    await session.SendAsync(Encoding.UTF8.GetBytes(msg + "\r\n"));
     //}, async (session, reason) =>
     //{
     //    await Task.Delay(0);
     //    Console.WriteLine($"session {session.RemoteEndPoint} closed: {reason}");
     //})

     .UseCommand((commandOptions) =>
     {
         // 一个一个的注册命令
         commandOptions.AddCommand<SendKey01>();
         commandOptions.AddCommand<SendKey02>();
         commandOptions.AddCommand<SendKey03>();
         commandOptions.AddGlobalCommandFilter<GlobalCommandFilterAttribute>(); 
     })
     .ConfigureLogging((hostCtx, loggingBuilder) =>
     {
         loggingBuilder.AddConsole();
     }).Build();
            await host.RunAsync();
            Console.Read();
        }
    }
}
View Code

需要说明的是,

上述代码中注释掉的代码不能使用,因为在命令中使用了参数session,session代表客户端,一旦使用了上述注释的代码,则命令中session不生效,从而导致命令中发送消息给客户端的方法因找不到客户端发送失败。

其他代码无非就是注册命令和全局命令过滤器

2、客户端

客户端和上一节使用的客户端一致,代码几乎无变化,可参考:Net6/SuperSocket服务端和客户端双工交互, 自定义包类型及实现你的 PipelineFilter

项目整体测试如下:

客户端:

服务器

 

@天才卧龙的博客

标签:双工,Console,SuperSocket,命令,session,过滤器,using,public
From: https://www.cnblogs.com/chenwolong/p/command.html

相关文章

  • java/Android获取单个文件的MD5值,解决首位0被省略问题,解决超大文件问题,cmd命令行查看
    code来源:https://betheme.net/news/txtlist_i67135v.html?action=onClickcmd命令行查看文件md5码:certutil-hashfilea.txtmd5,不加后面的md5,查看的默认是sha1码。packag......
  • STATA基本命令
    文件路径命令命令1:pwd//显示当前工作路径命令2:dir//显示当前路径下所有文件/文件夹命令3:cd“文件夹路径”//更改工作文件路径use“文件名.dta"//从当前文件路......
  • Mongodb常用命令
    最近经常用到mongodb,现把常用命令汇总如下:1、创建集合db.createCollection("modb_test")2、复制集合(同库复制)1db.modb_test.find().forEach(function(x){db.modb_test2.ins......
  • mysql-mysqldump命令详解
    注意:备份加入--databases备份文件中会有创建数据库的语句,否则没有建议:导出库加入、导出表不加此参数一、mysqldump命令备份Mysql数据库的参数说明在用mysqldump备份使用......
  • 通过TPT命令行自动化执行测试
    前言  随着DevOps、CI/CT在汽车行业日趋成熟和普及,TPT支持通过命令行的方式完成测试执行过程。TPT作为德国PikeTec公司的嵌入式软件模型测试工具,具有很高的扩展性和便......
  • 9.搜索查找命令
      查找当前目录下的文件  查找规定目录下的文件   *在shell中表示通配符,表示零个或多个字符。比如当前目录下有abc.c、abdd.c、abeff.c。可以用lsab*.c来......
  • docker常用的命令
    一、常规命令1、启动Dockersudosystemctlstartdocker2、停止Dockersudosystemctlstopdocker3、重启Dockersudosystemctlrestartdocker4、修改配置后重启Docker......
  • 8.用户权限命令
      -u指定用户的UID;-G指定用户的附加组普通用户处于home目录下:  useradd-d路径用户名用户名david当前用户david的主目录名为dave    设置用户密码,密......
  • 常用的DOS命令
    盘符切换查看当前目录的所有文件切换目录cdchangedirectorycd..清理屏幕cls退出终端exit查看电脑ipipconfig打开应用calcmspaintnotepadping命令ping......
  • cmake命令之option使用案例
    option的命令形式如下option(<variable>"<help_text>"[value]) option简介    cmake中option起到编译开关的作用,CMakeLists.txt中option以前的语句,变量......