1.1 RPC 是什么

定义:RPC(Remote Procedure Call Protocol)——远程过程调用协议 ,RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层应用层RPC使得开发包括网络分布式多程序在内的应用程序更加容易。

我的理解:与其说把RPC 看作是一种协议,倒不如把 它看作是一种 客户机/服务器交互的模式,但是 RPC一定是基于 TCP 或者 其他 通信协议的

下面我们来看一下一个RPC调用的流程涉及哪些通信细节:

  • 服务消费方(client)调用以本地调用方式调用服务;(1)
  • client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;(2)
  • client stub找到服务地址,并将消息发送到服务端;(3)
  • server stub收到消息后进行解码;(4)
  • server stub根据解码结果调用本地的服务;(5)
  • 本地服务执行并将结果返回给server stub;(6)
  • server stub将返回结果打包成消息并发送至消费方;(7)
  • client stub接收到消息,并进行解码;(8)
  • 服务消费方得到最终结果。(9)

RPC的目标就是要2~8这些步骤都封装起来,让用户对这些细节透明。

 

1.2 手动实现

1.2.1 先做一个空接口实现序列化接口

public interface IRpcService extends Serializable{

}


1.2.2 做一个需要被远程调用的接口 以及对应的接口实现类

public interface IHelloService extends IRpcService{
String sayHi(String name,String message);
}
public class HelloServiceImpl implements IHelloService{

	private static final long serialVersionUID = 146468468464364698L;

	@Override
public String sayHi(String name, String message) {
return new StringBuilder().append("hi~!").append(",").append(message).toString();
} }


1.2.3 需要写一个服务端,主要的作用 是进行服务注册(接口注册) 以及 接收客户端的调用参数 执行调用请求 返回结果

注:这个地方 我没有采用dom4j 解析配置文件的形式 进行接口注册 有时间的朋友可以多加一层

public interface Server {

	//Socket端口
int PORT = 8080; //启动服务端
void start() throws IOException; //停止服务端
void stop(); /**
* 服务注册
* -- serviceInterface 对外暴露接口
* -- 内部实现类
*/
void regist(Class<? extends IRpcService> serviceInterface,Class<? extends IRpcService> impl); }
public class ServerCenter implements Server{

	/**线程池 接收客户端调用**/
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 20, 200, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(10));
/**服务注册缓存**/
public static final Map<String,Class<?>> serviceRegistry = new HashMap<>(); /**
* 启动服务
*/
@Override
public void start() throws IOException {
ServerSocket server = new ServerSocket();
server.bind(new InetSocketAddress(PORT));
try {
while(true){
executor.execute(new ServiceTask(server.accept()));
}
} finally {
server.close();
}
} /**
* 停止服务
*/
@Override
public void stop() {
executor.shutdown();
} /**
* 注册服务
*/
@Override
public void regist(Class<? extends IRpcService> serviceInterface, Class<? extends IRpcService> impl) {
serviceRegistry.put(serviceInterface.getName(), impl);
} private static class ServiceTask implements Runnable{ Socket client = null; public ServiceTask(Socket client) {
this.client = client;
} @Override
public void run() {
ObjectInputStream input = null;
ObjectOutputStream output = null; try { input = new ObjectInputStream(client.getInputStream());
String serviceName = input.readUTF();
String methodName = input.readUTF(); Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
Object[] arguments = (Object[]) input.readObject();
Class<?> serviceClass = serviceRegistry.get(serviceName);
if(serviceClass == null){
throw new ClassNotFoundException(serviceName + "not found");
}
Method method = serviceClass.getMethod(methodName, parameterTypes);
Object result = method.invoke(serviceClass.newInstance(), arguments); //将执行结果反序列化 通过socket返给客户端
output = new ObjectOutputStream(client.getOutputStream());
output.writeObject(result); } catch (Exception e) {
e.printStackTrace();
} finally { if(input != null){
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
} if(output != null){
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
} if(client != null){
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
} }
} } public static void main(String[] args) throws Exception {
ServerCenter center = new ServerCenter();
center.regist(IHelloService.class,new HelloServiceImpl().getClass());
center.start();
}
}


1.2.4 写一个客户端,用动态代理 获取被代理接口的 各种参数 传输给 服务端,接收返回结果,打印到控制台

public class Client {

	@SuppressWarnings("unchecked")
public static <T extends IRpcService>T getRemoteProxyObj(final Class<? extends IRpcService> serviceInterface,final InetSocketAddress addr){
return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class<?>[]{serviceInterface}, new InvocationHandler() { @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket socket = null;
ObjectOutputStream output = null;
ObjectInputStream input = null;
try {
//1.创建Socket客户端,根据指定地址连接远程服务提供者
socket = new Socket();
socket.connect(addr); //2.将远程服务调用所需的接口类、方法名、参数列表等编码后发送给服务提供者
output = new ObjectOutputStream(socket.getOutputStream());
output.writeUTF(serviceInterface.getName());
output.writeUTF(method.getName());
output.writeObject(method.getParameterTypes());
output.writeObject(args); //3.同步阻塞等待服务器返回应答 获取应答后返回
input = new ObjectInputStream(socket.getInputStream());
return input.readObject();
} finally{
if(socket != null){
socket.close();
} if(output != null){
output.close();
} if(input != null){
input.close();
}
}
}
});
} }


