前言
本小節是NetworkSocket系列的第6小節,在閱讀本小節之前,您可能需要先閱讀前面的幾個小節,否則可能覺得內容跳轉比較大。
描述
FastTcpServerEx是從TcpServerEx派生,使用的協議和TcpServerEx完成相同,FastTcpServerEx充分結合C#強大的反射功能,大大地簡化了服務器編程難度,更符合實際通訊項目的編寫,與之相比,前兩章節提到的TcpServerBase和TcpServerEx構建服務器,離實際項目要求還相差很遠。FastTcpServerEx的工作原理是,當收到客戶端發來的數據DataEventExArgs后,分析DataEventExArgs的Action參數,把Action和對應的服務方法綁定,把DataEventExArgs.Binary轉換為服務方法對應的參數,然后通過反射調用服務方法,將方法的返回值再封裝為DataEventExArgs返回給客戶端,這里雖然用到了反射,但已經改善和優化過,方便性的提升帶來的價值大過於性能的損失價值。
服務器編寫思路
你可以把FastTcpServerEx比作Wcf來理解,編寫步驟是:1、編寫服務契約IDemoServer,當然還有用到的實體,建議實體單獨作一個項目工程,這樣在序列化工作很方便;2、新增DemoServer類,派生於FastTcpServerEx,並繼承IDemoServer接口;3、實現IDemoServer接口
編寫接口
新建Server工程,引用NetworkSocket.dll,引入NetworkSocket、NetWorkSocket.TcpExtention、NetWorkSocket.Serialization、Entity命名空間,添加IDemoServer接口

