問題場景
Asp.net Mvc提供了DependencyResolver、Routing、Filter、 Modelbinder等webForm所沒有新概念,提高Web服務編寫的便利性,記得很久之前寫的ashx處理程序,由於沒有Routing和Modelbinder,代碼里寫了很多switch case,還有很多參數類型轉換,寫得滿頭大汗。現在,開發WebSocket服務端時,同樣遇到和ashx差不多的狀況:解析數據包,分析Command值,switch(command),然后一個case一個case分支的服務邏輯實現。
優化思路
如果我們在webSocket協議之上提出一種請求和回復的數據包的格式約定,正如http在tcp之上的協議約定一樣,那么就可以仿照Asp.net Mvc一樣,實現服務端的DependencyResolver、Controller、Filter等類似功能,未來業務功能的開發只要繼承Controller即可,輕松地實現業務功能代碼和基礎通訊代碼完全分開。當然這個格式約定可以作很簡單化,而不是直接復制Http協議,我們現在約定的格式可以如下:
{"api":"Login","id":2,"body":["name","password"]}
- 請求和回復的內容都為Json文本;
- api指明請求到遠程端的哪個api方法;
- id為本數據包的唯一標識符;
- body為請求的遠程端api的參數值,為數組;如果是回復,則為回復的對象的json文本
客戶端請求如上的數據到服務器,服務器就自行調用它里面的Login方法,然后將返回值放到請求json的body字段返回給客戶端:
public bool Login(string theName, string thePassword) { return theName == "name" && thePassword == "password"; }
設計之道
Api服務基礎類(FastApiService)的設計
上面的Login方法是一個具體的業務Api,其所在的class派生於FastApiService,FastApiService的職責是反射調用其Login成員方法。
關於反射性能,可以對Login方法先生成一個調用的委托,緩存起來供下次調用,可以參考asp.net Mvc的ActionMethodDispatcher:http://www.projky.com/asp.netmvc/4.0/System/Web/Mvc/ActionMethodDispatcher.cs.html
FastApiService的職責接口如下:
/// <summary> /// 定義Api服務的執行 /// </summary> public interface IFastApiService : IDisposable { /// <summary> /// 執行Api行為 /// </summary> /// <param name="actionContext">Api行為上下文</param> void Execute(ActionContext actionContext); }
Routing的設計
這里我們偷工減料了,不作那么強大,分析請求數據包的api鍵的字符串值,查找哪個FastApiService定義了相關的成員方法,從而New出這個FastApiService實例,再調用Execute(ActionContext actionContext);
DependencyResolver的設計
Asp.netMvc+Autofac管理EF的Context對象非常方便,這得利於Asp.netMvc提供了DependencyResolver,可以把Controller的創建給IOC組件來管理,DependencyResolver接口很簡單,傳入對象類型,返回對象實例,中間過程由IOC來處理。
查找哪個FastApiService定義了相關的成員方法,從而New出這個FastApiService實例
這里獲取FastApiService的實例,改為DependencyResolver來獲取
各部件執行流程
Filter哪里去了
Filter實際是附屬的一種東西,在FastApiService的Execute前和后各執行各種Filter就可以了,不管是全局的Filter,還是打特性的,終究都是Filter,約定好他們的執行順序就OK!有了Filter,媽媽再也不擔心別人還未登錄就請求我的其它Api服務了。
成果展示
服務器c#代碼片斷
/// <summary> /// Cpu性能檢測控制服務 /// </summary> public class CpuCounterService : FastApiService { /// <summary> /// 獲取版本號 /// </summary> /// <returns></returns> [Api] [LogFilter("獲取版本號")] public string GetVersion() { return this.GetType().Assembly.GetName().Version.ToString(); } /// <summary> /// 訂閱/取消Cpu變化通知 /// </summary> /// <returns></returns> [Api] [LogFilter("訂閱/取消Cpu變化通知")] public bool SubscribeCpuChangeNotify(bool subscribe) { this.CurrentContext.Session.TagData.Set("NotifyFlag", subscribe); return true; } }
客戶端js代碼片斷
document.title = '正在連接到服務器 ..'; var ws = new fastWebSocket('ws://localhost:8282/'); // 注冊api ws.bindApi("CpuTimeChanged", function (data) { lineChart.addData(data); }); ws.onclose = function (e) { document.title = '連接已斷開:' + e.code + '' + e.reason; }; ws.onopen = function (e) { ws.invkeApi('getVersion', [], function (version) { document.title = '服務器版本號:' + version; }, function (ex) { alert('異常:' + ex); }); };
栗子下載
https://github.com/xljiulang/NetworkSocket