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}"); } } }
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; } } }
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加密/揭秘的证书的配置项目;
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、整体测试如下
启动服务端
启动客户端
连接服务端
发送数据
@天才卧龙的博客