分布式系統有很多成熟的解決方案。如:微軟的WCF。WCF太過於復雜,配置也麻煩。其實可以自己動手設計一個小的分布式系統。系統的原理完全在自己掌握之中,可以根據業務隨機而變。這里展示遠程調用最核心最基本的處理邏輯,其實遠程調用並不復雜神秘。
分布式系統其實是數據流的交換。數據必須快速的從一段傳送到另一端,否則系統性能就大打折扣。對於.net,本人設計一個非常優化易於使用的網絡庫(EasyNetMessage)。使用該庫,不需要關心底層細節,所有處理對象是string、byte;發送時,不需要處理分包(幾十M的數據也可以一次發送);收包時,不需要處理粘包。本文所示例代碼,就是基於該網絡庫。本文章實例代碼見底部。
實現目標
實現一個非常簡單的應用 internal int AddCall(int value1, int value2);就是兩個整數相加。調用過程和本地完全一樣,只是執行加法操作是在遠程服務器實現的。操作非常簡單,但是揭示了遠程調用的核心處理流程。
處理過程:
a) 數據發送
每次函數調用必須有一個唯一標識CallId,這個也是發包的唯一標識;服務端處理完后,返回的結果也帶有此標識。通過此標識,將發送端數據和返回數據關聯起來了。
發送完數據后,客戶端線程要掛起,等待服務器端的處理結果。線程掛起使用ManualResetEvent。並建立起CallId與ManualResetEvent的對應關系。當數據返回時,就能找到對應的ManualResetEvent。
//callid與事件關聯 Dictionary<int, ManualResetEvent> _callEventGroup = new Dictionary<int, ManualResetEvent>(); //callId與返回結果關聯 Dictionary<int, NetCallAddAck> _callResultGroup = new Dictionary<int, NetCallAddAck>(); internal int AddCall(int value1, int value2) { //組織發送包 NetCallAdd add = new NetCallAdd(); add.Value1 = value1.ToString(); add.Value2 = value2.ToString(); MonitorClient client = GetCurAppClient(); if (client == null) throw new Exception("socket未連接!"); //生成線程事件,並與CallId關聯 ManualResetEvent callEvent = new ManualResetEvent(false); lock (_callEventGroup) { _callEventGroup.Add(add.CallId, callEvent); } //發送數據 EN_SendDataResult result = _netServer.SendData(client.ClientSocket, add.ToEasyMessage().ToNetPacket()); if (result != EN_SendDataResult.ok) { lock (_callEventGroup) { _callEventGroup.Remove(add.CallId); } throw new Exception("網絡發送異常!"); } //等待結果 callEvent.WaitOne(3000); _callEventGroup.Remove(add.CallId); //查看結果集 lock (_callResultGroup) { if (_callResultGroup.ContainsKey(add.CallId)) { NetCallAddAck ack = _callResultGroup[add.CallId]; return int.Parse(ack.Result); } } throw new Exception("沒有返回結果!"); }
b)數據返回
數據返回后的處理是在另一個線程。數據返回后,先根據CallId查找對應的ManualResetEvent;如果找不到,有可能服務器處理太慢,超時了。
先將返回結果存儲到哈希數組中,key為CallId。再調用ManualResetEvent的Set函數,喚醒調用端線程。調用端線程根據CallId到哈希表中獲取結果。
internal void OnRcvAck(NetCallAddAck addAck) { //根據callid找到事件 ManualResetEvent callEvent = null; lock (_callEventGroup) { if (!_callEventGroup.ContainsKey(addAck.CallId)) return; callEvent =_callEventGroup[addAck.CallId]; } //結果存放到哈希表中 lock (_callResultGroup) { _callResultGroup.Remove(addAck.CallId); _callResultGroup.Add(addAck.CallId, addAck); } //設置事件為有信號,調用方掛起的線程可以繼續執行 callEvent.Set(); }
進一步說明:可以在此基礎上,進一步擴展。開發出類似Redis的內存庫。客戶端的調用也不一定是同步 ,可以采用異步回調的方式處理。其實如果知道處理的原理,可以根據自己的業務做裁剪。只有知其所以然,才能開發出最符合自己業務的系統,才可能進一步優化。
實例代碼 https://download.csdn.net/download/qq_29939347/10684858