1.2.5 测试

注:测试之前 需要开启服务端

public class RpcTest {
public static void main(String[] args) throws IOException {
IHelloService service = Client.getRemoteProxyObj(IHelloService.class, new InetSocketAddress(8080));
System.out.println(service.sayHi("张三", "新年快乐!"));
}
}

就这样我们实现了一个简陋的RPC

本文意在通过实现简单的RPC,去真正意义上对RPC框架的实现原理有初步的了解,而不是人云亦云。

此RPC实现有诸多缺点,但是 我们只要明白RPC的基座 其他的RPC框架只是完善基座以及扩展而已 。

rpc简单实现git代码地址

手把手教你写一个RPC的更多相关文章

  1. 只有20行Javascript代码!手把手教你写一个页面模板引擎

    http://www.toobug.net/article/how_to_design_front_end_template_engine.html http://barretlee.com/webs ...

  2. 手把手教你写一个java的orm(五)

    生成sql:where 上一篇里我们实现了生成insert的sql,下面要开始实现update,delete,select的sql语句了.但是这些语句有一个比较麻烦的地方是:它们一般后面都会有wher ...

  3. 手把手教你写一个java的orm(一)

    写之前的说明 其实吧. 这个东西已经写好了,地址在:https://github.com/hjx601496320/JdbcPlus 这系列文章算是我写的过程的总结吧.(恩系列,说明我可能会写好久,╮ ...

  4. 让我手把手教你写一个强大、方便使用的 IOC 容器

    一.介绍 1.介绍 最近无聊,也没什么事做,没事做总是要给自己找点事情做吧,毕竟人的生活在与折腾.于是,决定自己手动写一个 IOC 的框架.我们知道在 NetCore 的版本里面已经内置了 IOC 容 ...

  5. 手把手教你写一个RN小程序!

    时间过得真快,眨眼已经快3年了! 1.我的第一个App 还记得我14年初写的第一个iOS小程序,当时是给别人写的一个单机的相册,也是我开发的第一个完整的app,虽然功能挺少,但是耐不住心中的激动啊,现 ...

  6. 手把手教你写一个java的orm(完)

    生成sql:select 上一篇讲了怎样生成一个sql中where的一部分,之后我们要做事情就简单很多了,就只要像最开始一样的生成各种sql语句就好了,之后只要再加上我们需要的条件,一个完整的sql就 ...

  7. 手把手教你写一个java的orm(四)

    开始准备生成sql 在上一篇里,我们已经取到了我们在生成sql语句中所需要的信息,这一篇里我们开始根据class来生成我们需要的sql.在这之前我们先确认几件事情 sql里的参数我们使用占位符的形式. ...

  8. 手把手教你写一个java的orm(三)

    使用反射解析class 上一篇我们完成了class到表映射关系的建立,但是这个并不能被代码正确处理,我们还需要让程序能够正确的识别这些映射关系. 这一篇主要讲的是建立一个从class到表的模型,使我们 ...

  9. 手把手教你写一个java的orm(二)

    创建映射关系 ​ 想要实现一个orm的功能,我觉得就是要将class和数据库中的表创建映射关系.把class的名称和表的名称,class属性名称和表的字段名称,属性类型与表的字段类型一一对应起来.可以 ...

随机推荐

  1. centos networkmanager 和 network配置冲突

    1.由于NetworkManager与 network 有冲突,所以要把NetworkManager关掉

  2. [CSS3] 3D桃心

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  3. mxonline实战9,我要学习功能块,机构详情展示,收藏功能

    对应github地址:第9天   一. 实现我要学习功能

  4. SpringQuartz定时任务cronExpression配置

    <!-- 每一个小时跑一次--> <property name="cronExpression"> <value>0 0 0/1 * * ?&l ...

  5. .crx 文件修改

    .crx 文件类型:Chrome Extension 扩展名为.crx的文件是一个插件文件. 解压:使用7zip 修改: notepad++ 打包: Chrome 扩展项

  6. 华南理工大学“三七互娱杯”程序设计竞赛 HRY and codefire(概率期望DP)

    https://ac.nowcoder.com/acm/contest/874/A 题目:有两个账号 , 一开始都为0级 , 求任意一个账号升级到N的期望 要求:如果当前账号嬴了 , 就继续沿用当前的 ...

  7. 2019.04.07 第三次训练 【WHU校赛】

    A: (模拟退火+点到线段最短距离) https://blog.csdn.net/Link_Ray/article/details/89173222 B:✅ C: (线段树+二分) https://b ...

  8. How to install Jenkins on CentOS 7

    How to install Jenkins on CentOS 7 on March 3, 2018 by AmirLeave a comment Introduction Jenkins is a ...

  9. Mac下配置Java Web开发环境(Mac 10.12)

    1.JDK http://www.cnblogs.com/EasonJim/p/6277541.html 2.MySQL http://www.cnblogs.com/EasonJim/p/62758 ...

  10. Unity 依赖注入

    关于Ioc的框架有很多,比如astle Windsor.Unity.Spring.NET.StructureMap,我们这边使用微软提供的Unity做示例,你可以使用Nuget添加Unity,也可以引 ...