一.内置的指令行協定
什麼是協定?
什麼是協定? 很多人會回答 "TCP" 或者 "UDP"。 但是建構一個網絡應用程式, 僅僅知道是 TCP 還是 UDP 是遠遠不夠的。 TCP 和 UDP 是傳輸層協定。僅僅定義了傳輸層協定是不能讓網絡的兩端進行通信的。你需要定義你的應用層通信協定把你接收到的二進制資料轉化成你程式能了解的請求。
内置的指令行協定
指令行協定是一種被廣泛應用的協定。一些成熟的協定如 Telnet, SMTP, POP3 和 FTP 都是基于指令行協定的。 在SuperSocket 中, 如果你沒有定義自己的協定,SuperSocket 将會使用指令行協定, 這會使這樣的協定的開發變得很簡單。
指令行協定定義了每個請求必須以回車換行結尾 "\r\n"。
如果你在 SuperSocket 中使用指令行協定,所有接收到的資料将會翻譯成 StringRequestInfo 執行個體。
StringRequestInfo 是這樣定義的:
public class StringRequestInfo
{
public string Key { get; }
public string Body { get; }
public string[] Parameters { get; }
/*
Other properties and methods
*/
}
由于 SuperSocket 中内置的指令行協定用空格來分割請求的Key和參,是以當用戶端發送如下資料到伺服器端時:
"LOGIN kerry 123456" + NewLine
SuperSocket 伺服器将會收到一個 StringRequestInfo 執行個體,這個執行個體的屬性為:
Key: "LOGIN"
Body: "kerry 123456";
Parameters: ["kerry", "123456"]
如果你定義了名為 "LOGIN" 的指令, 這個指令的 ExecuteCommand 方法将會被執行,伺服器所接收到的StringRequestInfo執行個體也将作為參數傳給這個方法:
public class LOGIN : CommandBase<AppSession, StringRequestInfo>
{
public override void ExecuteCommand(AppSession session, StringRequestInfo requestInfo)
{
//Implement your business logic
}
}
自定義你的指令行協定
有些使用者可能會有不同的請求格式, 比如:
"LOGIN:kerry,12345" + NewLine
請求的 key 和 body 通過字元 ':' 分隔, 而且多個參數被字元 ',' 分隔。 支援這種類型的請求非常簡單, 你隻需要用下面的代碼擴充指令行協定:
public class YourServer : AppServer<YourSession>
{
public YourServer()
: base(new CommandLineReceiveFilterFactory(Encoding.Default, new BasicRequestInfoParser(":", ",")))
{
}
}
- 如果你想更深度的定義請求的格式, 你可以基于接口 IRequestInfoParser 來實作一個 RequestInfoParser 類, 然後當執行個體化 CommandLineReceiveFilterFactory 時傳入拟定一個 RequestInfoParser 執行個體:
-
<server name="TelnetServer" textEncoding="UTF-8" serverType="YourAppServer, YourAssembly" ip="Any" port="2020"> </server>
文本編碼
指令行協定的預設編碼是 Ascii,但是你也可以通過修改配置中的伺服器節點的"textEncoding"屬性來改變編碼:
-
<server name="TelnetServer"
-
textEncoding="UTF-8"
-
serverType="YourAppServer, YourAssembly"
-
ip="Any" port="2020">
-
</server>
二.内置的常用協定實作模版
閱讀了前面一篇文檔之後, 你可能會覺得用 SuperSocket 來實作你的自定義協定并不簡單。 為了讓這件事變得更容易一些, SuperSocket 提供了一些通用的協定解析工具, 你可以用他們簡單而且快速的實作你自己的通信協定:
- TerminatorReceiveFilter (SuperSocket.SocketBase.Protocol.TerminatorReceiveFilter, SuperSocket.SocketBase)
- CountSpliterReceiveFilter (SuperSocket.Facility.Protocol.CountSpliterReceiveFilter, SuperSocket.Facility)
- FixedSizeReceiveFilter (SuperSocket.Facility.Protocol.FixedSizeReceiveFilter, SuperSocket.Facility)
- BeginEndMarkReceiveFilter (SuperSocket.Facility.Protocol.BeginEndMarkReceiveFilter, SuperSocket.Facility)
- FixedHeaderReceiveFilter (SuperSocket.Facility.Protocol.FixedHeaderReceiveFilter, SuperSocket.Facility)
TerminatorReceiveFilter - 結束符協定
與指令行協定類似,一些協定用結束符來确定一個請求.
例如, 一個協定使用兩個字元 "##" 作為結束符, 于是你可以使用類 "TerminatorReceiveFilterFactory":
-
/// <summary>
-
/// TerminatorProtocolServer
-
/// Each request end with the terminator "##"
-
/// ECHO Your message##
-
/// </summary>
-
public class TerminatorProtocolServer : AppServer
-
{
-
public TerminatorProtocolServer()
-
: base(new TerminatorReceiveFilterFactory("##"))
-
{
-
}
-
}
預設的請求類型是 StringRequestInfo, 你也可以建立自己的請求類型, 不過這樣需要你做一點額外的工作:
基于TerminatorReceiveFilter實作你的接收過濾器(ReceiveFilter):
-
public class YourReceiveFilter : TerminatorReceiveFilter<YourRequestInfo>
-
{
-
//More code
-
}
實作你的接收過濾器工廠(ReceiveFilterFactory)用于建立接受過濾器執行個體:
-
public class YourReceiveFilterFactory : IReceiveFilterFactory<YourRequestInfo>
-
{
-
//More code
-
}
然後在你的 AppServer 中使用這個接收過濾器工廠(ReceiveFilterFactory).
CountSpliterReceiveFilter - 固定數量分隔符協定
有些協定定義了像這樣格式的請求 "#part1#part2#part3#part4#part5#part6#part7#". 每個請求有7個由 '#' 分隔的部分. 這種協定的實作非常簡單:
-
/// <summary>
-
/// Your protocol likes like the format below:
-
/// #part1#part2#part3#part4#part5#part6#part7#
-
/// </summary>
-
public class CountSpliterAppServer : AppServer
-
{
-
public CountSpliterAppServer()
-
: base(new CountSpliterReceiveFilterFactory((byte)'#', 8)) // 7 parts but 8 separators
-
{
-
}
-
}
你也可以使用下面的類更深入的定制這種協定:
-
CountSpliterReceiveFilter<TRequestInfo>
-
CountSpliterReceiveFilterFactory<TReceiveFilter>
-
CountSpliterReceiveFilterFactory<TReceiveFilter, TRequestInfo>
FixedSizeReceiveFilter - 固定請求大小的協定
在這種協定之中, 所有請求的大小都是相同的。如果你的每個請求都是有9個字元組成的字元串,如"KILL BILL", 你應該做的事就是想如下代碼這樣實作一個接收過濾器(ReceiveFilter):
-
class MyReceiveFilter : FixedSizeReceiveFilter<StringRequestInfo>
-
{
-
public MyReceiveFilter()
-
: base(9) //傳入固定的請求大小
-
{
-
}
-
protected override StringRequestInfo ProcessMatchedRequest(byte[] buffer, int offset, int length, bool toBeCopied)
-
{
-
//TODO: 通過解析到的資料來構造請求執行個體,并傳回
-
}
-
}
然後在你的 AppServer 類中使用這個接受過濾器 (ReceiveFilter):
-
public class MyAppServer : AppServer
-
{
-
public MyAppServer()
-
: base(new DefaultReceiveFilterFactory<MyReceiveFilter, StringRequestInfo>()) //使用預設的接受過濾器工廠 (DefaultReceiveFilterFactory)
-
{
-
}
-
}
BeginEndMarkReceiveFilter - 帶起止符的協定
在這類協定的每個請求之中 都有固定的開始和結束标記。例如, 我有個協定,它的所有消息都遵循這種格式 "!xxxxxxxxxxxxxx$"。是以,在這種情況下, "!" 是開始标記, "$" 是結束标記,于是你的接受過濾器可以定義成這樣:
-
class MyReceiveFilter : BeginEndMarkReceiveFilter<StringRequestInfo>
-
{
-
//開始和結束标記也可以是兩個或兩個以上的位元組
-
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: 通過解析到的資料來構造請求執行個體,并傳回
-
}
-
}
然後在你的 AppServer 類中使用這個接受過濾器 (ReceiveFilter):
-
public class MyAppServer : AppServer
-
{
-
public MyAppServer()
-
: base(new DefaultReceiveFilterFactory<MyReceiveFilter, StringRequestInfo>()) //使用預設的接受過濾器工廠 (DefaultReceiveFilterFactory)
-
{
-
}
-
}
FixedHeaderReceiveFilter - 頭部格式固定并且包含内容長度的協定
這種協定将一個請求定義為兩大部分, 第一部分定義了包含第二部分長度等等基礎資訊. 我們通常稱第一部分為頭部.
例如, 我們有一個這樣的協定: 頭部包含 6 個位元組, 前 4 個位元組用于存儲請求的名字, 後兩個位元組用于代表請求體的長度:
-
/// +-------+---+-------------------------------+
-
/// |request| l | |
-
/// | name | e | request body |
-
/// | (4) | n | |
-
/// | |(2)| |
-
/// +-------+---+-------------------------------+
使用 SuperSocket, 你可以非常友善的實作這種協定:
-
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實作你自己的接收過濾器.
- 傳入父類構造函數的 6 表示頭部的長度;
- 方法"GetBodyLengthFromHeader(...)" 應該根據接收到的頭部傳回請求體的長度;
- 方法 ResolveRequestInfo(....)" 應該根據你接收到的請求頭部和請求體傳回你的請求類型的執行個體.
然後你就可以使用接收或者自己定義的接收過濾器工廠來在 SuperSocket 中啟用該協定.
三.使用 IRequestInfo 和 IReceiveFilter 等等其他對象來實作自定義協定
為什麼你要使用自定義協定?
通信協定用于将接收到的二進制資料轉化成您的應用程式可以了解的請求。 SuperSocket提供了一個内置的通信協定“指令行協定”定義每個請求都必須以回車換行"\r\n"結尾。
但是一些應用程式無法使用指令行協定由于不同的原因。 這種情況下,你需要使用下面的工具來實作你的自定義協定:
-
* RequestInfo
-
* ReceiveFilter
-
* ReceiveFilterFactory
-
* AppServer and AppSession
請求(RequestInfo)
RequestInfo 是表示來自用戶端請求的實體類。 每個來自用戶端的請求都能應該被執行個體化為 RequestInfo 類型。 RequestInfo 類必須實作接口 IRequestInfo,該接口隻有一個名為"Key"的字元串類型的屬性:
-
public interface IRequestInfo
-
{
-
string Key { get; }
-
}
上面文檔提到了, 請求類型 StringRequestInfo 用在 SuperSocket 指令行協定中。
你也可以根據你的應用程式的需要來定義你自己的請求類型。 例如, 如果所有請求都包含 DeviceID 資訊,你可以在RequestInfo類裡為它定義一個屬性:
-
public class MyRequestInfo : IRequestInfo
-
{
-
public string Key { get; set; }
-
public int DeviceId { get; set; }
-
}
SuperSocket 還提供了另外一個請求類 "BinaryRequestInfo" 用于二進制協定:
-
public class BinaryRequestInfo
-
{
-
public string Key { get; }
-
public byte[] Body { get; }
-
}
你可以直接使用此類型 BinaryRequestInfo, 如果他能滿足你的需求的話。
接收過濾器(ReceiveFilter)
接收過濾器(ReceiveFilter)用于将接收到的二進制資料轉化成請求執行個體(RequestInfo)。
實作一個接收過濾器(ReceiveFilter), 你需要實作接口 IReceiveFilter:
-
public interface IReceiveFilter<TRequestInfo>
-
where TRequestInfo : IRequestInfo
-
{
-
/// <summary>
-
/// Filters received data of the specific session into request info.
-
/// </summary>
-
/// <param name="readBuffer">The read buffer.</param>
-
/// <param name="offset">The offset of the current received data in this read buffer.</param>
-
/// <param name="length">The length of the current received data.</param>
-
/// <param name="toBeCopied">if set to <c>true</c> [to be copied].</param>
-
/// <param name="rest">The rest, the length of the data which hasn't been parsed.</param>
-
/// <returns></returns>
-
TRequestInfo Filter(byte[] readBuffer, int offset, int length, bool toBeCopied, out int rest);
-
/// <summary>
-
/// Gets the size of the left buffer.
-
/// </summary>
-
/// <value>
-
/// The size of the left buffer.
-
/// </value>
-
int LeftBufferSize { get; }
-
/// <summary>
-
/// Gets the next receive filter.
-
/// </summary>
-
IReceiveFilter<TRequestInfo> NextReceiveFilter { get; }
-
/// <summary>
-
/// Resets this instance to initial state.
-
/// </summary>
-
void Reset();
-
}
- TRequestInfo: 類型參數 "TRequestInfo" 是你要在程式中使用的請求類型(RequestInfo);
- LeftBufferSize: 該接收過濾器已緩存資料的長度;
- NextReceiveFilter: 當下一塊資料收到時,用于處理資料的接收過濾器執行個體;
- Reset(): 重設接收過濾器執行個體到初始狀态;
- Filter(....): 該方法将會在 SuperSocket 收到一塊二進制資料時被執行,接收到的資料在 readBuffer 中從 offset 開始, 長度為 length 的部分。
TRequestInfo Filter(byte[] readBuffer, int offset, int length, bool toBeCopied, out int rest);
- readBuffer: 接收緩沖區, 接收到的資料存放在此數組裡
- offset: 接收到的資料在接收緩沖區的起始位置
- length: 本輪接收到的資料的長度
- toBeCopied: 表示當你想緩存接收到的資料時,是否需要為接收到的資料重新建立一個備份而不是直接使用接收緩沖區
- rest: 這是一個輸出參數, 它應該被設定為當解析到一個為政的請求後,接收緩沖區還剩餘多少資料未被解析
這兒有很多種情況需要你處理:
- 當你在接收緩沖區中找到一條完整的請求時,你必須傳回一個你的請求類型的執行個體.
- 當你在接收緩沖區中沒有找到一個完整的請求時, 你需要傳回 NULL.
- 當你在接收緩沖區中找到一條完整的請求, 但接收到的資料并不僅僅包含一個請求時,設定剩餘資料的長度到輸出變量 "rest". SuperSocket 将會檢查這個輸出參數 "rest", 如果它大于 0, 此 Filter 方法 将會被再次執行, 參數 "offset" 和 "length" 會被調整為合适的值.
接收過濾器工廠(ReceiveFilterFactory)
接收過濾器工廠(ReceiveFilterFactory)用于為每個會話建立接收過濾器. 定義一個過濾器工廠(ReceiveFilterFactory)類型, 你必須實作接口 IReceiveFilterFactory. 類型參數 "TRequestInfo" 是你要在整個程式中使用的請求類型
-
/// <summary>
-
/// Receive filter factory interface
-
/// </summary>
-
/// <typeparam name="TRequestInfo">The type of the request info.</typeparam>
-
public interface IReceiveFilterFactory<TRequestInfo> : IReceiveFilterFactory
-
where TRequestInfo : IRequestInfo
-
{
-
/// <summary>
-
/// Creates the receive filter.
-
/// </summary>
-
/// <param name="appServer">The app server.</param>
-
/// <param name="appSession">The app session.</param>
-
/// <param name="remoteEndPoint">The remote end point.</param>
-
/// <returns>
-
/// the new created request filer assosiated with this socketSession
-
/// </returns>
-
IReceiveFilter<TRequestInfo> CreateFilter(IAppServer appServer, IAppSession appSession, IPEndPoint remoteEndPoint);
-
}
你也可以直接使用預設的過濾器工廠(ReceiveFilterFactory)
DefaultReceiveFilterFactory<TReceiveFilter, TRequestInfo>
, 當工廠的CreateFilter方法被調用時,它将會調用TReceiveFilter類型的無參構造方法來建立并傳回TReceiveFilter.
和 AppSession,AppServer 配合工作
現在, 你已經有了 RequestInfo, ReceiveFilter 和 ReceiveFilterFactory, 但是你還沒有正式使用它們. 如果你想讓他們在你的程式裡面可用, 你需要定義你們的 AppSession 和 AppServer 來使用他們.
- 為 AppSession 設定 RequestInfo
-
public class YourSession : AppSession<YourSession, YourRequestInfo>
-
{
-
//More code...
-
}
-
- 為 AppServer 設定 RequestInfo 和 ReceiveFilterFactory
-
public class YourAppServer : AppServer<YourSession, YourRequestInfo>
-
{
-
public YourAppServer()
-
: base(new YourReceiveFilterFactory())
-
{
-
}
-
}
-
完成上面兩件事情,你的自定義協定就應該可以工作了。
四.指令和指令加載器
指令 (Command)
SuperSocket 中的指令設計出來是為了處理來自用戶端的請求的, 它在業務邏輯處理之中起到了很重要的作用。
指令類必須實作下面的基本指令接口:
-
public interface ICommand<TAppSession, TRequestInfo> : ICommand
-
where TRequestInfo : IRequestInfo
-
where TAppSession : IAppSession
-
{
-
void ExecuteCommand(TAppSession session, TRequestInfo requestInfo);
-
}
-
public interface ICommand
-
{
-
string Name { get; }
-
}
請求處理代碼必須被放置于方法 "ExecuteCommand(TAppSession session, TRequestInfo requestInfo)" 之中,并且屬性 "Name" 的值用于比對接收到請求執行個體(requestInfo)的Key。當一個請求執行個體(requestInfo) 被收到時,SuperSocket 将會通過比對請求執行個體(requestInfo)的Key和指令的Name的方法來查找用于處理該請求的指令。
舉個例子, 如果你收到如下請求(requestInfo):
-
Key: "ADD"
-
Body: "1 2"
于是 SuperSocket 将會尋找Name屬性為"ADD"的指令。如果有個指令定義如下:
-
public class ADD : StringCommandBase
-
{
-
public override void ExecuteCommand(AppSession session, StringRequestInfo requestInfo)
-
{
-
session.Send((int.Parse(requestInfo[0] + int.Parse(requestInfo[1])).ToString());
-
}
-
}
因為基類 StringCommandBase 會設定Name屬性的值為此類的名稱(ADD),是以個指令将會被找到.
但是在有些情況, 請求執行個體(requestInfo)的Key 無法當做類的名稱。 比如說:
-
Key: "01"
-
Body: "1 2"
為了讓讓你的 ADD 指令起作用,你需要為指令類重寫Name屬性:
-
public class ADD : StringCommandBase
-
{
-
public override string Name
-
{
-
get { return "01"; }
-
}
-
public override void ExecuteCommand(AppSession session, StringRequestInfo requestInfo)
-
{
-
session.Send((int.Parse(requestInfo[0] + int.Parse(requestInfo[1])).ToString());
-
}
-
}
指令程式集定義
是的,SuperSocket是用反射來查找哪些公開的類實作了基本的指令接口,但是它隻在你的AppServer類定義的程式集中查找。
舉例來說, 你的 AppServer 定義在程式集 GameServer.dll 中, 但是你的 ADD 指令是定義在程式集 BasicModules.dll 中:
-
GameServer.dll
-
+ MyGameServer.cs
-
BasicModules.dll
-
+ ADD.cs
預設的, 指令 "ADD" 将不會被加載到遊戲伺服器執行個體。 如果你想要加載該指令, 你如要在配置中添加程式集 BasicModules.dll 到指令程式集清單之中:
-
<?xml version="1.0" encoding="utf-8" ?>
-
<configuration>
-
<configSections>
-
<section name="superSocket" type="SuperSocket.SocketEngine.Configuration.SocketServiceConfig, SuperSocket.SocketEngine"/>
-
</configSections>
-
<appSettings>
-
<add key="ServiceName" value="BroardcastService"/>
-
</appSettings>
-
<superSocket>
-
<servers>
-
<server name="SampleServer"
-
serverType="GameServer.MyGameServer, GameServer"
-
ip="Any" port="2012">
-
<commandAssemblies>
-
<add assembly="BasicModules"></add>
-
</commandAssemblies>
-
</server>
-
</servers>
-
</superSocket>
-
<startup>
-
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0" />
-
</startup>
-
</configuration>
當然你也可以在配置中添加多個指令程式集。
指令加載器 (Command Loader)
在某些情況下,你可能希望通過直接的方式來加載指令,而不是通過自動的反射。 如果是這樣,你可以實作你自己的指令加載器 (Command Loader):
public interface ICommandLoader<TCommand>
然後配置你的伺服器來使用你建立的指令加載器 (Command Loader):
-
<superSocket>
-
<servers>
-
<server name="SampleServer"
-
serverType="GameServer.MyGameServer, GameServer"
-
ip="Any" port="2012"
-
commandLoader="MyCommandLoader">
-
</server>
-
</servers>
-
<commandLoaders>
-
<add name="MyCommandLoader"
-
type="GameServer.MyCommandLoader, GameServer" />
-
</commandLoaders>
-
</superSocket>
ps:以上内容均轉自http://www.supersocket.net/