由上一节可以看到我们每次新增加一个服务都需要添加一个proto文件,并且需要手动生成服务端和客户端的代码。
这样是不是很麻烦呢?那么可不可以定义一个公共的proto文件生成一个基础的方法,让所有的自定义方法都通过这个基础方法来执行自定义服务呢。
我们动手看看。
1、在上一节的基础上,在proto里面新增一个Invoke基础方法,定义两个公共参数。代码如下:
syntax="proto3"; package AidenGRPC.RPCBase; service TestServer { rpc GetFeature(Point) returns (Feature) {} rpc Invoke(GrpcRequest) returns (GrpcResponse){} } message GrpcRequest{ string requestMsg=1; } message GrpcResponse{ string responseMsg=1; } message Point { int32 latitude = 1; int32 longitude = 2; } message Feature { string name = 1; Point location = 2; }
2、双击此bat文件重新生成服务端和客户端类,TestServerBase类里面出现了一个新的方法,就是重新生成成功了。
3、新建一个module类库来保存(给每一个模块单独新建一个module类库,后面每个模块可以单独部署,比如后台的服务就叫adminModule),我这里就随便命名了一个module。里面建了一个SayHelloServer的
SayHelloSever里面建一个方法
public class SayHelleServer { public string SayHello(string name) { return $"{name} Say Hello World"; } }
4、服务端新增实现Invoke方法,服务端引用module 项目,这样就把module的dll引入到server的bin目录下了。目的是为了简单的反射执行该方法。新增一个BulidServices的方法来反射执行SayHelloServer的SayHello方法
public static T GetInvokeMethod<T>(GrpcRequest r) { try { string nameSpace = "AidenGRPC.Module"; string className = "SayHelloServer"; string methodName = "SayHello"; //命名空间.类名,程序集 object instance = Assembly.Load(nameSpace).CreateInstance(nameSpace + "." + className); Type type = instance.GetType(); //加载类型 // Type type = Type.GetType(path); //根据类型创建实例 object obj = Activator.CreateInstance(type, true); //加载方法参数类型及方法 MethodInfo method = null; if (r != null && !string.IsNullOrEmpty(r.RequestMsg)) { //加载方法参数类型 Type[] paratypes = new Type[1]; paratypes[0] = r.RequestMsg.GetType(); method = type.GetMethod(methodName, paratypes); //加载有参方法
return (T)method.Invoke(obj, new object[] { r.RequestMsg })
} else { //加载无参方法 method = type.GetMethod(methodName);
return (T)method.Invoke();
} //类型转换并返回 } catch (Exception ex) { //发生异常时,返回类型的默认值。 return default(T); } }
5、在实现方法中添加相关Invoke的实现
public class TestImpl:AidenGRPC.RPCBase.TestServer.TestServerBase { public override Task<Feature> GetFeature(Point request, ServerCallContext context) { return Task.FromResult(new Feature { Name = "aiden", Location = request }); } public override Task<GrpcResponse> Invoke(GrpcRequest r, ServerCallContext context) { GrpcResponse re = new GrpcResponse(); re.ResponseMsg= BuildServices.GetInvokeMethod<string>(r); return Task.FromResult(re); }
6、运行服务端,能够正常启动
7、再客户端写调用Invoke服务的代码,由于服务端定死了Invoke指向的是Sayhello方法,那么我们传一个名字过去,SayHello方法返回的是“Aiden Say Hello World”
Channel channel = new Channel("127.0.0.1:30052", ChannelCredentials.Insecure); var client = new AidenGRPC.RPCBase.TestServer.TestServerClient(channel) ; //Point p = new Point() { Latitude = 409146, Longitude = -88906 }; //Feature f= client.GetFeature(p); GrpcRequest re = new GrpcRequest(); re.RequestMsg = "Aiden"; GrpcResponse rp = client.Invoke(re); Console.WriteLine(rp.ResponseMsg); // Console.WriteLine(string.Format("Name:{0},Latitude:{1},Longitude:{2}", f.Name, f.Location.Latitude, f.Location.Longitude)); channel.ShutdownAsync().Wait(); Console.WriteLine("Press any key to exit client..."); Console.ReadKey();
运行一下客户端,效果如下:
ok这样整个路都走通了,上面我把命名空间,类型,方法名,端口啥的都写死了,这肯定不行的,调用哪个方法应该由客户端传给服务端,服务端根据客户端想要调用的方法信息反射执行后返回相应的值
下一节我们看怎么封装好一些。