为什么要生撸?
这个问题就好比
我自己已经有老婆了
为什么还要自己做饭 ?
好像这个例子 举的不是很恰当
主要是 为了 理解下 RPC 的一个具体编程模型
和他实现的一些细节
其实就是一个编程模型的 理解 和 实践 过程
基于netty框架
我们之前 学过netty框架的一个 编程模型
server client
基于事件驱动的模式。
上一个例子我们 把 数据传递到服务器
然后 服务器给我们返回数据
中间通过 netty的网络连接 实现打通
那么我们就会想 是否可以 把 传递过去的数据
变成一个抽象,
然后 服务器端的 数据获取 及处理 编程 具体的 实现类
客户端的 模拟调用
变成面向接口 ?
简单画了个图片给大家理解下
说白了就是 客户端 现在 要利用 service 接口
动态生成代理对象
而动态代理的实现细节中加入 和 网络交互
把 具体的代码实现放到了远程服务器。
远程服务器把结果通过网络返回给客户端
客户端再交由代理对象返回
于是我们感知到的就是
真的返回
看起来很你牛逼的样子
不错确实很牛逼
代码给我看看
Service.java
package cn.bywind.rpc.v_one;
public interface Service {
String sayHelloWithName(String name);
}
这个就是一个服务接口咯
没啥可说的
他接收一个参数
然后返回一个字符串。
ServiceProvider.java
package cn.bywind.rpc.v_one;
public class ServiceProvider implements Service {
@Override
public String sayHelloWithName(String name) {
return "hello "+name;
}
}
这个是 service 的具体实现
没啥可说的
就是拿到了 入参 然后 稍微处理下
加了个hello 然后返回了
我们这里只是模拟下,不要以为我不会写代码啊
ServiceServer.java
package cn.bywind.rpc.v_one;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
public class ServiceServer {
private int port = 0;
public ServiceServer(int port) {
this.port = port;
}
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
public void run(){
System.out.println("running on port :"+port);
try {
bootstrap.group(boss,worker);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new StringDecoder());
p.addLast(new StringEncoder());
p.addLast(new ServiceProviderHandler());
}
});
ChannelFuture channelFuture = bootstrap.bind("127.0.0.1", port).sync();
channelFuture.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
worker.shutdownGracefully();
boss.shutdownGracefully();
}
}
public static void main(String[] args) {
ServiceServer serviceServer = new ServiceServer(9999);
serviceServer.run();
}
}
这个就是一个编程模型
netty作为网络服务器
这个类就是一个 server端的代码
万年不变的哦
都是这么写的
大家可以在网上找到很多类似代码
不过我这个是 参照 netty官网 规范写的啊
大家可以借鉴下
server端 我们定有两个 NioEventLoopGroup
一个是boss 一个是 worker
boss 接收请求
worker 处理请求
别的没啥可说的了 照着写
ServiceProviderHandler.java
package cn.bywind.rpc.v_one;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
public class ServiceProviderHandler extends ChannelHandlerAdapter {
private static final Service SERVICE = new ServiceProvider();
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String s = msg.toString();
System.out.println("get str from client:"+s);
ctx.writeAndFlush(SERVICE.sayHelloWithName(s));
}
}
这个类正式 如他所说的 handler 或者是 adapter
其实 netty也看到了这个 地方的不妥
所以他们现在都叫 adapter了
你可以去看最新的 netty代码 之前 这部分 是 handler
现在都叫 adapter
了
这个地方就是 让 具体实现 接收网络传输数据
然后 自己 逻辑处理下
然后 通过网络传出 结果数据
这个地方 我们称为 adapter
ServiceClient.java
package cn.bywind.rpc.v_one;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ServiceClient {
private ServiceConsumerHandler handler ;
private static ExecutorService executor = Executors
.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public Object createProxy(final Class<?> serviceClass){
Object o = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{serviceClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(handler == null){
startClient();
}
handler.setParams(args[0].toString());
return executor.submit(handler).get();
}
});
return o;
}
public void startClient(){
handler = new ServiceConsumerHandler();
try {
NioEventLoopGroup worker = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(worker);
bootstrap.channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY,true);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new StringDecoder());
p.addLast(new StringEncoder());
p.addLast(handler);
}
});
bootstrap.connect("127.0.0.1", 9999).sync();
}catch (Exception e){
e.printStackTrace();
}
}
}
这个类就是一个netty的客户端
给他一个 server
的IP 端口 让他建立连接
连接以后 他就需要 动态代理了
在动态代理的过程中
我们调用 网络通信
然后得到 server
给我们的结果
然后返回给 代理对象
ServiceConsumerHandler.java
package cn.bywind.rpc.v_one;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import java.util.concurrent.Callable;
public class ServiceConsumerHandler extends ChannelHandlerAdapter implements Callable{
private ChannelHandlerContext context;
private String result;
private String params;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
context = ctx;
}
@Override
public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
result = msg.toString();
notify();
}
@Override
public synchronized Object call() throws Exception {
context.writeAndFlush(params);
wait();
return result;
}
public String getParams() {
return params;
}
public void setParams(String params) {
this.params = params;
}
}
跟上面的那个 ServiceProviderHandler
类似
都是处理 netty 网络数据
他主要实现两个功能
1. 拿到入参 向netty中写数据
2. 拿到服务端返回值 返回给程序
我们让这个类实现 callable 接口
可以被线程池调用
因为我们会模拟多个客户端的情形
我们需要我们的 数据获取 和 数据写入
是线程安全的
这块的代码大家可以 详细看下
ServiceConsumer.java
package cn.bywind.rpc.v_one;
public class ServiceConsumer {
public static void main(String[] args) throws Exception{
ServiceClient client = new ServiceClient();
Service proxy = (Service) client.createProxy(Service.class);
int i = 0;
for (;;){
Thread.sleep(1000);
String result = proxy.sayHelloWithName("bywind"+i);
System.out.println("from rpc server:"+result);
i++;
}
}
}
这个类就是一个客户端吧
具体的也没啥可说的
运行我们的程序
首先我们运行我们的服务器端代码
接下来我们运行我们的客户端代码
多线程的哦, 并且我们启动两个客户端
我们正好看下 服务器是否会返回错乱,或者活 客户端是否会错乱
准确的说是 客户端是否会错乱
因为我们的客户端 实际处理代码是 一个线程模型的
我们加了 线程安全的模型哦
好的现在我们看下效果
获取服务端的数据完全没有问题
紧接着我么启动第二个客户端
我们发现他的序号也是从 0 开始
这就实现了不同线程中间互补干扰
以上就是我们 1.0 版本的 一个 远程RPC调用
仿佛不是很过瘾
这种只能给一个 service
服务了
我们需要的是程序的通用性
可以适配更多
并且在传递 参数 和返回参数的时候
我们不想只有 String
了
我们需要 Object
这样 就更完美了
这也就是 框架的意义所在的
从1.0 升级到 2.0
还是先贴代码吧
GoodByeService.java
package cn.bywind.rpc.v_two;
public interface GoodByeService {
String sayGoodbye(Person person);
}
GoodByeServiceImpl.java
package cn.bywind.rpc.v_two;
public class GoodByeServiceImpl implements GoodByeService {
@Override
public String sayGoodbye(Person person) {
return "GoodBye :"+person;
}
}
HelloService.java
package cn.bywind.rpc.v_two;
public interface HelloService {
String sayHelloWithName(String name);
}
HelloServiceImpl.java
package cn.bywind.rpc.v_two;
public class HelloServiceImpl implements HelloService {
@Override
public String sayHelloWithName(String name) {
return "hello "+name;
}
}
Person.java
package cn.bywind.rpc.v_two;
public class Person {
private String name;
private Integer age;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
RPCDecoder.java
package cn.bywind.rpc.v_two;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
public class RPCDecoder extends ByteToMessageDecoder {
private Class<?> genericClass;
public RPCDecoder(Class<?> genericClass) {
this.genericClass = genericClass;
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
final int length = in.readableBytes();
final byte[] bytes = new byte[length];
in.readBytes(bytes, 0, length);
Object obj = SerializationUtil.deserialize(bytes, genericClass);
out.add(obj);
}
}
RPCEncoder.java
package cn.bywind.rpc.v_two;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class RPCEncoder extends MessageToByteEncoder {
private Class<?> genericClass;
public RPCEncoder(Class<?> genericClass) {
this.genericClass = genericClass;
}
@Override
public void encode(ChannelHandlerContext ctx, Object in, ByteBuf out) throws Exception {
if (genericClass.isInstance(in)) {
byte[] data = SerializationUtil.serialize(in);
out.writeBytes(data);
}
}
}
RpcRequest.java
package cn.bywind.rpc.v_two;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Arrays;
public class RpcRequest implements Serializable {
private String className;
private String methodName;
private Class<?>[] parameterTypes;
private Object [] args;
private String requestId;
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Class<?>[] getParameterTypes() {
return parameterTypes;
}
public void setParameterTypes(Class<?>[] parameterTypes) {
this.parameterTypes = parameterTypes;
}
public Object[] getArgs() {
return args;
}
public void setArgs(Object[] args) {
this.args = args;
}
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
@Override
public String toString() {
return "RpcRequest{" +
"className='" + className + '\'' +
", methodName='" + methodName + '\'' +
", parameterTypes=" + Arrays.toString(parameterTypes) +
", args=" + Arrays.toString(args) +
", requestId='" + requestId + '\'' +
'}';
}
}
RpcResponse.java
package cn.bywind.rpc.v_two;
import java.io.Serializable;
public class RpcResponse implements Serializable {
private String requestId;
private Object result;
public RpcResponse(){
}
public RpcResponse(String requestId, Object result) {
this.requestId = requestId;
this.result = result;
}
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
}
SerializationUtil.java
package cn.bywind.rpc.v_two;
import io.protostuff.LinkedBuffer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.Schema;
import io.protostuff.runtime.RuntimeSchema;
import org.objenesis.Objenesis;
import org.objenesis.ObjenesisStd;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class SerializationUtil {
private static Map<Class<?>, Schema<?>> cachedSchema = new ConcurrentHashMap<Class<?>, Schema<?>>();
private static Objenesis objenesis = new ObjenesisStd(true);
private SerializationUtil() {
}
@SuppressWarnings("unchecked")
private static <T> Schema<T> getSchema(Class<T> cls) {
Schema<T> schema = (Schema<T>) cachedSchema.get(cls);
if (schema == null) {
schema = RuntimeSchema.createFrom(cls);
if (schema != null) {
cachedSchema.put(cls, schema);
}
}
return schema;
}
@SuppressWarnings("unchecked")
public static <T> byte[] serialize(T obj) {
Class<T> cls = (Class<T>) obj.getClass();
LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
try {
Schema<T> schema = getSchema(cls);
return ProtostuffIOUtil.toByteArray(obj, schema, buffer);
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
} finally {
buffer.clear();
}
}
public static <T> T deserialize(byte[] data, Class<T> cls) {
try {
T message = (T) objenesis.newInstance(cls);
Schema<T> schema = getSchema(cls);
ProtostuffIOUtil.mergeFrom(data, message, schema);
return message;
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
}
ServiceClient.java
package cn.bywind.rpc.v_two;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ServiceClient {
private ServiceConsumerHandler handler ;
private static ExecutorService executor = Executors
.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public Object createProxy(final Class<?> serviceClass){
Object o = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{serviceClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(handler == null){
startClient();
}
RpcRequest request = new RpcRequest(); // 创建并初始化 RPC 请求
request.setRequestId(UUID.randomUUID().toString());
request.setClassName(method.getDeclaringClass().getName());
request.setMethodName(method.getName());
request.setParameterTypes(method.getParameterTypes());
request.setArgs(args);
handler.setParams(request);
return executor.submit(handler).get();
}
});
return o;
}
public void startClient(){
handler = new ServiceConsumerHandler();
try {
NioEventLoopGroup worker = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(worker);
bootstrap.channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY,true);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new RPCEncoder(RpcRequest.class));
p.addLast(new RPCDecoder(RpcResponse.class));
p.addLast(handler);
}
});
bootstrap.connect("127.0.0.1", 9999).sync();
}catch (Exception e){
e.printStackTrace();
}
}
}
ServiceConsumer.java
package cn.bywind.rpc.v_two;
public class ServiceConsumer {
public static void main(String[] args) throws Exception{
ServiceClient client = new ServiceClient();
HelloService helloService = (HelloService) client.createProxy(HelloService.class);
String bywind = helloService.sayHelloWithName("bywind");
System.out.println("helloService :"+bywind);
GoodByeService goodByeService = (GoodByeService) client.createProxy(GoodByeService.class);
String bywind1 = goodByeService.sayGoodbye(new Person("bywind", 28));
System.out.println("goodByeService : "+bywind1);
}
}
ServiceConsumerHandler.java
package cn.bywind.rpc.v_two;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import java.util.concurrent.Callable;
public class ServiceConsumerHandler extends ChannelHandlerAdapter implements Callable{
private ChannelHandlerContext context;
private RpcResponse rpcResponse;
private Object result;
private RpcRequest params;
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
context = ctx;
}
@Override
public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
rpcResponse = (RpcResponse) msg;
result = rpcResponse.getResult();
notify();
}
@Override
public synchronized Object call() throws Exception {
context.writeAndFlush(params);
wait();
return result;
}
public RpcRequest getParams() {
return params;
}
public void setParams(RpcRequest params) {
this.params = params;
}
}
ServiceProviderHandler.java
package cn.bywind.rpc.v_two;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import net.sf.cglib.reflect.FastClass;
import net.sf.cglib.reflect.FastMethod;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
public class ServiceProviderHandler extends ChannelHandlerAdapter {
private static final HashMap<String,Object> SERVICE = new HashMap<String, Object>();
static {
SERVICE.put(GoodByeService.class.getName(),new GoodByeServiceImpl());
SERVICE.put(HelloService.class.getName(),new HelloServiceImpl());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("get obj from client:"+msg);
RpcRequest request = (RpcRequest) msg;
Object result = handle(request);
RpcResponse response = new RpcResponse();
response.setRequestId(request.getRequestId());
response.setResult(result);
ctx.writeAndFlush(response);
}
public Object handle(RpcRequest request){
String className = request.getClassName();
Object object = SERVICE.get(className);
Class<?>[] parameterTypes = request.getParameterTypes();
String methodName = request.getMethodName();
Object[] args = request.getArgs();
Class<?> targetClass = object.getClass();
FastClass fastClass = FastClass.create(targetClass);
FastMethod method = fastClass.getMethod(methodName, parameterTypes);
Object invoke = null;
try {
invoke = method.invoke(object, args);
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return invoke;
}
}
ServiceServer.java
package cn.bywind.rpc.v_two;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.serialization.ClassResolver;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
public class ServiceServer {
private int port = 0;
public ServiceServer(int port) {
this.port = port;
}
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
public void run(){
System.out.println("running on port :"+port);
try {
bootstrap.group(boss,worker);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(new RPCDecoder(RpcRequest.class));
p.addLast(new RPCEncoder(RpcResponse.class));
p.addLast(new ServiceProviderHandler());
}
});
ChannelFuture channelFuture = bootstrap.bind("127.0.0.1", port).sync();
channelFuture.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
worker.shutdownGracefully();
boss.shutdownGracefully();
}
}
public static void main(String[] args) {
ServiceServer serviceServer = new ServiceServer(9999);
serviceServer.run();
}
}
我们来按下第二版的 运行效果吧
还是一样的 运行 服务端
然后
运行我们的 client
我们看下结果
客户端结果 输出
服务端接收参数 输出:
大家如果想要自己实际运行下代码的化
可以去看我的github
https://github.com/ibywind/dubbo-learn
希望大家可以先看一遍 代码 然后把 动作记下来
自己去实际 生撸 一个 RPC框架
我学到了什么
说句实在话
这个东西网上例子很多的
我之所以 想自己尝试着写下代码
主要为了 防止自己 被 频繁的 拷贝 粘贴
变成老年痴呆
首先分析 原理
这个东西 就是个 代理模式
另外 熟悉了 netty的编程模型
其实做起来还是很简单的.
这个代码我上传到 github
https://github.com/ibywind/dubbo-learn
大家可以下载下来继续学习和指正哦