using System; using System.Collections.Generic; using System.Linq; using System.Text; using Entity; using NetWorkSocket; using NetWorkSocket.TcpExtention; namespace Server { /// <summary> /// 服務接口 /// 要求每個方法的第一個參數必須是SocketAsync(DataEventExArgs)類型 /// 這個參數很重要(驗證客端,斷開客戶端等都用到) /// </summary> public interface IDemoServer { /// <summary> /// 客戶端登錄 /// </summary> /// <param name="client">客戶端</param> /// <param name="user">用戶信息</param> /// <param name="fAdmin">是否是管理員登錄</param> /// <returns></returns> bool Login(SocketAsync<DataEventExArgs> client, Userinfo user, bool fAdmin); /// <summary> /// 求和 /// </summary> /// <param name="client">客戶端</param> /// <param name="x">參數x</param> /// <param name="y">參數y</param> /// <param name="z">參數z</param> /// <returns></returns> int GetSum(SocketAsync<DataEventExArgs> client, int x, int y, int z); /// <summary> /// 獲取客戶端本機上的時間 /// 此方法由客戶端來實現,服務器來調用 /// [ClientMethod]是修飾此方法為客戶端方法的特性 /// </summary> /// <param name="client">客戶端</param> /// <param name="callBack">回調,收到數據后,將回調此方法</param> [ClientMethod] void GetDateTime(SocketAsync<DataEventExArgs> client, Action<DateTime> callBack); } }
上面的接口有三個服務方法,分別為客戶端登錄、客戶獲取數據相加、獲取客戶端本機時間,前兩個方法都是客戶端主動請求,服務器被動處理,而后一個方法是服務器主動請求,客戶端被動處理(即服務器和客戶端功能倒置)。這里要注意的是,每個方法的第一個參數必須是SocketAsync<DataEventExArgs>,這個參數不是數據參數,不會被序列化然后傳送到另一端,而是在方法的實現中會經常用到這個參數。
實現接口
實現接口也就是實現了服務器的功能,這里也就不多篇幅來說明怎么實現接口了。

using System; using System.Collections.Generic; using System.Linq; using System.Text; using Entity; using NetWorkSocket; using NetWorkSocket.Serialization; using NetWorkSocket.TcpExtention; namespace Server { /// <summary> /// 服務器接口的實現 /// 第一個接口必須是服務器的接口(如果有多個接口的話) /// </summary> public class DemoServer : FastTcpServerEx, IDemoServer { /// <summary> /// 重寫驗證客戶端的方法 /// 在客戶端請求服務時,此方法第一時間被調用 /// 如果返回flase,則服務器會丟棄客戶端的請求 /// </summary> /// <param name="client"></param> /// <param name="action"></param> /// <returns></returns> protected override bool ValidateClient(SocketAsync<DataEventExArgs> client, int action) { // action為0,對應接口的第一個方法,也就是Login方法 bool forLogin = (action == 0); // 如果此請求不是Login類型請求 if (forLogin == false) { // 驗證通過后才可以請求其它服務方法 return client.HasValidated; } return base.ValidateClient(client, action); } /// <summary> /// 客戶端登錄 /// </summary> /// <param name="client">客戶端</param> /// <param name="user">用戶信息</param> /// <param name="fAdmin">是否是管理員登錄</param> /// <returns></returns> public bool Login(SocketAsync<DataEventExArgs> client, Userinfo user, bool fAdmin) { if (user != null) { // 標記客戶端為合法有效的用戶 // 這樣,client在登錄后,就可以有權調用GetSum服務方法 client.HasValidated = true; client.UserToken = user; // 保存user對象,方便查找client,client可以從this.AliveClients屬性查找 // 從客戶端獲取時間 this.GetDateTime(client, (time) => { Console.WriteLine("{0}({1}) {2}", user.name, fAdmin ? "Admin" : "User", time); }); // 返回登錄成功 return true; } return false; } /// <summary> /// 求和 /// </summary> /// <param name="client">客戶端</param> /// <param name="x">參數x</param> /// <param name="y">參數y</param> /// <param name="z">參數z</param> /// <returns></returns> public int GetSum(SocketAsync<DataEventExArgs> client, int x, int y, int z) { return x + y + z; } /// <summary> /// 獲取客戶端本機上的時間 /// 此方法由客戶端來實現,服務器來調用 /// [ClientMethod]是修飾此方法為客戶端方法的特性 /// </summary> /// <param name="client">客戶端</param> /// <param name="callBack">回調,收到數據后,將回調此方法</param> [ClientMethod] public void GetDateTime(SocketAsync<DataEventExArgs> client, Action<DateTime> callBack) { // 使用InvokeClient簡化發送數據到客戶端的過程 // 如果沒有數據參數,InvokeClient的第二個參數可以為null base.InvokeClient<DateTime>(client, null, callBack); } } }
當接口實現后,服務器的功能已編寫完成,下面是啟動服務器的方法

static void Main(string[] args) { Console.Title = "Flex Server"; DemoServer server = new DemoServer(); server.StartListen(new IPEndPoint(IPAddress.Any, 8181)); Console.WriteLine("127.0.0.1 8181"); while (true) { Console.ReadLine(); } }
生成服務調用代理
wcf的時候,我們把服務發布后,通過vs很方便就可以生成服務調用的代理類,FastTcpServerEx也有類似功能,不同的是,這個代理類是通過ProxyMaker工具來反射IDemoServer,獲取里面的方法,然后逆向生成調用代碼,最終於編譯為Server.dll輸出,客戶端只要引用Server.dll,就可以方便的和服務器通訊了。
運行ProxyMaker
這里對應我們的命令是:ProxyMaker.exe /a ..\Demo\Server\bin\Debug\Server.exe /i IDemoServer ,我們可以把它放到批處理文件,以后雙擊就可以編譯出Server.dll;
編寫客戶端
有了Server.dll,編寫客戶端的難度也大大的降低了,新建Client工程,引用NetworkSocke.dll和剛才生成的Server.dll;實例化 DemoServer client = new DemoServer();然后就可以調用里面的方法了,這里和wcf客戶端幾乎完全一樣;由於比較簡單,客戶端代碼中我就不注釋了。

using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Windows.Forms; using Entity; using Server; namespace Client { static class Program { /// <summary> /// 應用程序的主入口點。 /// </summary> static void Main() { DemoServer client = new DemoServer(); client.OnGetDateTime += new DemoServer.GetDateTime(client_OnGetDateTime); client.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8181)); Random ran = new Random(); Userinfo user = new Userinfo() { password = "123456" }; while (true) { Console.ReadLine(); user.name = "A" + ran.Next(0, 100).ToString(); client.Login(user, true, (state) => { if (state) { int x = ran.Next(0, 100); int y = ran.Next(0, 100); int z = ran.Next(0, 100); client.GetSum(x, y, z, (sum) => { Console.WriteLine("{0} + {1} + {2} = {3}", x, y, z, sum); }); } }); } } static DateTime client_OnGetDateTime() { return DateTime.Now; } } }
運行效果