仿照这位Up主写的:Up主视频
项目地址:在这


首先 什么事RPC

  • 看知乎上这几个回答
  • 就像在本地调用函数一样去调用远程的函数,但是本地和远端拥有不同内存空间直接调用肯定是没有办法的,所以思路就是,我在本地调用方法,内部实现利用网络消息传输,去调用远程的函数。本质还是消息协议,只不过是在解析完协议又封装了一层用于调用具体的方法

注意

  • up主用的的是大端(高尾端),我这个因为习惯用的是小端(低尾端)
  • 剩下的就是写法问题,没什么太大差别
  • 只是为了了解具体的思路,里面存在很多问题,比如没有处理粘包分包问题,字节缓存,运行时直接利用反射等等。

客户端(调用端)

  • 首先我们上面说的就像在本地调用函数一样去调用远程的函数。仔细想想,A调用函数Func实际执行的是B的Func。好了,现在有了一个规范了两端都要实现同样的函数。看红字强调了实现这2个字,所以我们采用接口形式,两端都实现接口IUserInfo需要实现是登陆与加法
    interface IUserInfo{bool Login(string account, string pwd);int Add(int a, int b);}
  • 在看具体实现先看一下使用的几个数据结构,内置对象的枚举,ArgTypeInfo是参数附带类型枚举。
    public class ArgTypeInfo{public TypeEnum argType { get; set; }public object value { get; set; }}public enum TypeEnum{Void = 0,Int,Bool,String}
  • EncodeSendPackage主要用于将接口名称、方法名称、参数个数、返回类型以及参数,然后依照下面的RPC协议格式进行编码得到字节数组。
/* RPC协议格式接口名长度(1字节)、接口名, 方法名长度(1字节),方法名,参数长度(1字节),返回类型,参数序列(string类型前面有字符串长度),int类型是32位
*/ private static byte[] EncodeSendPackage(string interfaceName, string methodName, int argLen, TypeEnum returnType, List<ArgTypeInfo> argTypeInfos){// 接口名称List<byte> byteList = new List<byte>();byte[] interfaceBytes = Encoding.UTF8.GetBytes(interfaceName);byteList.Add((byte)interfaceBytes.Length);byteList.AddRange(interfaceBytes);// 方法名称byte[] methodNameBytes = Encoding.UTF8.GetBytes(methodName);byteList.Add((byte)methodNameBytes.Length);byteList.AddRange(methodNameBytes);// 参数个数byteList.Add((byte)argLen);// 返回类型byteList.Add((byte)returnType);// 参数列表foreach (ArgTypeInfo ati in argTypeInfos){byteList.Add((byte)ati.argType);if (ati.argType == TypeEnum.String){string value = ati.value as string;byte[] stringBytes = Encoding.UTF8.GetBytes(value);byteList.Add((byte)stringBytes.Length);byteList.AddRange(stringBytes);}else if (ati.argType == TypeEnum.Int){int value = Convert.ToInt32(ati.value);byte[] intBytes = Int2Bytes(value);byteList.AddRange(intBytes);}else if (ati.argType == TypeEnum.Bool){bool value = Convert.ToBoolean(ati.value);byte boolBytes = value ? (byte)1 : (byte)0;byteList.Add(boolBytes);}}return byteList.ToArray();}}
  • 里面出现的Int2Bytes和Bytes2Int如下(小端格式)
        private static byte[] Int2Bytes(int val){byte[] res = new byte[4];res[0] = (byte)(val >> 0);res[1] = (byte)(val >> 8);res[2] = (byte)(val >> 16);res[3] = (byte)(val >> 24);return res;}private static int Bytes2Int(byte[] bytes){int res = (bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24));return res;}
  • 然后看具体接口实现,这里我为了图方便,有很多重复代码而且实现的很粗暴不要过于在意,可以看到参数被封装成ArgTypeInfo传递,然后依靠socket发出。
        public bool Login(string account, string pwd){byte[] sendBytes = EncodeSendPackage(nameof(IUserInfo), "Login", 2, TypeEnum.Bool, new List<ArgTypeInfo>() {new ArgTypeInfo(){argType = TypeEnum.String,value = account},new ArgTypeInfo(){argType = TypeEnum.String,value = pwd}});Socket sk = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);sk.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8888));sk.Send(sendBytes);byte[] rBuffer = new byte[1024];int rCount = sk.Receive(rBuffer);if (rCount > 0){return rBuffer[0] == 1;}throw new Exception();}public int Add(int a, int b){byte[] sendBytes = EncodeSendPackage(nameof(IUserInfo), "Add", 2, TypeEnum.Int, new List<ArgTypeInfo>() {new ArgTypeInfo(){argType = TypeEnum.Int,value = a},new ArgTypeInfo(){argType = TypeEnum.Int,value = b}});Socket sk = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);sk.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8888));sk.Send(sendBytes);byte[] rBuffer = new byte[1024];int rCount = sk.Receive(rBuffer);if (rCount > 0){int res = Bytes2Int(rBuffer);return res;}throw new Exception();}

