netty实现RPC小demo
1. RPC
网上有好多RPC的讲述,这里我就不多说了,简单的说说,我这里是怎么搞的?
2. netty实现方式分析
大体的思路就是上面的那个样子,至于协议的封装,我搞的没那么复杂,就是一个小demo,对于调用协议和响应协议我规定如下:
- 调用协议
调用类的全限定类名&调用的方法名&参数类型=参数值
- 响应协议
这个就很简单了,因为我返回值都是String,我直接按照Stirng就来了
对于consumer通过接口调用provider,必然是consumer中没有实现类,所以在这里我通过JDK的动态代理来创建代理对象,在调用方法的时候,去通过netty来调用provider,从而得到调用的效果。
3. netty实现demo
- 统一的接口
interface SayHello {
String sayHello(String message);
}
- provider实现SayHello接口
class DemoServer implements SayHello {
@Override
public String sayHello(String message) {
return "server say hello" + message;
}
}
- consumer
class ClientRpc<T> {
private MyClientRpcHandle myClientRpcHandle;
public static final ExecutorService threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public T getBean(Class<?> serverName, String providerName) {
return (T)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{serverName},
(InvocationHandler) (proxy, method, args) -> {
System.out.println("newProxyInstance");
if(Objects.isNull(myClientRpcHandle)){
initClient();
}
//这里为了简单就把服务方的全限定类名就写死了。
myClientRpcHandle.setParam("com.lc.DemoServer"+"&"+ method.getName() + "&"+method.getParameters()[0].getParameterizedType().getTypeName() + "=" + args[0]);
Object o = threadPool.submit(myClientRpcHandle).get();
return String.valueOf(o);
});
}
public void initClient() {
this.myClientRpcHandle = new MyClientRpcHandle();
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast(new StringEncoder())
.addLast(new StringDecoder())
.addLast(myClientRpcHandle);
}
});
//这里和之前的不一样,这里没有172行,不会让线程同步在这里。
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress(7777)).sync();
// channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
// finally {
// //注意,因为上面没有同步阻塞,所以,这里不能有这个,这个会导致在上面的代码结束之后,就关掉workGroup,导致消息发送不出去。
//要不不关掉的话,就会导致有的资源没有释放,所以这里肯定是有问题的,我的想法是把workGroup 放在属性里面,或者常量里面,最后
// 在关掉
// workGroup.shutdownGracefully();
// }
}
}
- consumer netty里面的handle
//这里实现callAble是为了用的call方法
class MyClientRpcHandle extends SimpleChannelInboundHandler<String> implements Callable {
private ChannelHandlerContext channelHandlerContext;
private volatile String result;
private String param;
//这里先保存起来,之后在使用
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
PrintUtil.printInfo("MyClientRpcHandle#channelActive");
this.channelHandlerContext = ctx;
}
@Override
protected synchronized void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
PrintUtil.printInfo("MyClientRpcHandle#channelRead0");
this.result = s;
notifyAll();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
//这是一个小知识点,wait notify 是必须和锁关联在一块的。
@Override
public synchronized Object call() throws Exception {
channelHandlerContext.writeAndFlush(param);
wait();//等待read线程唤醒
System.out.println("call wake");
return result;
}
//设置调用的方法
public void setParam(String param) {
this.param = param;
}
}
- provider
public void startServer() {
NioEventLoopGroup boosGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boosGroup, workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast(new StringDecoder())//这里就是简单的使用string的编解码了
.addLast(new StringEncoder())
.addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String byteBuf) throws Exception {
String requestBody = byteBuf;
System.out.println("服务端收到消息" + requestBody);
//这里做服务的调用
//按照标准来说的话,这里肯定必须要规定一种协议,比如,类的全限定类名,参数名字和参数值的对应关系
//在这里,我就很简单的规定了一种,我也就不写实体类了。
// 全限定类名&方法名字&参数类型=参数值
//就按照这种方式来搞了
String[] split = requestBody.split("&");
String className = split[0];
String methodName = split[1];
String argTypeAndValue = split[2];
String[] split1 = argTypeAndValue.split("=");
Class<?> aClass = Class.forName(className);
//找到要调用的方法
Method method = aClass.getMethod(methodName, Class.forName(split1[0]));
//在这里写成string是不好的,我感觉在标准的调用里面,方法的返回值肯定是经过包装的,像请求参数一样也有一个统一
//的协议来返回。我这里就只是一个简答的demo,所以,就先这样写了。
String responseResult = (String) method.invoke(aClass.newInstance(), split1[1]);
channelHandlerContext.writeAndFlush(responseResult);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
});
}
});
PrintUtil.printInfo("server is start");
ChannelFuture future = serverBootstrap.bind(7777).sync();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workGroup.shutdownGracefully();
boosGroup.shutdownGracefully();
}
}
- 测试启动consumer
public void startClient() {
ClientRpc<SayHello> clientRpc = new ClientRpc<>();
SayHello bean = clientRpc.getBean(SayHello.class, "");
System.out.println(bean.sayHello("nihao"));
}
- 完整的代码
package com.lc;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import org.junit.Test;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* rpc实现步骤
* 1. 规定一个统一的接口,提供者需要实现这个接口,消费者需要通过这个接口来调用服务者。
* 2. 基于netty实现,所以,这里肯定是长连接,
* 3. 客户端调用其实就是把调用的方法,参数传递给服务端,服务端通过本地调用这个方法,将结果返回给客户端
*/
public class NettyRpcDemo {
@Test
public void test() throws IOException {
NettyRpcDemo nettyRpcDemo = new NettyRpcDemo();
new Thread(()->{
nettyRpcDemo.startServer();
}).start();
PrintUtil.sleep(3);
new Thread(()->{
nettyRpcDemo.startClient();
}).start();
System.in.read();
}
@Test
public void testClient(){
System.out.println(DemoServer.class.getName());
}
@Test
public void test12() {
System.out.println(String.class.getName());
System.out.println(DemoServer.class.getName());
}
public void startServer() {
NioEventLoopGroup boosGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boosGroup, workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast(new StringDecoder())//这里就是简单的使用string的编解码了
.addLast(new StringEncoder())
.addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String byteBuf) throws Exception {
String requestBody = byteBuf;
System.out.println("服务端收到消息" + requestBody);
//这里做服务的调用
//按照标准来说的话,这里肯定必须要规定一种协议,比如,类的全限定类名,参数名字和参数值的对应关系
//在这里,我就很简单的规定了一种,我也就不写实体类了。
// 全限定类名&方法名字&参数类型=参数值
//就按照这种方式来搞了
String[] split = requestBody.split("&");
String className = split[0];
String methodName = split[1];
String argTypeAndValue = split[2];
String[] split1 = argTypeAndValue.split("=");
Class<?> aClass = Class.forName(className);
//找到要调用的方法
Method method = aClass.getMethod(methodName, Class.forName(split1[0]));
//在这里写成string是不好的,我感觉在标准的调用里面,方法的返回值肯定是经过包装的,像请求参数一样也有一个统一
//的协议来返回。我这里就只是一个简答的demo,所以,就先这样写了。
String responseResult = (String) method.invoke(aClass.newInstance(), split1[1]);
channelHandlerContext.writeAndFlush(responseResult);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
});
}
});
PrintUtil.printInfo("server is start");
ChannelFuture future = serverBootstrap.bind(7777).sync();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workGroup.shutdownGracefully();
boosGroup.shutdownGracefully();
}
}
public void startClient() {
ClientRpc<SayHello> clientRpc = new ClientRpc<>();
SayHello bean = clientRpc.getBean(SayHello.class, "");
System.out.println(bean.sayHello("nihao"));
}
}
class ClientRpc<T> {
private MyClientRpcHandle myClientRpcHandle;
public static final ExecutorService threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
public T getBean(Class<?> serverName, String providerName) {
return (T)Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{serverName},
(InvocationHandler) (proxy, method, args) -> {
System.out.println("newProxyInstance");
if(Objects.isNull(myClientRpcHandle)){
initClient();
}
//这里为了简单就把服务方的全限定类名就写死了。
myClientRpcHandle.setParam("com.lc.DemoServer"+"&"+ method.getName() + "&"+method.getParameters()[0].getParameterizedType().getTypeName() + "=" + args[0]);
Object o = threadPool.submit(myClientRpcHandle).get();
return String.valueOf(o);
});
}
public void initClient() {
this.myClientRpcHandle = new MyClientRpcHandle();
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast(new StringEncoder())
.addLast(new StringDecoder())
.addLast(myClientRpcHandle);
}
});
//这里和之前的不一样,这里没有172行,不会让线程同步在这里。
ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress(7777)).sync();
// channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
// finally {
// //注意,因为上面没有同步阻塞,所以,这里不能有这个,这个会导致在上面的代码结束之后,就关掉workGroup,导致消息发送不出去。
//要不不关掉的话,就会导致有的资源没有释放,所以这里肯定是有问题的,我的想法是把workGroup 放在属性里面,或者常量里面,最后
// 在关掉
// workGroup.shutdownGracefully();
// }
}
}
//这里实现callAble是为了用的call方法
class MyClientRpcHandle extends SimpleChannelInboundHandler<String> implements Callable {
private ChannelHandlerContext channelHandlerContext;
private volatile String result;
private String param;
//这里先保存起来,之后在使用
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
PrintUtil.printInfo("MyClientRpcHandle#channelActive");
this.channelHandlerContext = ctx;
}
@Override
protected synchronized void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
PrintUtil.printInfo("MyClientRpcHandle#channelRead0");
this.result = s;
notifyAll();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
//这是一个小知识点,wait notify 是必须和锁关联在一块的。
@Override
public synchronized Object call() throws Exception {
channelHandlerContext.writeAndFlush(param);
wait();//等待read线程唤醒
System.out.println("call wake");
return result;
}
//设置调用的方法
public void setParam(String param) {
this.param = param;
}
}
class DemoServer implements SayHello {
@Override
public String sayHello(String message) {
return "server say hello" + message;
}
}
interface SayHello {
String sayHello(String message);
}
4. 结果
补充说明
此demo是在韩顺平老师基础上改的。