使用netty实现一个简易的rpc服务器

510 阅读3分钟

前言

在看完dubbo和netty的源码以后,决定实践一下,利用netty来实现一个简易的RPC服务器。

正文

/**
 * 资源提供者
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Provider {
}

标记这个注解的类表示可以被扫描

@Data
public class InvokeProtocol implements Serializable {
    private static final long serialVersionUID = 109558080020766645L;
    private String className;
    private String methodName;
    private Class<?>[] parameTypes;
    private Object[] values;
}

用来表示目标类的实体类,使用了lombok来简化实体类的代码数量

public class RegisterMap {
    private static final ConcurrentHashMap<String ,Object> registerMap = new ConcurrentHashMap<>();
    private static final List<String> CLASS_LISTS = new ArrayList<>();

创建一个类RegisterMap,来存放被扫描的类的map

private static void scannerClass(String packageName) {

        URL url = RegisterMap.class.getClassLoader().getResource(
                packageName.replaceAll("\\.","/"));
        if (url != null) {
            File dir = new File(url.getFile());
            for (File file : Objects.requireNonNull(dir.listFiles())) {
                if (file.isDirectory()) {
                    scannerClass(packageName+"."+file.getName());
                }else {
                    CLASS_LISTS.add(packageName+"."+
                            file.getName().replace(".class","").trim());
                }
            }
        }
    }

首先是根据包名来扫描类,如果是一个目录就继续递归,如果是一个类将.class的后缀给去掉然后加入list

private static void doRegister() {
        for (String className : CLASS_LISTS) {
            try {
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(Provider.class)) {
                    Class<?> i = clazz.getInterfaces()[0];
                    registerMap.put(i.getName(),clazz.newInstance());
                }
            } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
                e.printStackTrace();
            }
        }
    }

遍历扫描的类list,获取Provider注解并且获取接口信息放入map

public static Object getClass(String clazzName) {
        return registerMap.get(clazzName);
    }

    public static boolean containsClass(String clazzName) {
        return registerMap.containsKey(clazzName);
    }

暴露两个方法,根据接口名称获取和判断是否存在

public class RegisterHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Object result = new Object();

        InvokeProtocol request = (InvokeProtocol) msg;
        if (RegisterMap.containsClass(request.getClassName())) {
            Object clazz = RegisterMap.getClass(request.getClassName());
            Method method = clazz.getClass().getMethod(request.getMethodName(),
                    request.getParameTypes());
            result = method.invoke(clazz, request.getValues());
        }
        ctx.write(result);
        ctx.flush();
        ctx.close();
    }
}

RegisterHandler重写channelRead,接受一个InvokeProtocol类型的msg,RegisterMap获取实现类信息,然后根据java底层方法,根据方法名和参数类型获取方法后invoke进行返回

 public static void main(String[] args) {
        new Registry(8081).start();
    }
    private int port;

    public Registry(int port) {
        this.port = port;
    }

    public void start() {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap server = new ServerBootstrap();
            server.group(bossGroup, workGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            pipeline.addLast(
                                    new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                            pipeline.addLast(new LengthFieldPrepender(4));
                            pipeline.addLast("encoder", new ObjectEncoder());
                            pipeline.addLast("decoder",
                                    new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
                            pipeline.addLast(new RegisterHandler());
                        }
                    }).option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);
            ChannelFuture future = server.bind(port).sync();
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }

这一段就是标准的netty使用案例,最主要的是initChannel里面添加的handler,这里添加了帧解码器和对象解码器,并且在最后将RegisterHandler给添加进去了,当一个请求进来会将对象给解码交给channelRead

最后一步就是使用代理来将接口进行包装

 private static class MethodProxy implements InvocationHandler{

        private final Class<?> clazz;

        public MethodProxy(Class<?> clazz) {
            this.clazz = clazz;
        }


        @Override
        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);
            }
        }

注意invoke方法,这里主要是进入rpcInvoke才能实现核心的rpc功能

  public Object rpcInvoke(Object proxy, Method method, Object[] args) {
	    //将调用的方法以及参数信息创建
            InvokeProtocol msg = new InvokeProtocol();
            msg.setClassName(this.clazz.getName());
            msg.setMethodName(method.getName());
            msg.setValues(args);
            msg.setParameTypes(method.getParameterTypes());

            final ProxyHandler handler =new ProxyHandler();
            EventLoopGroup group = new NioEventLoopGroup();
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.group(group)
                        .channel(NioSocketChannel.class)
                        .option(ChannelOption.TCP_NODELAY, true)
                        .handler(new ChannelInitializer<SocketChannel>() {

                            @Override
                            protected void initChannel(SocketChannel socketChannel) throws Exception {
                                ChannelPipeline pipeline = socketChannel.pipeline();
                                pipeline.addLast("frameDecoder",
                                        new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                                pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
                                pipeline.addLast("encoder", new ObjectEncoder());
                                pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
                                pipeline.addLast("handler", handler);
                            }
                        });
                ChannelFuture future = bootstrap.connect(hosts,port).sync();
                future.channel().writeAndFlush(msg).sync();
                future.channel().closeFuture().sync();
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                group.shutdownGracefully();
            }
            return handler.getResponse();
        }

这里是用于netty的客户端,将信息包装成InvokeProtocol,发送给服务器并且接受服务器返回的内容,ProxyHandler就是用于接受服务器返回的内容,实现很简单

@Getter
public class ProxyHandler extends ChannelInboundHandlerAdapter {
    private Object response;
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        this.response = msg;
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("发生异常");
    }
}
 @SuppressWarnings("unchecked")
    public static <T> T create(Class<?> clazz) {
           MethodProxy proxy = new MethodProxy(clazz);
           Class<?>[] interfaces = clazz.isInterface() ? new Class[]{clazz}:clazz.getInterfaces();
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(),interfaces,proxy);
    }

RpcProxy的静态方法,将一个接口包装成MethodProxy,MethodProxy的invoke方法会给服务器发送一次TCP请求,服务器则会将真实的inoke对象给返回

rpc功能就实现完成了,测试一下

public interface HelloService {
    String hello(String name);
}

@Provider
public class HelloServiceImpl implements HelloService {
    @Override
    public String hello(String name) {
        return "Hello "+name;
    }
}

建立一个测试接口和实现类

 public static void main(String[] args) {
        HelloService helloService = RpcProxy.create(HelloService.class);
        System.out.println(helloService.hello("123"));
    }