手把手教你写一个RPC
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的更多相关文章
- 只有20行Javascript代码!手把手教你写一个页面模板引擎
http://www.toobug.net/article/how_to_design_front_end_template_engine.html http://barretlee.com/webs ...
- 手把手教你写一个java的orm(五)
生成sql:where 上一篇里我们实现了生成insert的sql,下面要开始实现update,delete,select的sql语句了.但是这些语句有一个比较麻烦的地方是:它们一般后面都会有wher ...
- 手把手教你写一个java的orm(一)
写之前的说明 其实吧. 这个东西已经写好了,地址在:https://github.com/hjx601496320/JdbcPlus 这系列文章算是我写的过程的总结吧.(恩系列,说明我可能会写好久,╮ ...
- 让我手把手教你写一个强大、方便使用的 IOC 容器
一.介绍 1.介绍 最近无聊,也没什么事做,没事做总是要给自己找点事情做吧,毕竟人的生活在与折腾.于是,决定自己手动写一个 IOC 的框架.我们知道在 NetCore 的版本里面已经内置了 IOC 容 ...
- 手把手教你写一个RN小程序!
时间过得真快,眨眼已经快3年了! 1.我的第一个App 还记得我14年初写的第一个iOS小程序,当时是给别人写的一个单机的相册,也是我开发的第一个完整的app,虽然功能挺少,但是耐不住心中的激动啊,现 ...
- 手把手教你写一个java的orm(完)
生成sql:select 上一篇讲了怎样生成一个sql中where的一部分,之后我们要做事情就简单很多了,就只要像最开始一样的生成各种sql语句就好了,之后只要再加上我们需要的条件,一个完整的sql就 ...
- 手把手教你写一个java的orm(四)
开始准备生成sql 在上一篇里,我们已经取到了我们在生成sql语句中所需要的信息,这一篇里我们开始根据class来生成我们需要的sql.在这之前我们先确认几件事情 sql里的参数我们使用占位符的形式. ...
- 手把手教你写一个java的orm(三)
使用反射解析class 上一篇我们完成了class到表映射关系的建立,但是这个并不能被代码正确处理,我们还需要让程序能够正确的识别这些映射关系. 这一篇主要讲的是建立一个从class到表的模型,使我们 ...
- 手把手教你写一个java的orm(二)
创建映射关系 想要实现一个orm的功能,我觉得就是要将class和数据库中的表创建映射关系.把class的名称和表的名称,class属性名称和表的字段名称,属性类型与表的字段类型一一对应起来.可以 ...
随机推荐
- deepin配置反向代理映射本地到公网
这里我是用的小米球的免费ngrok 相信deepin的新用户在配置反向代理时,会感觉到一脸茫然,因为一开始我也是这样,但经过短暂的了解了deepin后,发现,其实与在Debian上配置并没有什么区别! ...
- Kali Linux来袭~老司机带你进击
Kali是BackTrackLinux完全遵循Debian开发标准彻底的完全重建.全新的目录框架,复查并打包所有工具,我们还为VCS建立了Git树. 本次推荐内容主要介绍Kali-Linux的安装,包 ...
- 负载均衡群集LB
负载均衡群集是目前企业用得最多的群集类型,通过主节点负载调度器(Director),使用特定的分流算法,将来自客户机的访问请求分担给多个服务器节点共同处理,从而缓解整个系统的负载压力,响应更多请求:群 ...
- MQ 消息队列的比较
目前业界有很多MQ产品,我们作如下对比: RabbitMQ 是使用Erlang编写的一个开源的消息队列,本身支持很多的协议:AMQP,XMPP, SMTP, STOMP,也正是如此,使的它变的非常重量 ...
- day06 --class --home
# -*- coding: utf-8 -*-# @Time : 2018/12/26 20:29# @Author : Endless-cloud# @Site : # @File : day 06 ...
- php-fpm epoll封装
参考 http://www.jianshu.com/p/dac223d7d9ad 事件对象结构 //fpm_event.h struct fpm_event_s { int fd; /* IO 文件句 ...
- Java_多线程2_线程池
线程池(pool): 线程池的作用: 1.节省资源,减少线程的数量和创建销毁线程的开销2.合理的管理线程的分配 线程池的创建: 1.newCachedThreadPool //优点:很灵活,弹性的线程 ...
- 1. C++11保证稳定性与兼容性
1.1 __func__预定义标识符 在c99中,__func__基本功能是返回所在函数的名字,c++11中允许使用在类或结构体中. #include <iostream> using n ...
- 洛谷 P2480 [SDOI2010]古代猪文 题解【欧拉定理】【CRT】【Lucas定理】
数论综合题. 题目背景 题目背景与题目无关因此省略.题目链接 题目描述 猪王国的文明源远流长,博大精深. iPig 在大肥猪学校图书馆中查阅资料,得知远古时期猪文文字总个数为 \(N\).当然,一种语 ...
- [转] spark-submit 提交任务及参数说明
[From] https://www.cnblogs.com/weiweifeng/p/8073553.html#undefined spark-submit 可以提交任务到 spark 集群执行,也 ...