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、整體測試如下

啓動服務端

 

 啓動客戶端

 

 連接服務端

 

 

 

 發送數據

 

 @天才臥龍的博客

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章