十年河東,十年河西,莫欺少年窮
學無止境,精益求精
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、整體測試如下
啓動服務端
啓動客戶端
連接服務端
發送數據
@天才臥龍的博客