C# 常用協(xié)議實現(xiàn)模版及FixedSizeReceiveFilter示例(SuperSocket入門)
Socket里面的協(xié)議解析是Socket通訊程序設計中最復雜的地方,如果你的應用層協(xié)議設計或實現(xiàn)不佳,Socket通訊中常見的粘包,分包就難以避免。SuperSocket內置了命令行格式的協(xié)議CommandLineProtocol,如果你使用了其它格式的協(xié)議,就必須自行實現(xiàn)自定義協(xié)議CustomProtocol??戳艘黄臋n之后, 你可能會覺得用 SuperSocket 來實現(xiàn)你的自定義協(xié)議并不簡單。 為了讓這件事變得更容易一些, SuperSocket 提供了一些通用的協(xié)議解析工具, 你可以用他們簡單而且快速的實現(xiàn)你自己的通信協(xié)議:
- TerminatorReceiveFilter (SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter, SuperSocket.SocketBase) ---結束符協(xié)議
- CountSpliterReceiveFilter (SuperSocket.Facility.Protocol.CountSpliterReceiveFilter, SuperSocket.Facility)---固定數(shù)量分隔符協(xié)議
- FixedSizeReceiveFilter (SuperSocket.Facility.Protocol.FixedSizeReceiveFilter, SuperSocket.Facility)---固定請求大小協(xié)議
- BeginEndMarkReceiveFilter (SuperSocket.Facility.Protocol.BeginEndMarkReceiveFilter, SuperSocket.Facility)---帶起止符協(xié)議
- FixedHeaderReceiveFilter (SuperSocket.Facility.Protocol.FixedHeaderReceiveFilter, SuperSocket.Facility)---頭部格式固定并包含內容長度協(xié)議
1、TerminatorReceiveFilter結束符協(xié)議
結束符協(xié)議和命令行協(xié)議類似,一些協(xié)議用結束符來確定一個請求.例如, 一個協(xié)議使用兩個字符 "##" 作為結束符, 于是你可以使用類 "TerminatorReceiveFilterFactory":
結束符協(xié)議TerminatorProtocolServer :
public class TerminatorProtocolServer : AppServer { public TerminatorProtocolServer() : base(new TerminatorReceiveFilterFactory("##")) { } }
基于TerminatorReceiveFilter實現(xiàn)你的接收過濾器(ReceiveFilter):
public class YourReceiveFilter : TerminatorReceiveFilter<YourRequestInfo> { //More code }
實現(xiàn)你的接收過濾器工廠(ReceiveFilterFactory)用于創(chuàng)建接受過濾器實例:
public class YourReceiveFilterFactory : IReceiveFilterFactory<YourRequestInfo> { //More code }
2、CountSpliterReceiveFilter 固定數(shù)量分隔符協(xié)議
有些協(xié)議定義了像這樣格式的請求 "#part1#part2#part3#part4#part5#part6#part7#". 每個請求有7個由 '#' 分隔的部分. 這種協(xié)議的實現(xiàn)非常簡單:
/// <summary> /// 請求格式:#part1#part2#part3#part4#part5#part6#part7# /// </summary> public class CountSpliterAppServer : AppServer { public CountSpliterAppServer() : base(new CountSpliterReceiveFilterFactory((byte)'#', 8)) //8個分隔符,7個參數(shù)。除使用默認的過濾工廠,還可以參照上一個實例定制協(xié)議 { } }
3、FixedSizeReceiveFilter 固定請求大小協(xié)議
在這種協(xié)議之中, 所有請求的大小都是相同的。如果你的每個請求都是有8個字符組成的字符串,如"HUANG LI", 你應該做的事就是想如下代碼這樣實現(xiàn)一個接收過濾器(ReceiveFilter):
class MyReceiveFilter : FixedSizeReceiveFilter<StringRequestInfo> { public MyReceiveFilter() : base(8) //傳入固定的請求大小 { } protected override StringRequestInfo ProcessMatchedRequest(byte[] buffer, int offset, int length, bool toBeCopied) { //TODO: 通過解析到的數(shù)據(jù)來構造請求實例,并返回 } }
然后在你的 AppServer 類中使用這個接受過濾器 (ReceiveFilter):
public class MyAppServer : AppServer { public MyAppServer() : base(new DefaultReceiveFilterFactory<MyReceiveFilter, StringRequestInfo>()) //使用默認的接受過濾器工廠 (DefaultReceiveFilterFactory) { } }
4、BeginEndMarkReceiveFilter 帶起止符協(xié)議
在這類協(xié)議的每個請求之中 都有固定的開始和結束標記。例如, 我有個協(xié)議,它的所有消息都遵循這種格式 "&xxxxxxxxxxxxxx#"。因此,在這種情況下, "&" 是開始標記, "#" 是結束標記,于是你的接受過濾器可以定義成這樣:
class MyReceiveFilter : BeginEndMarkReceiveFilter<StringRequestInfo> { //開始和結束標記也可以是兩個或兩個以上的字節(jié) private readonly static byte[] BeginMark = new byte[] { (byte)'&' }; private readonly static byte[] EndMark = new byte[] { (byte)'#' }; public MyReceiveFilter() : base(BeginMark, EndMark) //傳入開始標記和結束標記 { } protected override StringRequestInfo ProcessMatchedRequest(byte[] readBuffer, int offset, int length) { //TODO: 通過解析到的數(shù)據(jù)來構造請求實例,并返回 } }
然后在你的 AppServer 類中使用這個接受過濾器 (ReceiveFilter):
public class MyAppServer : AppServer { public MyAppServer() : base(new DefaultReceiveFilterFactory<MyReceiveFilter, StringRequestInfo>()) //使用默認的接受過濾器工廠 (DefaultReceiveFilterFactory) { } }
5、FixedHeaderReceiveFilter 頭部格式固定并包含內容長度協(xié)議
這種協(xié)議將一個請求定義為兩大部分, 第一部分定義了包含第二部分長度等等基礎信息. 我們通常稱第一部分為頭部.
例如, 我們有一個這樣的協(xié)議: 頭部包含 6 個字節(jié), 前 4 個字節(jié)用于存儲請求的名字, 后兩個字節(jié)用于代表請求體的長度:
/// +-------+---+-------------------------------+
/// |request| l | |
/// | name | e | request body |
/// | (4) | n | |
/// | |(2)| |
/// +-------+---+-------------------------------+
使用 SuperSocket, 你可以非常方便的實現(xiàn)這種協(xié)議:
class MyReceiveFilter : FixedHeaderReceiveFilter<BinaryRequestInfo> { public MyReceiveFilter() : base(6) { } protected override int GetBodyLengthFromHeader(byte[] header, int offset, int length) { return (int)header[offset + 4] * 256 + (int)header[offset + 5]; } protected override BinaryRequestInfo ResolveRequestInfo(ArraySegment<byte> header, byte[] bodyBuffer, int offset, int length) { return new BinaryRequestInfo(Encoding.UTF8.GetString(header.Array, header.Offset, 4), bodyBuffer.CloneRange(offset, length)); } }
你需要基于類FixedHeaderReceiveFilter實現(xiàn)你自己的接收過濾器.
- 傳入父類構造函數(shù)的 6 表示頭部的長度;
- 方法"GetBodyLengthFromHeader(...)" 應該根據(jù)接收到的頭部返回請求體的長度;
- 方法 ResolveRequestInfo(....)" 應該根據(jù)你接收到的請求頭部和請求體返回你的請求類型的實例.
實際使用場景:
到這里五種協(xié)議的模板你都已經了解了一遍,并且知道了相關的格式處理。接下來看一個網絡示例:
通訊協(xié)議格式:
在看到上圖協(xié)議是在糾結客戶端發(fā)送16進制,服務器怎么接收,16進制的報文如下:
26 01 00 19 4E 4A 30 31 31 01 44 41 31 31 32 00 07 00 00 00 00 00 00 34 23
16進制也好,10進制也好,其他的進制也好,最終都是轉換成byte[],其實在處理數(shù)據(jù)時,發(fā)送過去的數(shù)據(jù)都是可以轉換成為byte[]的,所以服務的只要解析byte[]數(shù)組就行了。按照協(xié)議來解析就能得到想要的數(shù)據(jù)。下面使用FixedSizeReceiveFilter的例子,代碼如下:
根據(jù)上面的通訊協(xié)議,開始來實現(xiàn)解析:
第一步、定義一個和協(xié)議合適的數(shù)據(jù)結構
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; /**************************************************************** * 作者:黃昏前黎明后 * CLR版本:4.0.30319.42000 * 創(chuàng)建時間:2017-01-23 21:12:30 * 2017 * 描述說明:協(xié)議數(shù)據(jù)包 * * 修改歷史: * * *****************************************************************/ namespace SuperSocketDemo { public class HLData { /// <summary> /// 開始符號 /// </summary> public char Head { get; set; } /// <summary> /// 協(xié)議包數(shù)據(jù) /// </summary> public byte Ping { get; set; } /// <summary> /// 數(shù)據(jù)長度 /// </summary> public ushort Lenght { get; set; } /// <summary> /// 終端ID /// </summary> public uint FID { get; set; } /// <summary> /// 目標類型 /// </summary> public byte Type { get; set; } /// <summary> /// 轉發(fā)終端ID /// </summary> public uint SID { get; set; } /// <summary> /// 發(fā)送計數(shù) /// </summary> public ushort SendCount { get; set; } /// <summary> /// 保留字段 /// </summary> public byte[] Retain { get; set; } /// <summary> /// 異或校驗 /// </summary> public byte Check { get; set; } /// <summary> /// 結束符號 /// </summary> public char End { get; set; } public override string ToString() { return string.Format("開始符號:{0},包數(shù)據(jù):{1},數(shù)據(jù)長度:{2},終端ID:{3},目標類型:{4},轉發(fā)終端ID:{5},發(fā)送包計數(shù):{6},保留字段:{7},異或校驗:{8},結束符號:{9}", Head, Ping, Lenght, FID, Type, SID, SendCount, Retain, Check, End); } } } HLData
第二步、建立一個RequestInfo來給server數(shù)據(jù)接收
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using SuperSocket.SocketBase.Protocol; /**************************************************************** * 作者:黃昏前黎明后 * CLR版本:4.0.30319.42000 * 創(chuàng)建時間:2017-01-22 21:03:31 * 2017 * 描述說明:數(shù)據(jù)請求 * * 修改歷史: * * *****************************************************************/ namespace SuperSocketDemo { public class HLProtocolRequestInfo : RequestInfo<HLData> { public HLProtocolRequestInfo(HLData hlData) { //如果需要使用命令行協(xié)議的話,那么命令類名稱HLData相同 Initialize("HLData", hlData); } } } HLProtocolRequestInfo 類
第三步、FixedSize協(xié)議解析
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using SuperSocket.SocketBase.Protocol; using SuperSocket.Facility.Protocol; using SuperSocket.Common; /**************************************************************** * 作者:黃昏前黎明后 * CLR版本:4.0.30319.42000 * 創(chuàng)建時間:2017-01-22 21:06:01 * 2017 * 描述說明:協(xié)議解析類,固定請求大小的協(xié)議 * * 修改歷史: * * *****************************************************************/ namespace SuperSocketDemo { /// <summary> /// 固定請求大小的協(xié)議,(幀格式為HLProtocolRequestInfo) /// </summary> public class HLProtocolReceiveFilter : FixedSizeReceiveFilter<HLProtocolRequestInfo> { public HLProtocolReceiveFilter() : base(25)//總的字節(jié)長度 1+1+2+5+1+5+2+6+1+1 = 25 { } protected override HLProtocolRequestInfo ProcessMatchedRequest(byte[] buffer, int offset, int length, bool toBeCopied) { var HLData = new HLData(); HLData.Head = (char)buffer[offset];//開始標識的解析,1個字節(jié) HLData.Ping = buffer[offset + 1];//數(shù)據(jù),從第2位起,只有1個字節(jié) HLData.Lenght = BitConverter.ToUInt16(buffer, offset + 2);//數(shù)據(jù)長度,從第3位開始,2個字節(jié) HLData.FID = BitConverter.ToUInt32(buffer, offset + 4);//本終端ID,從第5位開始,5個字節(jié) HLData.Type = buffer[offset + 9];//目標類型,從第10位開始,1個字節(jié) HLData.SID = BitConverter.ToUInt32(buffer, offset + 10);//轉發(fā)終端ID,從第11位開始,5個字節(jié) HLData.SendCount = BitConverter.ToUInt16(buffer, offset + 15);//發(fā)送包計數(shù),從第16位開始,2個字節(jié) HLData.Retain = buffer.CloneRange(offset + 17, 6);//保留字段,從18位開始,6個字節(jié) HLData.Check = buffer[offset + 23];//異或校驗,從24位開始,1個字節(jié) HLData.End = (char)buffer[offset + 24];//結束符號,從第25位開始,一個字節(jié) return new HLProtocolRequestInfo(HLData); } } } HLProtocolReceiveFilter類
第四步、建立協(xié)議工廠HLReceiveFilterFactory
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using SuperSocket.SocketBase; using SuperSocket.SocketBase.Protocol; using System.Net; /**************************************************************** * 作者:黃昏前黎明后 * CLR版本:4.0.30319.42000 * 創(chuàng)建時間:2017-01-23 :22:01:25 * 2017 * 描述說明:協(xié)議工廠 * * 修改歷史: * * *****************************************************************/ namespace SuperSocketDemo { public class HLReceiveFilterFactory: IReceiveFilterFactory<HLProtocolRequestInfo> { public IReceiveFilter<HLProtocolRequestInfo> CreateFilter(IAppServer appServer, IAppSession appSession, IPEndPoint remoteEndPoint) { return new HLBeginEndMarkReceiveFilter(); } } } HLReceiveFilterFactory類
第五步、自定義HLProtocolSession繼承AppSession
using SuperSocket.SocketBase; using SuperSocket.SocketBase.Protocol; using System; /**************************************************************** * 作者:黃昏前黎明后 * CLR版本:4.0.30319.42000 * 創(chuàng)建時間:2017-01-22 21:15:11 * 2017 * 描述說明:自定義HLProtocolSession * * 修改歷史: * * *****************************************************************/ namespace SuperSocketDemo { public class HLProtocolSession : AppSession<HLProtocolSession, HLProtocolRequestInfo> { protected override void HandleException(Exception e) { } } } HLProtocolSession類
第六步、自定義HLProtocolServer繼承AppServer
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using SuperSocket.SocketBase; using SuperSocket.SocketBase.Protocol; /**************************************************************** * 作者:黃昏前黎明后 * CLR版本:4.0.30319.42000 * 創(chuàng)建時間:2017-01-22 21:16:57 * 2017 * 描述說明:自定義server * * 修改歷史: * * *****************************************************************/ namespace SuperSocketDemo { public class HLProtocolServer : AppServer<HLProtocolSession, HLProtocolRequestInfo> { /// <summary> /// 使用自定義協(xié)議工廠 /// </summary> public HLProtocolServer() : base(new HLReceiveFilterFactory()) { } } } HLProtocolServer類
第七步、加上起止符協(xié)議HLBeginEndMarkReceiveFilter
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using SuperSocket.Common; using SuperSocket.Facility.Protocol; /**************************************************************** * 作者:黃昏前黎明后 * CLR版本:4.0.30319.42000 * 創(chuàng)建時間:2017-01-23 22:07:03 * 2017 * 描述說明:帶起止符的協(xié)議, "&" 是開始標記, "#" 是結束標記,開始結束標記由自己定義 * * 修改歷史: * * *****************************************************************/ namespace SuperSocketDemo { public class HLBeginEndMarkReceiveFilter : BeginEndMarkReceiveFilter<HLProtocolRequestInfo> { private readonly static char strBegin = '&'; private readonly static char strEnd = '#'; //開始和結束標記也可以是兩個或兩個以上的字節(jié) private readonly static byte[] BeginMark = new byte[] { (byte)strBegin }; private readonly static byte[] EndMark = new byte[] { (byte)strEnd }; public HLBeginEndMarkReceiveFilter() : base(BeginMark, EndMark) { } /// <summary> /// 這里解析的到的數(shù)據(jù)是會把頭和尾部都給去掉的 /// </summary> /// <param name="readBuffer"></param> /// <param name="offset"></param> /// <param name="length"></param> /// <returns></returns> protected override HLProtocolRequestInfo ProcessMatchedRequest(byte[] readBuffer, int offset, int length) { var HLData = new HLData(); HLData.Head = strBegin;//自己定義開始符號 HLData.Ping = readBuffer[offset];//數(shù)據(jù),從第1位起,只有1個字節(jié) HLData.Lenght = BitConverter.ToUInt16(readBuffer, offset + 1);//數(shù)據(jù)長度,從第2位開始,2個字節(jié) HLData.FID = BitConverter.ToUInt32(readBuffer, offset + 3);//本終端ID,從第4位開始,5個字節(jié) HLData.Type = readBuffer[offset + 8];//目標類型,從第9位開始,1個字節(jié) HLData.SID = BitConverter.ToUInt32(readBuffer, offset + 9);//轉發(fā)終端ID,從第10位開始,5個字節(jié) HLData.SendCount = BitConverter.ToUInt16(readBuffer, offset + 14);//發(fā)送包計數(shù),從第15位開始,2個字節(jié) HLData.Retain = readBuffer.CloneRange(offset + 16, 6);//保留字段,從17位開始,6個字節(jié) HLData.Check = readBuffer[offset + 22];//異或校驗,從23位開始,1個字節(jié) HLData.End = strEnd;//結束符號,自己定義 return new HLProtocolRequestInfo(HLData); } } } HLBeginEndMarkReceiveFilter類
第八步、服務啟動和停止
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using SuperSocket.SocketBase; using SuperSocket.SocketBase.Protocol; using SuperSocket.SocketEngine; /**************************************************************** * 作者:黃昏前黎明后 * CLR版本:4.0.30319.42000 * 創(chuàng)建時間:2017-01-19 00:02:17 * 2017 * 描述說明:服務啟動和停止入口 * * 修改歷史: 2017 -01-19 調整自定義mysession和myserver * 2017 -01-23 通訊協(xié)議解析,直接使用入口注冊事件 * *****************************************************************/ namespace SuperSocketDemo { class Program { /// <summary> /// SuperSocket服務啟動或停止 /// </summary> /// <param name="args"></param> static void Main(string[] args) { Console.WriteLine("請按任何鍵進行啟動SuperSocket服務!"); Console.ReadKey(); Console.WriteLine(); var HLProtocolServer = new HLProtocolServer(); // 設置端口號 int port = 2017; //啟動應用服務端口 if (!HLProtocolServer.Setup(port)) //啟動時監(jiān)聽端口2017 { Console.WriteLine("服務端口啟動失敗!"); Console.ReadKey(); return; } Console.WriteLine(); //注冊連接事件 HLProtocolServer.NewSessionConnected += HLProtocolServer_NewSessionConnected; //注冊請求事件 HLProtocolServer.NewRequestReceived += HLProtocolServer_NewRequestReceived; //注冊Session關閉事件 HLProtocolServer.SessionClosed += HLProtocolServer_SessionClosed; //嘗試啟動應用服務 if (!HLProtocolServer.Start()) { Console.WriteLine("服務啟動失敗!"); Console.ReadKey(); return; } Console.WriteLine("服務器狀態(tài):" + HLProtocolServer.State.ToString()); Console.WriteLine("服務啟動成功,請按'E'停止服務!"); while (Console.ReadKey().KeyChar != 'E') { Console.WriteLine(); continue; } //停止服務 HLProtocolServer.Stop(); Console.WriteLine("服務已停止!"); Console.ReadKey(); } static void HLProtocolServer_SessionClosed(HLProtocolSession session, SuperSocket.SocketBase.CloseReason value) { Console.WriteLine(session.RemoteEndPoint.ToString() + "連接斷開. 斷開原因:" + value); } static void HLProtocolServer_NewSessionConnected(HLProtocolSession session) { Console.WriteLine(session.RemoteEndPoint.ToString() + " 已連接."); } /// <summary> /// 協(xié)議并沒有什么太多復雜邏輯,不需要用到命令模式,直接用這種方式就可以了 /// </summary> /// <param name="session"></param> /// <param name="requestInfo"></param> private static void HLProtocolServer_NewRequestReceived(HLProtocolSession session, HLProtocolRequestInfo requestInfo) { Console.WriteLine(); Console.WriteLine("數(shù)據(jù)來源: " + session.RemoteEndPoint.ToString()); Console.WriteLine("接收數(shù)據(jù)內容:"+requestInfo.Body); } } } Program類
通訊協(xié)議需要使用小工具進行調試,本人使用的是TCP/UDP端口調試工具SocketTool V2.大家可以直接進行下載。使用HEX模式進行發(fā)送16進制報文,服務器輸出結果:
本文參考官方文檔 內置的常用協(xié)議實現(xiàn)模版
以上就是本文的全部內容,希望本文的內容對大家的學習或者工作能帶來一定的幫助,同時也希望多多支持我們!
上一篇:淺談C#網絡編程詳解篇
欄 目:C#教程
本文標題:C# 常用協(xié)議實現(xiàn)模版及FixedSizeReceiveFilter示例(SuperSocket入門)
本文地址:http://mengdiqiu.com.cn/a1/C_jiaocheng/5993.html
您可能感興趣的文章
- 01-10C#實現(xiàn)txt定位指定行完整實例
- 01-10WinForm實現(xiàn)仿視頻 器左下角滾動新聞效果的方法
- 01-10C#實現(xiàn)清空回收站的方法
- 01-10C#實現(xiàn)讀取注冊表監(jiān)控當前操作系統(tǒng)已安裝軟件變化的方法
- 01-10C#實現(xiàn)多線程下載文件的方法
- 01-10C#實現(xiàn)Winform中打開網頁頁面的方法
- 01-10C#實現(xiàn)遠程關閉計算機或重啟計算機的方法
- 01-10C#自定義簽名章實現(xiàn)方法
- 01-10C#文件斷點續(xù)傳實現(xiàn)方法
- 01-10winform實現(xiàn)創(chuàng)建最前端窗體的方法


閱讀排行
本欄相關
- 01-10C#通過反射獲取當前工程中所有窗體并
- 01-10關于ASP網頁無法打開的解決方案
- 01-10WinForm限制窗體不能移到屏幕外的方法
- 01-10WinForm繪制圓角的方法
- 01-10C#實現(xiàn)txt定位指定行完整實例
- 01-10WinForm實現(xiàn)仿視頻 器左下角滾動新
- 01-10C#停止線程的方法
- 01-10C#實現(xiàn)清空回收站的方法
- 01-10C#通過重寫Panel改變邊框顏色與寬度的
- 01-10C#實現(xiàn)讀取注冊表監(jiān)控當前操作系統(tǒng)已
隨機閱讀
- 08-05織夢dedecms什么時候用欄目交叉功能?
- 01-11ajax實現(xiàn)頁面的局部加載
- 01-10使用C語言求解撲克牌的順子及n個骰子
- 01-10SublimeText編譯C開發(fā)環(huán)境設置
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
- 01-10C#中split用法實例總結
- 04-02jquery與jsp,用jquery
- 01-10delphi制作wav文件的方法
- 08-05DEDE織夢data目錄下的sessions文件夾有什