netty实现RPC小demo

本文通过一个简单的RPC Demo展示了如何使用Netty实现客户端与服务端之间的远程过程调用。利用JDK动态代理创建消费者代理对象,通过Netty进行服务调用。服务端接收到请求后解析协议并本地调用相应方法,返回结果。整个过程中,采用了字符串编码解码以及线程池管理。
摘要由CSDN通过智能技术生成

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是在韩顺平老师基础上改的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值