Net6/SuperSocket服务端和客户端双工交互, 自定义包类型及实现你的 PipelineFilter

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

学无止境,精益求精

1、定义包类型及PipelineFilter

双工通信中,客户端和服务端双方一般都会协商好数据格式,譬如

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

RequestType为请求类型,根据不同的请求类型,发送不同的数据包

len:为数据包requestbody长度

requestbody:为数据包

根据上述通信格式,我们定义如下包类型及消息处理管道,所谓消息处理管道可以理解为业务处理逻辑

    /// <summary>
    /// 包类型
    /// </summary>
    public class MyPackage
    {
        public byte Key { get; set; }

        public int BodyLength { get; set; }

        public string Body { get; set; }
    }

    /// <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 的实例
        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;
        }
    }

2、SuperSocket客户端

新建winform应用程序,并引入superSocket.Client 2.0版本

 

 2.1、准备窗体,如下

 

 2.2、创建类TcpSupersocket

类TcpSupersocket包含连接服务器方法、发送数据方法,释放方法等,如下:

    /// +-------+---+-------------------------------+
    /// |request| l |                               |
    /// | type  | e |    request body               |
    /// |  (1)  | n |                               |
    /// |       |(2)|                               |
    /// +-------+---+-------------------------------+
    public class TcpSupersocket
    { 
        public static object o = new object();
        public static TcpSupersocket? Instance;
        public static bool InstanceConnectState = false;

        public static IEasyClient<MyPackage> mClient = new EasyClient<MyPackage>(new MyPackageFilter()).AsClient();


        public static TcpSupersocket GetInstance()
        {
            if (Instance == null)
            {
                Instance = new TcpSupersocket();
            }
            return Instance;
        } 

        public async Task<bool> Connect(string TCPTestIP,int TCPTestPort)
        { 
            bool isConnect = true;
            int i = 0;
            //每隔1秒,重连三次,测试
            while (!await mClient.ConnectAsync(new IPEndPoint(IPAddress.Parse(TCPTestIP), TCPTestPort))&&i<3)
            {
                isConnect = false; 
                i++;
                await Task.Delay(1000);
            };

            InstanceConnectState = isConnect;
            return isConnect;

        }


        public async Task<bool> CloseConnect()
        {
            await mClient.CloseAsync();
            InstanceConnectState = false;
            return true;
        }



        public void Send(string cmd)
        {
            try
            {
                byte[] byteArray = System.Text.Encoding.ASCII.GetBytes(cmd);
                mClient.SendAsync(byteArray);
                Console.WriteLine($"【TCPClient】 发送数据  [{cmd}]");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"【TCPClient】 发送数据异常  {cmd} {ex.Message}");

            }
        }

        public void SendBytes(byte[] data)
        {
            try
            {
                lock (o)
                {
                    mClient.SendAsync(data);
                    Console.WriteLine($"【TCPClient】 发送数据成功,长度  [{data.Length}]");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"SuperSocket发送数据异常  {ex.Message}");
            }
        }
         
    }
View Code

2.3、窗体加载方法

        public Form1()
        {
            InitializeComponent();
        }
        public TcpSupersocket instance  ;

        private void Form1_Load(object sender, EventArgs e)
        {
            instance = TcpSupersocket.GetInstance();
        }

2.4、连接按钮方法

连接到服务器并侦听接收消息

        //连接server并侦听接收消息
        private async void buttonConn_Click(object sender, EventArgs e)
        {
            string msg = string.Empty;
            string TCPTestIP = IpTxt.Text.Trim();
            int TCPTestPort = int.Parse(portTxt.Text.Trim());
            bool isConnect = await instance.Connect(IpTxt.Text, int.Parse(portTxt.Text));
            if (!isConnect)
            {
                dataTxt.Text += $"连接失败,Ip:{TCPTestIP},端口号:{TCPTestPort}\r\n";
                return;
            }
            else
            {
                dataTxt.Text += $"连接成功,Ip:{TCPTestIP},端口号:{TCPTestPort}\r\n";
            }
            while (true && TcpSupersocket.InstanceConnectState)
            {
                var pp = await TcpSupersocket.mClient.ReceiveAsync();
                if (pp != null)
                {
                    try
                    {
                        string key = BitConverter.ToString(new byte[] { pp.Key });
                        //  System.Text.Encoding.UTF8.GetString(pp.Key);
                        // msg = $"requestType{key}接收到数据: {pp.Body}  \r\n";  
                        msg = $"接收到数据:{JsonConvert.SerializeObject(pp)}  \r\n";
                        dataTxt.Text += msg;

                    }
                    catch (Exception ex)
                    {
                        msg = $"【TCPClient】 接收到数据异常 {ex.Message}   \r\n";   
                        dataTxt.Text += msg;
                    }
                }
                else
                {
                    dataTxt.Text += "连接已断开 \r\n";
                }
            }
        }

2.5、发送数据按钮方法

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void buttonSend_Click(object sender, EventArgs e)
        {
            string bys = sentDataTxt.Text.Trim();
            var data = GetByteByString(bys).ToArray();
            instance.SendBytes(data);
            dataTxt.Text += "发送数据Success:" + bys+ "\r\n";
        }

        /// <summary>
        /// Bys示例:7e7e 
        /// </summary>
        /// <param name="Bys">字符串转16进制</param>
        /// <returns>[0x7e,0x7e]</returns>
        private List<byte> GetByteByString(string Bys)
        {
            var arty = Bys.ToArray();
            List<byte> lst = new List<byte>();
            for (int i = 0; i < arty.Length / 2; i++)
            {
                string byStr = string.Empty;
                foreach (var item in arty.Skip(i * 2).Take(2).ToList())
                {
                    byStr += item.ToString();
                }
                byStr = "0x" + byStr;
                var bts = Convert.ToByte(byStr, 16);
                lst.Add(bts);
            }
            return lst;
        }

2.6、断开连接按钮方法

        private  async void buttonClose_Click(object sender, EventArgs e)
        {
            await instance.CloseConnect();
        }

3、SuperSocket服务器端

3.1、新建控制台应用程序并引入SuperSocket.Server

 

 3.2、自定义包类型及管道过滤器【和客户端一致】

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 MyPackage
    {
        public byte Key { get; set; }
        public int BodyLength { get; set; }
        public string Body { get; set; }
    }

    /// <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

3.3、增加配置文件: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

3.4、Program.cs代码如下

using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MySupersocket.Filters;
using MySupersocket.Package;
using Newtonsoft.Json;
using SuperSocket;
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;
         }
         // \r\n 为键盘回车换行
         await session.SendAsync(new ReadOnlyMemory<byte>(result));
     })
     .ConfigureLogging((hostCtx, loggingBuilder) =>
     {
         loggingBuilder.AddConsole();
     }).Build();
            await host.RunAsync();
            Console.Read();
        }
    }
}

由代码可知,

接收到key值为 01 时,向客户端发送 : 0x01,0x00,0x03,0x1a,0x1b,0x1c

接收到key值为 02 时,向客户端发送 : 0x02,0x00,0x03,0x2a,0x2b,0x2c

接收到key值为 03 时,向客户端发送 : 0x03,0x00,0x03,0x3a,0x3b,0x3c

4、整体测试如下

启动服务端

 

 启动客户端

 

 连接服务端

 

 

 

 发送数据

 

 @天才卧龙的博客

posted @ 2023-01-31 16:27  天才卧龙  阅读(1637)  评论(0编辑  收藏  举报