C#实现RPC(远程过程调用)
仿照这位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(远程过程调用)相关推荐
- 微服务之RPC(远程过程调用)的四种方式
微服务思想 微服务思想-注册中心zookeeper 微服务: 架构设计采用分布式思想,当服务器发生故障时,可以实现自动化的故障迁移.无需人为干预. 注册中心实现原理: ZK工作原理说明 Zookeep ...
- spring-boot重头再来 6 分布式理论 RPC远程过程调用 Zookeeper安装 Dubbo SpringBoot + Dubbo + zookeeper Spring Security
spring-boot重头再来 6 文章目录 spring-boot重头再来 6 分布式理论 RPC远程过程调用 Zookeeper安装 Dubbo dubbo-admin安装 dubbo-admin ...
- rpc远程过程调用_什么是远程过程调用(RPC)?
rpc远程过程调用 Remote Procedure Call (RPC) is a protocol or architecture which is used to run programmes ...
- RPC远程过程调用简介
1. 什么是RPC 远程过程调用(英语:Remote Procedure Call,缩写为 RPC,也叫远程程序调用)是一个计算机通信协议.该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而 ...
- RPC(远程过程调用协议)介绍
RPC框架解释 谁能用通俗的语言解释一下什么是RPC框架? -远程过程调用协议RPC(Remote Procedure Call Protocol) 首先了解什么叫RPC,为什么要RPC,RPC是指远 ...
- RPC 远程过程调用协议
RPC(Remote Procedure Call Protocol)--远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议. RPC协议假定某些传输协议的存 ...
- 译:6.RabbitMQ Java Client 之 Remote procedure call (RPC,远程过程调用)
在 译:2. RabbitMQ 之Work Queues (工作队列) 我们学习了如何使用工作队列在多个工作人员之间分配耗时的任务. 但是如果我们需要在远程计算机上运行一个函数并等待结果呢?嗯,这 ...
- RPC 远程过程调用(Remote Procedure Call)
是RPC框架,首先了解什么叫RPC,为什么要RPC,RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调 ...
- 浅谈rpc(远程过程调用)
http://www.cppblog.com/jb8164/archive/2008/08/17/58949.html http://forum.eviloctal.com/thread-15010- ...
- java rpc远程调用_浅谈rpc(远程过程调用)
信息来源:邪恶八进制信息安全团队 RPC协议2:这个协议是一个够年头的协议 本文介绍用于ONC RPC协议规范.此协议使用XDR语言进行描述,并文不打算描述具体的使用细节而只介绍RPC协议本身. ON ...
最新文章
- 控制iptables的nat转发端口的实现
- 编写配置文件不能出现帮助信息
- JavaScript--在页面的下拉框控件中遍历出日期--先天下能力工场
- Android之android.system.ErrnoException: open failed: ENOENT (No such file or directory)
- 查看mysql主从复制是否成功的命令_mysql主从复制 - hong查理的个人空间 - OSCHINA - 中文开源技术交流社区...
- Order By 排序条件中带参数的写法(Oracle数据库、MyBatis)
- webbrowser 打开支付宝网页提示无权打开_在押人员生活缴费更便利:支付宝线上“智慧监所”...
- spymemcached 的 useNagle 问题与 TCP/IP延迟发送数据
- 海康威视:工程项目不是我们的目标,对创新业务发展充满信心...
- 下拉选择框 其他_WPS表格下拉菜单的多种做法(一)
- 强制更改wifi名前缀CMCC
- 大数据项目实训教学解决方案
- NERO刻录ISO镜像图解教程
- 二叉树——前序和中序得到后序
- python批量改文件名,截取原文件名的一部分
- building workspace js validation
- 车间动态调度的研究方法
- 沈阳达内python培训
- OpenCV中文手册,非常实用,物有所值
- Leetcode LCP2.分式化简
热门文章
- Python flask使用ajax上传文件
- idea同时打开多个项目
- 互联互通、电子病历、智慧服务、智慧管理、公立医院绩效考核的5项测评
- 刘德华的号召力,一则广告引发的潮流
- 2、线程池篇 - 从理论基础到具体代码示例讲解(持续更新中......)
- 25-[案例]百度风云榜(网格布局)
- 如何取消掉计算机更新图标,电脑系统总是自动更新图标怎么关闭自动更新
- 蓝桥平方怪圈 JAVA
- 测试架构师: 软件测试架构师应该做和不该做的事情
- python为什么进行单元测试_python单元测试有什么好处