服务端

  • 可以看到客户端已经将消息发出了,服务端就需要开启监听套接字,监听连接请求以及解析,并根据解析的信息去调用相应的方法。
  • 首先来看服务端具体接口如何实现,简单的判断账号密码是否符合要求,以及加法功能。
    public class MyUserInfo : IUserInfo{public int Add(int a, int b){return a + b;}public bool Login(string account, string pwd){if (account == "abc" && pwd == "123"){return true;}return false;}}
  • 然后是让服务器开启监听,并处理相应的连接请求,代码很基础有注释
        private static Socket listenSocket;static void Main(string[] args){listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);IPAddress ipadr = IPAddress.Parse("0.0.0.0");IPEndPoint iPEnd = new IPEndPoint(ipadr, 8888);listenSocket.Bind(iPEnd);listenSocket.Listen(0);Thread t = new Thread(Execute);// 后台线程随着主线程结束而结束t.IsBackground = true;t.Start();Console.WriteLine("Server Lauch");Console.Read();}private static void Execute(){while (true){Socket client = listenSocket.Accept();Thread cThread = new Thread(SingleClientExecute);cThread.IsBackground = true;cThread.Start(client);}}
  • SingleClientExecute就是具体的解析了,里面是在运行时进行反射调用效率很低,这里只是为了验证,下面是具体代码,注释都有
