前言
课程目标:
- 深刻理解RPC框架设计的基本原理
- 基于Netty手写mini版本RPC框架
一、Dubbo四大核心板块
二、基于Netty手写RPC框架
2.1 maven依赖及包的创建
maven依赖:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
包的创建(总览):
正常情况下,provider、consumer、registry不会在一个项目里,但是我们简化了代码,将他们放在一个项目中。
2.2 创建通信协议类
InvokerProtocol.class
public class InvokerProtocol implements Serializable {
private String className; //类名
private String methodName; //方法名称
private Class<?>[] params; //形参列表
private Object[] values; //实参列表
}
2.3 Registry实现
2.3.1 RpcRegistry.class(Netty 服务端启动类)
public class RpcRegistry {
private int port;
public RpcRegistry(int port) {
this.port = port;
}
private void start() {
// 服务器端应用程序使用两个NioEventLoopGroup创建两个EventLoop的组,EventLoop这个相当于一个处理线程,是Netty接收请求和处理IO请求的线程。
// 主线程组, 用于接受客户端的连接,但是不做任何处理,跟老板一样,不做事
EventLoopGroup bossGroup = new NioEventLoopGroup();
// 从线程组, 当boss接受连接并注册被接受的连接到worker时,处理被接受连接的流量。
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// netty服务器启动类的创建, 辅助工具类,用于服务器通道的一系列配置
ServerBootstrap server = new ServerBootstrap();
/**
* 使用了多少线程以及如何将它们映射到创建的通道取决于EventLoopGroup实现,甚至可以通过构造函数进行配置。
* 设置循环线程组,前者用于处理客户端连接事件,后者用于处理网络IO(server使用两个参数这个)
* public ServerBootstrap group(EventLoopGroup group)
* public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup)
*/
server.group(bossGroup, workerGroup)
// 用于构造socketchannel工厂
.channel(NioServerSocketChannel.class)//指定NIO的模式
/**
* @Description: 初始化器,channel注册后,会执行里面的相应的初始化方法,传入自定义客户端Handle(服务端在这里操作)
*
@Override
protected void initChannel(SocketChannel channel) throws Exception {
// 通过SocketChannel去获得对应的管道
ChannelPipeline pipeline = channel.pipeline();
// 通过管道,添加handler
pipeline.addLast("nettyServerOutBoundHandler", new NettyServerOutBoundHandler());
pipeline.addLast("nettyServerHandler", new NettyServerHandler());
}
* 子处理器也可以通过下面的内部方法来实现。
*/
.childHandler(new ChannelInitializer() {// 子处理器,用于处理workerGroup
@Override
protected void initChannel(Channel ch) throws Exception {
//接收课客户端请求的处理流程
ChannelPipeline pipeline = ch.pipeline();
int fieldLength = 4;
//通用解码器设置
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,fieldLength,0,fieldLength));
//通用编码器
pipeline.addLast(new LengthFieldPrepender(fieldLength));
//对象编码器
pipeline.addLast("encoder",new ObjectEncoder());
//对象解码器
pipeline.addLast("decoder",new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
pipeline.addLast(new RegistryHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 启动server,绑定端口,开始接收进来的连接,设置8088为启动的端口号,同时启动方式为同步
ChannelFuture future = server.bind(this.port).sync();
System.out.println("GP RPC registry is start,listen at " + this.port);
// 监听关闭的channel,等待服务器 socket 关闭 。设置位同步方式
future.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
//退出线程组
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
new RpcRegistry(8080).start();
}
}
上述代码步骤如下:
- 初始化用于Acceptor的主"线程池"以及用于I/O工作的从"线程池";
- 初始化ServerBootstrap实例, 此实例是netty服务端应用开发的入口;
- 通过ServerBootstrap的group方法,设置(1)中初始化的主从"线程池";
- 指定通道channel的类型,由于是服务端,故而是NioServerSocketChannel;
- 设置ServerSocketChannel的处理器
- 设置子通道也就是SocketChannel的处理器, 其内部是实际业务开发的"主战场"
- 配置子通道也就是SocketChannel的选项
- 绑定并侦听某个端口
netty编程的精华就在上面的代码中,代码注释已经给出,以后慢慢研究。
2.3.2 RegistryHandler.class(Netty 服务端处理类Handler)
public class RegistryHandler extends ChannelInboundHandlerAdapter {
//注册中心容器
private static ConcurrentHashMap<String,Object> registryMap = new ConcurrentHashMap<String, Object>();
private List<String> classNames = new ArrayList<String>();
public RegistryHandler(){
//扫描所有需要注册的类
//com.gupaoedu.vip.netty.rpc.provider
scannerClass("com.gupaoedu.vip.netty.rpc.provider");
//将扫描到的类注册到一个容器中
doRegister();
}
private void doRegister() {
if(classNames.size() == 0){return;}
for (String className : classNames) {
try {
Class<?> clazz = Class.forName(className);
Class<?> i = clazz.getInterfaces()[0];
registryMap.put(i.getName(),clazz.newInstance());
}catch (Exception e){
e.printStackTrace();
}
}
}
private void scannerClass(String packageName) {
URL url = this.getClass().getClassLoader().getResource(packageName.replaceAll("\\.","/"));
File dir = new File(url.getFile());
for (File file : dir.listFiles()) {
//如果是文件夹,继续递归
if(file.isDirectory()){
scannerClass(packageName + "." + file.getName());
}else {
classNames.add(packageName + "." + file.getName().replace(".class","").trim());
}
}
}
/**本方法用于读取客户端发送的信息*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Object result = new Object();
InvokerProtocol request = (InvokerProtocol) msg;
if(registryMap.containsKey(request.getClassName())){
//用反射直接调用Provider的方法
Object provider = registryMap.get(request.getClassName());
Method method = provider.getClass().getMethod(request.getMethodName(),request.getParams());
result = method.invoke(provider,request.getValues());
}
ctx.write(result);
ctx.flush();
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
2.4 Provider代码实现
提供者接口:
public interface IRpcHelloService {
String hello(String name);
}
提供者实现:
public String hello(String name) {
return "hello " + name + "!";
}
2.5 Consumer消费端代码实现
2.5.1 RpcProxy.class(代理类——Netty 客户端启动类)
public class RpcProxy {
public static <T> T create(Class<?> clazz){
MethodProxy proxy = new MethodProxy(clazz);
Class<?> [] interfaces = clazz.isInterface() ?
new Class[]{clazz} :
clazz.getInterfaces();
T result = (T)Proxy.newProxyInstance(clazz.getClassLoader(),interfaces,proxy);
return result;
}
public static class MethodProxy implements InvocationHandler {
private Class<?> clazz;
public MethodProxy(Class<?> clazz) {
this.clazz = clazz;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(Object.class.equals(method.getDeclaringClass())){
return method.invoke(this,args);
}else{
return rpcInvoke(proxy,method,args);
}
}
private Object rpcInvoke(Object proxy, Method method, Object[] args) {
//封装请求的内容
InvokerProtocol msg = new InvokerProtocol();
msg.setClassName(this.clazz.getName());
msg.setMethodName(method.getName());
msg.setParams(method.getParameterTypes());
msg.setValues(args);
final RpcProxyHandler consumerHandler = new RpcProxyHandler();
// 客户端启动类程序
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap client = new Bootstrap();
//EventLoop的组
client.group(group)
//用于构造socketchannel工厂
.channel(NioSocketChannel.class)
//自定义客户端Handle(客户端在这里搞事情)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(Channel ch) throws Exception {
//接收课客户端请求的处理流程
ChannelPipeline pipeline = ch.pipeline();
int fieldLength = 4;
//通用解码器设置
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,fieldLength,0,fieldLength));
//通用编码器
pipeline.addLast(new LengthFieldPrepender(fieldLength));
//对象编码器
pipeline.addLast("encoder",new ObjectEncoder());
//对象解码器
pipeline.addLast("decoder",new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
pipeline.addLast("handler",consumerHandler);
}
})
/**设置选项
* 参数:Socket的标准参数(key,value),可自行百度保持呼吸,不要断气!
* */
.option(ChannelOption.TCP_NODELAY, true);
/** 开启客户端监听,连接到远程节点,阻塞等待直到连接完成*/
ChannelFuture future = client.connect("localhost",8080).sync();
future.channel().writeAndFlush(msg).sync();
/**阻塞等待数据,直到channel关闭(客户端关闭)*/
future.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
group.shutdownGracefully();
}
return consumerHandler.getResponse();
}
}
}
2.5.2 消费这代码
public class RpcConsumer {
public static void main(String[] args) {
IRpcHelloService rpcHello = RpcProxy.create(IRpcHelloService.class);
System.out.println(rpcHello.hello("Tom"));
}
}
2.6 启动效果
启动注册中心:
启动客户端:
此结果我们这次基于Netty手写RPC框架成功!
本章参考博客:Netty学习之Demo搭建