在上一篇文章中,我們已經瞭解到瞭如何在SuperSocket處理客戶端請求。 同時我們可能會發現一個問題,如果我們的伺服器端包含有很多複雜的業務邏輯,這樣的switch/case代碼將會很長而且非常難看,並且沒有遵循面向對象設計的原則(OOD)。 在這種情況下,SuperSocket提供了一些讓我們 ...
在上一篇文章中,我們已經瞭解到瞭如何在SuperSocket處理客戶端請求。 同時我們可能會發現一個問題,如果我們的伺服器端包含有很多複雜的業務邏輯,這樣的switch/case代碼將會很長而且非常難看,並且沒有遵循面向對象設計的原則(OOD)。 在這種情況下,SuperSocket提供了一些讓我們在多個獨立的類中處理各自不同的請求的命令框架,接下來我們一起來看一下怎麼使用 1、自定義AppSession AppSession 代表一個和客戶端的邏輯連接,基於連接的操作應該放在該類之中。你可以用該類的實例發送數據到客戶端,接收客戶端發送的數據或者關閉連接。 使用方法:創建自定義類MySession,繼承AppSession類並重寫AppSession類的方法(註意:一個AppSession對象對應一個連接)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using SuperSocket.Common; using SuperSocket.SocketBase; using SuperSocket.SocketBase.Protocol; /**************************************************************** * 作者:黃昏前黎明後 * CLR版本:4.0.30319.42000 * 創建時間:2017-01-19 00:02:17 * 2017 * 描述說明:自定義連接類MySession,繼承AppSession,並傳入到AppSession * * 修改歷史: * * *****************************************************************/ namespace SuperSocketDemo.Session { /// <summary> /// 自定義連接類MySession,繼承AppSession,並傳入到AppSession /// </summary> public class MySession : AppSession<MySession> { /// <summary> /// 新連接 /// </summary> protected override void OnSessionStarted() { //輸出客戶端IP地址 Console.WriteLine(this.LocalEndPoint.Address.ToString()); this.Send("Hello User,Welcome to SuperSocket Telnet Server!"); } /// <summary> /// 未知的Command /// </summary> /// <param name="requestInfo"></param> protected override void HandleUnknownRequest(StringRequestInfo requestInfo) { this.Send("unknow"); } /// <summary> /// 捕捉異常並輸出 /// </summary> /// <param name="e"></param> protected override void HandleException(Exception e) { this.Send("error: {0}", e.Message); } /// <summary> /// 連接關閉 /// </summary> /// <param name="reason"></param> protected override void OnSessionClosed(CloseReason reason) { base.OnSessionClosed(reason); } } }MySession類 2、自定義AppServer AppServer 代表了監聽客戶端連接,承載TCP連接的伺服器實例。理想情況下,我們可以通過AppServer實例獲取任何你想要的客戶端連接,伺服器級別的操作和邏輯應該定義在此類之中。 使用方法:創建自定義類MyServer,繼承AppServer類並重寫AppServer類的方法
using SuperSocket.SocketBase; using SuperSocket.SocketBase.Config; using SuperSocketDemo.Session; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; /**************************************************************** * 作者:黃昏前黎明後 * CLR版本:4.0.30319.42000 * 創建時間:2017-01-19 00:15:45 * 2017 * 描述說明:自定義伺服器類MyServer,繼承AppServer,並傳入自定義連接類MySession * * 修改歷史: * * *****************************************************************/ namespace SuperSocketDemo.Server { /// <summary> /// 自定義伺服器類MyServer,繼承AppServer,並傳入自定義連接類MySession /// </summary> public class MyServer : AppServer<MySession> { protected override void OnStartup() { base.OnStartup(); // Console.WriteLine("伺服器啟動"); } /// <summary> /// 輸出新連接信息 /// </summary> /// <param name="session"></param> protected override void OnNewSessionConnected(MySession session) { base.OnNewSessionConnected(session); //輸出客戶端IP地址 Console.Write("\r\n" + session.LocalEndPoint.Address.ToString() + ":連接"); } /// <summary> /// 輸出斷開連接信息 /// </summary> /// <param name="session"></param> /// <param name="reason"></param> protected override void OnSessionClosed(MySession session, CloseReason reason) { base.OnSessionClosed(session, reason); Console.Write("\r\n" + session.LocalEndPoint.Address.ToString() + ":斷開連接"); } protected override void OnStopped() { base.OnStopped(); Console.WriteLine("服務已停止"); } } }MyServer類 3、使用Command 在SuperSocket 中的Command讓我們進行擴展,使用方法也極其簡單。只需要繼承一個CommandBase<AppSession, StringRequestInfo>類(註意:如果使用了自定義的Session,需要修改此處,如add類下的Add:CommandBase<MySession, StringRequestInfo>)類),並override這個類ExecuteCommand方法。現在我們來處理上篇文章的示例,先取消Telnet示例中的 appServer.NewRequestReceived 事件處理。這樣我們就可以編寫大量的命令讓我們的Socket更靈活。 例如,我們可以定義一個名為"Hello "的類去處理Key為"Hello"的請求:
public class Hello: CommandBase<MySession, StringRequestInfo> { /// <summary> /// 自定義執行命令方法,註意傳入的變數session類型為MySession /// </summary> /// <param name="session">會話</param> /// <param name="requestInfo">請求數據信息</param> public override void ExecuteCommand(MySession session, StringRequestInfo requestInfo) { session.Send(string.Format("Hello {0}:{1} {2}", session.Config.Ip, session.Config.Port, requestInfo.Body)); } }Hello類
定義一個名為"ADD"的類去處理Key為"ADD"的請求:
public class ADD : CommandBase<MySession, StringRequestInfo> { public override void ExecuteCommand(MySession session, StringRequestInfo requestInfo) { session.Send(requestInfo.Parameters.Select(p => Convert.ToInt32(p)).Sum().ToString()); } }Add類
定義一個名為"MULT"的類去處理Key為"MULT"的請求:
public class MULT : CommandBase<MySession, StringRequestInfo> { public override void ExecuteCommand(MySession session, StringRequestInfo requestInfo) { var result = 1; foreach (var factor in requestInfo.Parameters.Select(p => Convert.ToInt32(p))) { result *= factor; } session.Send(result.ToString()); } }Mult類 定義一個名為"Echo"的類去處理Key為"Echo"的請求:
public class Echo: CommandBase<MySession, StringRequestInfo> { public override void ExecuteCommand(MySession session, StringRequestInfo requestInfo) { session.Send(requestInfo.Body); } }Echo類
同時我們要移除請求處理方法的註冊,因為它和命令不能同時被支持,註釋下麵代碼即可
//appServer.NewRequestReceived += new RequestHandler<MySession, StringRequestInfo>(appServer_NewRequestReceived);
4、配置App.config使用BootStrap啟動SuperSocket
SuperSocket配置section SuperSocket使用.NET自帶的配置技術,SuperSocket有一個專門的配置Section.使用配置啟動SuperSocket可以靈活配置選項
配置完成後,還需要修改program類。將原有在program中定義的埠信息以及方法註釋,只保留服務啟動和停止的代碼。引入using SuperSocket.SocketEngine;使用BootStrap啟動
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; using SuperSocketDemo.Server; /**************************************************************** * 作者:黃昏前黎明後 * CLR版本:4.0.30319.42000 * 創建時間:2017-01-19 00:02:17 * 2017 * 描述說明:服務啟動和停止入口 * * 修改歷史: 2017 -01-19 調整自定義mysession和myserver * * *****************************************************************/ namespace SuperSocketDemo { class Program { /// <summary> /// SuperSocket服務啟動或停止 /// </summary> /// <param name="args"></param> static void Main(string[] args) { Console.WriteLine("請按任何鍵進行啟動SuperSocket服務!"); Console.ReadKey(); Console.WriteLine(); var bootstrap = BootstrapFactory.CreateBootstrap(); if (!bootstrap.Initialize()) { Console.WriteLine("初始化失敗!"); Console.ReadKey(); return; } //修改appserver為myserver //var appServer = new AppServer(); // var appServer = new MyServer(); //註冊事件 // appServer.NewSessionConnected += new SessionHandler<AppSession>(appServer_NewSessionConnected); //appServer.NewRequestReceived += new RequestHandler<AppSession, StringRequestInfo>(appServer_NewRequestReceived); //設置埠號 //int port = 2017; //啟動應用服務埠 //if (!appServer.Setup(port)) //啟動時監聽埠2017 //{ // Console.WriteLine("服務埠啟動失敗!"); // Console.ReadKey(); // return; //} //Console.WriteLine(); ////嘗試啟動應用服務 //if (!appServer.Start()) //{ // Console.WriteLine("服務啟動失敗!"); // Console.ReadKey(); // return; //} var result = bootstrap.Start(); Console.WriteLine("服務正在啟動: {0}!", result); if (result == StartResult.Failed) { Console.WriteLine("服務啟動失敗!"); Console.ReadKey(); return; } Console.WriteLine("服務啟動成功,請按'E'停止服務!"); while (Console.ReadKey().KeyChar != 'E') { Console.WriteLine(); continue; } //停止服務 // appServer.Stop(); bootstrap.Stop(); Console.WriteLine("服務已停止!"); Console.ReadKey(); } /// <summary> /// 在事件處理代碼中發送歡迎信息給客戶端 /// </summary> /// <param name="session"></param> //static void appServer_NewSessionConnected(AppSession session) //{ // session.Send("Welcome to SuperSocket Telnet Server!"); //} /// <summary> ///客戶端請求處理 /// </summary> /// <param name="session">會話</param> /// <param name="requestInfo">請求信息</param> //static void appServer_NewRequestReceived(AppSession session, StringRequestInfo requestInfo) //{ // switch (requestInfo.Key.ToUpper()) // { // case ("ECHO"): // session.Send(requestInfo.Body); // break; // case ("ADD"): // session.Send(requestInfo.Parameters.Select(p => Convert.ToInt32(p)).Sum().ToString()); // break; // case ("MULT"): // var result = 1; // foreach (var factor in requestInfo.Parameters.Select(p => Convert.ToInt32(p))) // { // result *= factor; // } // session.Send(result.ToString()); // break; // } //} } }program類
最後我們看一下修改後程式的運行結果:
斷開調試工具看一下效果,可以看到服務端顯示客戶端斷開連接
註意事項:
a) MyServer、自定義命令和MySession的訪問許可權必須設置為public b) MyServer父類為AppServer<MySession> c) MySession父類為AppSession<MySession> d) HELLO父類為CommandBase<MySession,StringRequestInfo>,ExecueteCommand方法傳入值類型分別為MySession和StringRequestInfo e) 多伺服器中需註意AppSession、AppServer、自定義命令中的AppSession不要搞錯 調試常見錯誤:總結:
通過自定義Session和Server,可以實現我們自己的AppSession和AppServer允許你根據你業務的需求來方便的擴展SuperSocket,你可以綁定session的連接和斷開事件,伺服器實例的啟動和停止事件。你還可以在AppServer的Setup方法中讀取你的自定義配置信息。總而言之,這些功能讓你方便的創建一個你所需要的socket伺服器成為可能。