前言
本小节是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; } } }
运行效果