private static void SingleClientExecute(object obj){Socket client = obj as Socket;byte[] rBuffer = new byte[1024];int rCount = client.Receive(rBuffer);if (rCount > 0){MemoryStream ms = new MemoryStream(rBuffer);BinaryReader br = new BinaryReader(ms);// RPC协议格式// 接口名长度(1字节)、接口名, 方法名长度(1字节),方法名,参数长度(1字节),返回类型,// 参数序列(string类型前面有字符串长度),int类型是32位// 接口名int interfaceLen = br.ReadByte();byte[] interfaceBytes = br.ReadBytes(interfaceLen);string interfaceName = Encoding.UTF8.GetString(interfaceBytes);// 方法名int methodNameLen = br.ReadByte();byte[] methodNameBytes = br.ReadBytes(methodNameLen);string methodName = Encoding.UTF8.GetString(methodNameBytes);// 参数长度int argsLen = br.ReadByte();// 返回类型int returnType = br.ReadByte();// 解析argsLen个参数List<object> argsList = new List<object>();for (int i = 0; i < argsLen; i++){int singleArgType = br.ReadByte();if (singleArgType == 1){ // intbyte[] intBytes = br.ReadBytes(4);int value = Bytes2Int(intBytes);argsList.Add(value); //涉及装箱}else if (singleArgType == 2){ // boolbool value = br.ReadByte() == 1;argsList.Add(value);}else if (singleArgType == 3){ // stringint stringBytesLen = br.ReadByte();byte[] stringBytes = br.ReadBytes(stringBytesLen);string value = Encoding.UTF8.GetString(stringBytes);argsList.Add(value);}}// 下面通过得到的信息,去调用相关方法Type interfaceType = Type.GetType(MethodBase.GetCurrentMethod().DeclaringType.Namespace + "." + interfaceName);if (interfaceType == null){ //没有这个接口throw new Exception();}Type subClassType = null;var types = Assembly.GetExecutingAssembly().GetTypes();foreach (var type in types){if (interfaceType.IsAssignableFrom(type) && type != interfaceType){subClassType = type;break;}}if (subClassType == null) throw new Exception();MethodInfo[] methodInfos = subClassType.GetMethods();MethodInfo method = null;foreach (var mi in methodInfos){if (mi.Name == methodName){method = mi;break;}}if (method == null) throw new Exception();// 执行object instance = Activator.CreateInstance(subClassType);// 得到结果 用于发送object res = method.Invoke(instance, argsList.ToArray());if (returnType == 1){ // intint value = Convert.ToInt32(res);byte[] resBytes = Int2Bytes(value);client.Send(resBytes);return;}else if (returnType == 2){ // boolbool value = Convert.ToBoolean(res);byte[] boolBytes = new byte[1] { value ? (byte)1 : (byte)0 };client.Send(boolBytes);return;}else if (returnType == 3){ // stringList<byte> sendBytes = new List<byte>();byte[] strBytes = Encoding.UTF8.GetBytes(res.ToString());sendBytes.Add((byte)strBytes.Length);sendBytes.AddRange(strBytes);client.Send(sendBytes.ToArray());return;}}}
  • 具体代码在上面的Git项目。在Exe文件中有生成的exe文件可以跑一下试试。

C#实现RPC(远程过程调用)相关推荐

  1. 微服务之RPC(远程过程调用)的四种方式

    微服务思想 微服务思想-注册中心zookeeper 微服务: 架构设计采用分布式思想,当服务器发生故障时,可以实现自动化的故障迁移.无需人为干预. 注册中心实现原理: ZK工作原理说明 Zookeep ...

  2. spring-boot重头再来 6 分布式理论 RPC远程过程调用 Zookeeper安装 Dubbo SpringBoot + Dubbo + zookeeper Spring Security

    spring-boot重头再来 6 文章目录 spring-boot重头再来 6 分布式理论 RPC远程过程调用 Zookeeper安装 Dubbo dubbo-admin安装 dubbo-admin ...

  3. rpc远程过程调用_什么是远程过程调用(RPC)?

    rpc远程过程调用 Remote Procedure Call (RPC) is a protocol or architecture which is used to run programmes ...

  4. RPC远程过程调用简介

    1. 什么是RPC 远程过程调用(英语:Remote Procedure Call,缩写为 RPC,也叫远程程序调用)是一个计算机通信协议.该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而 ...

  5. RPC(远程过程调用协议)介绍

    RPC框架解释 谁能用通俗的语言解释一下什么是RPC框架? -远程过程调用协议RPC(Remote Procedure Call Protocol) 首先了解什么叫RPC,为什么要RPC,RPC是指远 ...

  6. RPC 远程过程调用协议

    RPC(Remote Procedure Call Protocol)--远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议. RPC协议假定某些传输协议的存 ...

  7. 译:6.RabbitMQ Java Client 之 Remote procedure call (RPC,远程过程调用)

    在  译:2. RabbitMQ 之Work Queues (工作队列)  我们学习了如何使用工作队列在多个工作人员之间分配耗时的任务. 但是如果我们需要在远程计算机上运行一个函数并等待结果呢?嗯,这 ...

  8. RPC 远程过程调用(Remote Procedure Call)

    是RPC框架,首先了解什么叫RPC,为什么要RPC,RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调 ...

  9. 浅谈rpc(远程过程调用)

    http://www.cppblog.com/jb8164/archive/2008/08/17/58949.html http://forum.eviloctal.com/thread-15010- ...

  10. java rpc远程调用_浅谈rpc(远程过程调用)

    信息来源:邪恶八进制信息安全团队 RPC协议2:这个协议是一个够年头的协议 本文介绍用于ONC RPC协议规范.此协议使用XDR语言进行描述,并文不打算描述具体的使用细节而只介绍RPC协议本身. ON ...

最新文章

  1. 用 C 语言开发一门编程语言 — 变量元素设计
  2. Python中常用字符串 函数-转
  3. [Python人工智能] 三十三.Bert模型 (2)keras-bert库构建Bert模型实现文本分类
  4. mybatis一级缓存二级缓存
  5. R语言和 Python —— 一个错误的分裂
  6. javascript闭包_通过邮寄包裹解释JavaScript闭包
  7. [html] 你有使用过IE的条件注释吗?
  8. C#多线程技术总结(异步)
  9. 求之不得的 Java 文档教程大汇总!
  10. 两个运放制作加法器_同相加法器电路图_反相加法器电路图_运放加法器电路图解析...
  11. dreamhost 优惠码_DreamHost如何通过OpenStack重塑自己
  12. 网购成瘾是一种精神障碍?将导致抑郁和破坏社交 网友:没钱即可根治
  13. 编程语言对比 引用数据类型-列表
  14. Redis实战篇(视频学习来自黑马程序员)
  15. alin的学习之路(Qt篇:三)(常用控件,自定义控件,事件捕捉)
  16. 设计心理学读书笔记 之一 记忆的结构
  17. ue4 射线Trace Responses(踪迹响应)
  18. Android10 HAL模块的实现
  19. 页面加载缓冲的login
  20. 树莓派wiringPi输出PMW

热门文章

  1. 使用Siri+捷径控制ESP8266+WS2812B
  2. (转)如何判断你的实际收入有多少
  3. spring 发送qq邮件内容和附件(样式发送邮件)
  4. MySQL数据库入门与实战
  5. 如何删除正在运行的dll文件
  6. git 中merge的用法
  7. 微信封禁大量色情账号
  8. JS 利用vue过滤器将阿拉伯数字转化为汉字
  9. 树莓派4B一键变身无线路由
  10. win10 定期的なタスク-schtasks