仿照这位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. 控制iptables的nat转发端口的实现
  2. 编写配置文件不能出现帮助信息
  3. JavaScript--在页面的下拉框控件中遍历出日期--先天下能力工场
  4. Android之android.system.ErrnoException: open failed: ENOENT (No such file or directory)
  5. 查看mysql主从复制是否成功的命令_mysql主从复制 - hong查理的个人空间 - OSCHINA - 中文开源技术交流社区...
  6. Order By 排序条件中带参数的写法(Oracle数据库、MyBatis)
  7. webbrowser 打开支付宝网页提示无权打开_在押人员生活缴费更便利:支付宝线上“智慧监所”...
  8. spymemcached 的 useNagle 问题与 TCP/IP延迟发送数据
  9. 海康威视:工程项目不是我们的目标,对创新业务发展充满信心...
  10. 下拉选择框 其他_WPS表格下拉菜单的多种做法(一)
  11. 强制更改wifi名前缀CMCC
  12. 大数据项目实训教学解决方案
  13. NERO刻录ISO镜像图解教程
  14. 二叉树——前序和中序得到后序
  15. python批量改文件名,截取原文件名的一部分
  16. building workspace js validation
  17. 车间动态调度的研究方法
  18. 沈阳达内python培训
  19. OpenCV中文手册,非常实用,物有所值
  20. Leetcode LCP2.分式化简

热门文章

  1. Python flask使用ajax上传文件
  2. idea同时打开多个项目
  3. 互联互通、电子病历、智慧服务、智慧管理、公立医院绩效考核的5项测评
  4. 刘德华的号召力,一则广告引发的潮流
  5. 2、线程池篇 - 从理论基础到具体代码示例讲解(持续更新中......)
  6. 25-[案例]百度风云榜(网格布局)
  7. 如何取消掉计算机更新图标,电脑系统总是自动更新图标怎么关闭自动更新
  8. 蓝桥平方怪圈 JAVA
  9. 测试架构师: 软件测试架构师应该做和不该做的事情
  10. python为什么进行单元测试_python单元测试有什么好处