坦白講,我們公司其實沒啥技術實力,之所以還能不斷接到各種項目,全憑我們老板神通廣大!要知道他每次的飯局上可都是些什么人物!
但是項目接下一大把,就憑咱哥兒幾個的水平,想要獨立自主、保質保量保期地一個個做出來,那也是有點難以置信。之前咱也跟老板反映過這個困難,建議他再召兩個高手過來。不過領導雖然書讀的不多,有一句古訓倒是背得特別熟——“君子生非異也,善假於物也”。所以咱們公司一直奉行拿來主義。
園子里的這個GGTalk——C#開源即時通訊系統,咱們前前后后用它移花接木做的IM項目也不下三四個了。初次入手的時候,洋洋代碼,多少感覺有些難以把握。不過一來二去,理清了頭緒,也就一覽無余了。
相信跟我們一樣想要利用GGTalk的同學大有人在,於是我打算寫這樣一個《GGTalk——C#開源即時通訊系統源碼介紹系列》,把自己對GGTalk的梳理分享給大家,讓大家更容易上手。
那接下來我們就言歸正傳。
一.GGTalk的宏觀結構
如圖,GGTalk中有4個項目,第一個“GGTalk”是客戶端項目;“GGTalk.Core”是客戶端和服務端共用的一些類,主要包括一些數據實體,還有一些通信協議類;“GGTalk.Server”是服務端項目;“JustLib”是作者封裝的一些常用類和控件。
二.GGTalk是如何實現注冊的?
GGTalk采用Remoting技術來完成注冊。
1.先定義接口
namespace GGTalk { /// <summary> /// 用於提供注冊服務的Remoting接口。 /// </summary> public interface IRemotingService :IChatRecordPersister { RegisterResult Register(GGUser user); /// <summary> /// 根據ID或Name搜索用戶【完全匹配】。 /// </summary> List<GGUser> SearchUser(string idOrName); /// <summary> /// 發送系統通知給所有在線用戶。 /// </summary> void SendSystemNotify(string title, string content); } }
2.服務端接口實現
namespace GGTalk.Server { internal class RemotingService :MarshalByRefObject, IRemotingService { private GlobalCache globalCache; private IRapidServerEngine rapidServerEngine; public RemotingService(GlobalCache db ,IRapidServerEngine engine) { this.globalCache = db; this.rapidServerEngine = engine; } public RegisterResult Register(GGUser user) { try { if (this.globalCache.IsUserExist(user.UserID)) { return RegisterResult.Existed; } this.globalCache.InsertUser(user); return RegisterResult.Succeed; } catch (Exception ee) { return RegisterResult.Error; } } public List<GGUser> SearchUser(string idOrName) { return this.globalCache.SearchUser(idOrName); } public override object InitializeLifetimeService() { return null; } } }
3.服務端發布服務
#region 發布用於注冊的Remoting服務 RemotingConfiguration.Configure("GGTalk.Server.exe.config", false); RemotingService registerService = new Server.RemotingService(globalCache ,Program.RapidServerEngine); RemotingServices.Marshal(registerService, "RemotingService"); #endregion
服務端配置文件:
<system.runtime.remoting> <application> <channels> <!--用戶注冊Remoting服務端口--> <channel ref="tcp" port="4500" > <serverProviders> <provider ref="wsdl" /> <formatter ref="soap" typeFilterLevel="Full" /> <formatter ref="binary" typeFilterLevel="Full" /> </serverProviders> <clientProviders> <formatter ref="binary" /> </clientProviders> </channel> </channels> </application> </system.runtime.remoting>
4.客戶端訂閱服務
int registerPort = int.Parse(ConfigurationManager.AppSettings["RemotingPort"]); this.remotingService = (IRemotingService)Activator.GetObject(typeof(IRemotingService), string.Format("tcp://{0}:{1}/RemotingService", ConfigurationManager.AppSettings["ServerIP"], registerPort)); ;
三.總結Remoting技術
1.Remoting使用中的三要素:
1.一個可遠程處理的對象。
2.一個服務端應用程序域用(也叫宿主應用程序域中),於偵聽針對該對象的請求。
3.一個客戶端應用程序域,用於發出針對該對象的請求。
2.代理:
代理是一個提供了和真實對象完全一樣的接口、公共方法、屬性等成員的對象。在運行過程中,.NET Remoting基於對象元數據生成代理。代理只提供接口,不提供對象的狀態,因為對象的真正狀態在宿主應用程序域中存儲。代理在這里只轉發調用。轉發調用到一個對象叫封送處理。封送處理的目標是讓客戶端調用服務端時感覺不到是在與遠端對象交流,而是與本地對象在交流。如果通過代理來訪問對象,該對象必需是從 MarshalByRefObject抽象類派生。
3.Remoting架構:
四.知識點拓展
1. 用應用程序域
操作系統和運行庫環境通常會在應用程序間提供某種形式的隔離。例如,Microsoft Windows 使用進程來隔離應用程序。為確保在一個應用程序中運行的代碼不會對其他不相關的應用程序產生不良影響,這種隔離是必需的。
2.用應用程序域優點
在一個應用程序中出現的錯誤不會影響其他應用程序。因為類型安全的代碼不會導致內存錯誤,所以使用應用程序域可以確保在一個域中運行的代碼不會影響進程中的其他應用程序。
能夠在不停止整個進程的情況下停止單個應用程序。使用應用程序域使您可以卸載在單個應用程序中運行的代碼。
在一個應用程序中運行的代碼不能直接訪問其他應用程序中的代碼或資源。為了強制實施此隔離,公共語言運行庫禁止在不同應用程序域中的對象之間進行直接調用。要在各域之間傳遞對象,可以復制這些對象,或通過代理訪問這些對象。如果復制對象,那么對該對象的調用為本地調用。也就是說,調用方和被引用的對象位於同一應用程序域中。如果通過代理訪問對象,那么對該對象的調用為遠程調用。在此情況下,調用方和被引用的對象位於不同的應用程序域中。域間調用所采用的遠程調用基礎結構與兩個進程間的調用或兩台計算機間的調用的基礎結構相同。因此,被引用的對象的元數據必須對於兩個應用程序域均可用,以便用 JIT 正確編譯該方法調用。如果調用域對被調用對象的元數據沒有訪問權,則編譯可能失敗,並引發類型為 System.IO.FileNotFound 的異常。
代碼行為的作用范圍由它運行所在的應用程序決定。換言之,應用程序域將提供應用程序版本策略等配置設置、它所訪問的任意遠程程序集的位置,以及加載到該域中的程序集的位置信息。
向代碼授予的權限可以由代碼運行所在的應用程序域來控制。
五.后記
這是該系列的第一篇,一篇博客篇幅有限,能講清楚一兩個問題已經很不錯。要想把GGTalk——C#開源即時通訊系統全部講透,恐怕得要上百篇博客才夠。首先要感謝GGTalk的作者,能夠開發出這樣優秀的C#開源即時通訊系統,而且無私的開源出來。其次,希望我做的工作也能給大家帶來收獲,一方面可以方便大家利用GGTalk,另一方面也為學習GGTalk的同學提供一個參考,希望大家可以從中汲取營養,不僅僅是復用這些代碼,也能夠從中學到蘊含着的知識與技術。
解讀GGTalk,這個工作很辛苦,希望大家鼓勵,也希望更多的朋友參與其中!