gpRPC是基于google protobuf结构扩展出来的RPC通讯组件,它默认交互的是protobuf格式的消息。通讯中处理消息是一件繁琐的工作,为了让gpRPC使用起来更方便简单,组件扩展了基于逻辑接口的方式来处理消息;gpRPC分别针对了服务端和客户端接口调用的扩展,接下来详细讲述这个实现.
服务端
由于基于消息处理所以在处理接口上还是有些限制的,接口方法只接通一个参数定义,而这个参数的类型必须实现了google protobuf组件的IMessage接口,而方法的返回值侧是Task或Task<MSG>类型,因为这样才能更好协同异步处理,而MSG同样也要实现IMessage接口作为规则。
首先需要定义一个方法调用描述的结构
internal class MethodInvokeHandler
{
public MethodInvokeHandler(MethodInfo method)
{
ResultProperty = method.ReturnType.GetProperty("Result", BindingFlags.Public | BindingFlags.Instance);
Method = method;
}
public MethodInfo Method { get; set; }
public PropertyInfo ResultProperty
{
get; set;
}
public object Service { get; set; }
}
这结构保存了方法的反射调用信息和对应的逻辑对象;在这基础上再装一个类用于存储所有消息对应的方法信息。
internal class ServiceMethodHandlers
{
private Dictionary<Type, MethodInvokeHandler> _methods = new Dictionary<Type, MethodInvokeHandler>();
private static readonly ServiceMethodHandlers _default = new ServiceMethodHandlers();
public static ServiceMethodHandlers Default => _default;
public ServiceMethodHandlers()
{
}
public MethodInvokeHandler GetMethod(Type msgType)
{
_methods.TryGetValue(msgType, out var method);
return method;
}
public Func<Type, object> ServiceCreateInstanceHandler { get; set; }
private object ServiceCreateInstance(Type type)
{
if (ServiceCreateInstanceHandler != null)
return ServiceCreateInstanceHandler(type);
return Activator.CreateInstance(type);
}
public void Register(Type type, IGetLogHandler loger)
{
var service = ServiceCreateInstance(type);
Type gtask = Type.GetType("System.Threading.Tasks.Task`1");
foreach (var method in type.GetMethods(BindingFlags.Instance | BindingFlags.Public))
{
if (string.Compare("Equals", method.Name, true) == 0
|| string.Compare("GetHashCode", method.Name, true) == 0
|| string.Compare("GetType", method.Name, true) == 0
|| string.Compare("ToString", method.Name, true) == 0 || method.Name.IndexOf("set_") >= 0
|| method.Name.IndexOf("get_") >= 0 || method.GetParameters().Length != 1 ||
(method.ReturnType.Name != "Task`1" && method.ReturnType != typeof(Task)))
continue;
var req = method.GetParameters()[0].ParameterType;
Type resp = null;
if (method.ReturnType.IsGenericType)
{
resp = method.ReturnType.GetGenericArguments()[0];
}
if (req.GetInterface("Google.Protobuf.IMessage") != null && (method.ReturnType == typeof(Task) || resp.GetInterface("Google.Protobuf.IMessage") != null))
{
loger.GetLoger(LogLevel.Info)?.Write((EndPoint)null, "gpRPC", "ObjectMapping", $"{req.Name} mapping to {type.Name}.{method.Name}");
var handler = new MethodInvokeHandler(method);
handler.Service = service;
_methods[req] = handler;
}
}
}
}
在注册方法时会对一些不符合规则的方法过虑,只加载符合规则的方法。接下来需要一个简单的标记用于描述接口服务方法的类
[AttributeUsage(AttributeTargets.Class)]
public class RpcServiceAttribute : Attribute
{
}
有了这个标记,服务端在启动的时候就可以加载这些类方法了
public void RegisterMessages<T>()
{
ProtocolMessageMapperFactory.UintMapper.RegisterAssembly<T>();
foreach (var type in typeof(T).Assembly.GetTypes())
{
if (type.GetCustomAttribute<RpcServiceAttribute>() != null)
_serviceTypes.Add(type);
}
}
可以把带有RpcServiceAttribute标记对象的方法添加了消息映射表中,有了这个表就可以在消息接收的时候去匹配这个表获取执行方法信息了。
protected virtual async Task OnReceiveMessage(RpcMessage req)
{
RpcMessage resp = new RpcMessage();
resp.Identifier = req.Identifier;
var method = ServiceMethodHandlers.Default.GetMethod(req.Body.GetType());
if (method != null)
{
try
{
Task task = (Task)method.Method.Invoke(method.Service, new object[] { req.Body });
await task;
if (method.ResultProperty != null)
{
var result = method.ResultProperty.GetValue(task);
resp.Body = result;
}
else
{
Success success = new Success();
resp.Body = success;
}
NetContext.GetLoger(LogLevel.Debug)?.Write(NetContext, "gpRPCSession", "InvokeSuccess", $"{req.Body.GetType().Name}");
}
catch (Exception e_)
{
Error error = new Error();
error.ErrorCode = RpcException.METHOD_INVOKE_ERROR;
error.ErrorMessage = e_.Message;
error.StackTrace = e_.StackTrace;
resp.Body = error;
NetContext?.GetLoger(LogLevel.Error)?.Write(NetContext, "gpRPCSession", "InvokeError", $"{req.Body.GetType().Name} invok error {e_.Message} {e_.StackTrace}!");
}
}
else
{
Error error = new Error();
error.ErrorCode = RpcException.METHOD_NOTFOUND;
error.ErrorMessage = $"{req.Body.GetType().Name} handler not found!";
resp.Body = error;
NetContext?.GetLoger(LogLevel.Warring)?.Write(NetContext, "gpRPCSession", "InvokeError", $"{req.Body.GetType().Name} handler not found!");
}
NetContext?.Send(resp);
}
接收消息从消息方法映射表中获取具体方法调用的对象,然后调用反射调用方法并await 对应返回的Task对象;当Task是泛型的时返回对应的内部返回值,否则给调用端返回一个 Success结构。
客户端
对于客户调用则需要实现接口代理了,C#实现接口代理非常简单,只需要继承DispatchProxy即可以实列接口代理
public class RpcInterfaceProxy : DispatchProxy
{
internal RpcClient RpcClient { get; set; }
internal Type Type { get; set; }
protected override object Invoke(MethodInfo targetMethod, object[] args)
{
var req = args[0].GetType();
if (mHandlers.TryGetValue(req, out var handler))
{
var resp = handler.GetCompletionSource();
var reqs = RpcClient.Request((IMessage)args[0]);
resp.Wait(reqs);
return resp.GetTask();
}
else
{
throw new NotImplementedException(targetMethod.Name);
}
}
private Dictionary<Type, ActionHandler> mHandlers = new Dictionary<Type, ActionHandler>();
private Dictionary<string, string> mHeader = new Dictionary<string, string>();
internal void InitHandlers()
{
Type type = Type;
Type gtask = Type.GetType("System.Threading.Tasks.Task`1");
foreach (MethodInfo method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
if (string.Compare("Equals", method.Name, true) == 0
|| string.Compare("GetHashCode", method.Name, true) == 0
|| string.Compare("GetType", method.Name, true) == 0
|| string.Compare("ToString", method.Name, true) == 0 || method.Name.IndexOf("set_") >= 0
|| method.Name.IndexOf("get_") >= 0 || method.GetParameters().Length != 1 ||
(method.ReturnType.Name != "Task`1" && method.ReturnType != typeof(Task)))
continue;
var req = method.GetParameters()[0].ParameterType;
Type resp = null;
if (method.ReturnType.IsGenericType)
{
resp = method.ReturnType.GetGenericArguments()[0];
}
if (req.GetInterface("Google.Protobuf.IMessage") != null && (method.ReturnType == typeof(Task) || resp?.GetInterface("Google.Protobuf.IMessage") != null))
{
ActionHandler action = new ActionHandler(method);
mHandlers[req] = action;
}
}
}
}
同样在代理实例化之前初始化方法信息,对于符合规则的方法才允许调用。Invoke方法则是接口代理时上体执行的过程,根据MethodInfo信息找到对应的方法调用信息后,向RpcClient发送对应的google protobuf消息。
internal async Task<object> Request(IMessage message)
{
RpcMessage req = new RpcMessage();
req.Body = message;
var result = (RpcMessage)await ((IAwaiterNetClient)this).Request(req);
if (result.Body is Error err)
{
throw new RpcException( $"{message.GetType().Name} remote invoke error {err.ErrorMessage}");
}
return result.Body;
}
internal async Task<RESP> Request<RESP>(IMessage message)
where RESP : IMessage
{
return (RESP)await Request(message);
}
RpcClient最终也只是往BeetleX的NetClient对象发送一个消息,而BeetleX则通过应用层传递过来的协议通道来做具体的处理过程
public class ProtobufChannel<T> : IProtocolChannel<T>
where T : INetContext
{
public string Name => "GoogleProtobuf";
public T Context { get; set; }
public object Clone()
{
var result = new ProtobufChannel<T>();
return result;
}
public void Encoding(IStreamWriter writer, object message)
{
writer.WriteBinaryObject(HeaderSizeType.UInt, message,
(stream, msg) =>
{
RpcMessage rpcMessage = msg as RpcMessage;
rpcMessage.Type = ProtocolMessageMapperFactory.UintMapper.WriteType(stream, rpcMessage.Body, writer.LittleEndian);
stream.Write(rpcMessage.Identifier);
IMessage message1 = (IMessage)rpcMessage.Body;
message1.WriteTo(stream);
});
}
public void Decoding(IStreamReader reader, Action<T, object> completed)
{
while (reader.TryReadBinaryObject(HeaderSizeType.UInt,
out object result, memory =>
{
RpcMessage rpcMessage = new RpcMessage();
var type = ProtocolMessageMapperFactory.UintMapper.ReadType(memory, reader.LittleEndian);
rpcMessage.Type = type.Value;
memory = memory.Slice(type.BuffersLength);
rpcMessage.Identifier = memory.Span.ReadUInt64();
memory = memory.Slice(8);
IMessage message = (IMessage)Activator.CreateInstance(type.MessageType);
message.MergeFrom(memory.Span);
rpcMessage.Body = message;
return rpcMessage;
})
)
{
completed(Context, result);
}
}
public void Dispose()
{
}
}
以上就是gpRPC消息和网络数据流转化的过程了,实现BeetleX的IProtocolChannel接口的Encoding和Decoding即可。到这里gpRPC处理消息调用接口功能相关实现代码就完成,是不是非常简单?而这么简单的处理也得利于BeetleX针对基础网络通讯功能封装。
BeetleX
开源跨平台通讯框架(支持TLS)
提供HTTP,Websocket,MQTT,Redis,RPC和服务网关开源组件
个人微信:henryfan128 QQ:28304340
有丰富的高吞吐网络服务设计经验
关注公众号
https://github.com/beetlex-io/