netty 實現簡單的rpc呼叫

她的開呀發表於2020-05-23

yls 2020/5/23

netty 實現簡單rpc準備

  1. 使用netty傳輸java bean物件,可以使用protobuf,也可以通過json轉化
  2. 客戶端要將呼叫的介面名稱,方法名稱,引數列表的型別和值傳輸到服務端,
    可以用動態代理
  3. 服務端要對介面和實現類進行對映(或者自定義名稱與實現類對映),接收到客戶端的資料,使用反射呼叫相關類的函式
  4. 客戶端使用callable返回撥用的結果,先等待,有資料寫回後喚醒執行緒,賦值返回

基於netty編碼實現 rpc 呼叫

大致流程:

  1. netty搭建rpc框架;
  2. 建立服務消費者和服務提供者的公共介面和類
  3. 建立服務提供者,啟動netty框架的服務端
  4. 建立服務消費者,啟動netty框架的客戶端,然後獲取呼叫結果

1.首先用netty實現一個rpc框架

1.1 建立客戶端呼叫服務端時傳輸資訊的類
/**
 * rpc呼叫時傳輸類的資訊
 * 客戶端與服務端之間通訊,傳遞資訊的媒介
 */
public class ClassInfo {
    //自定義name,一般一個介面有多個實現類的時候使用自定義
    // 或者預設使用介面名稱
    private String name;
    private String methodName;
    //引數型別
    private Class[] types;
    //引數列表
    private Object[] params;
    //自定義rpc協議
    private String protocol="#rpc#";

    public String getProtocol() {
        return protocol;
    }

    public void setProtocol(String protocol) {
        this.protocol = protocol;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Class[] getTypes() {
        return types;
    }

    public void setTypes(Class<?>[] types) {
        this.types = types;
    }

    public Object[] getParams() {
        return params;
    }

    public void setParams(Object[] params) {
        this.params = params;
    }
}
1.2 建立解決TCP粘包拆包的編解碼器
/**
 * 編碼器
 * MyMessageEncoder  MyMessageDecoder解決粘包拆包問題
 */
public class MyMessageEncoder extends MessageToByteEncoder<String> {
    @Override
    protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {
        //先傳送內容長度
        out.writeInt(msg.getBytes().length);
        //傳送具體的內容
        out.writeBytes(msg.getBytes());
    }
}
/**
 * 解碼器
 */
public class MyMessageDecoder extends ReplayingDecoder<Void> {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        //先讀取要接收的位元組長度
        final int len = in.readInt();
        final byte[] bytes = new byte[len];
        //再根據長度讀取真正的位元組陣列
        in.readBytes(bytes);
        String s = new String(bytes);
        out.add(s);
    }
}
1.3 建立netty客戶端以及自定義的處理器
public class NettyClient {
    private static NettyClientHandler nettyClientHandler;
    static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(3, 5, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));

    public static <T> T getBean(Class<T> service) {
        String simpleName = service.getSimpleName();
        return getBean(service, simpleName);
    }

    //獲取一個動態代理物件
    public static <T> T getBean(Class<T> service, String name) {
        T o = (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[]{service}, ((proxy, method, args1) -> {
            //先建立連線
            if (nettyClientHandler == null) {
                start(ClientBootStrap.getHost()
                        , ClientBootStrap.getPort());
            }
            //組裝傳輸類的屬性值
            ClassInfo classInfo = new ClassInfo();
            classInfo.setName(name);
            classInfo.setMethodName(method.getName());
            Class<?>[] parameterTypes = method.getParameterTypes();
            classInfo.setTypes(parameterTypes);
            classInfo.setParams(args1);
            nettyClientHandler.setClassInfo(classInfo);
            //執行執行緒,傳送資料
            Future future = threadPool.submit(nettyClientHandler);
            //返回結果
            String o1 = (String) future.get();
            ObjectMapper objectMapper = new ObjectMapper();
            //獲取返回型別,並將服務端返回的json資料轉化為對應的型別
            Type returnType = method.getAnnotatedReturnType().getType();
            Object o2 = objectMapper.readValue(o1, (Class<?>) returnType);
            return o2;
        }));
        return o;
    }

    //啟動netty客戶端
    public static void start(String host, int port) {
        nettyClientHandler = new NettyClientHandler();
        //客戶端需要一個事件迴圈組就可以
        NioEventLoopGroup group = new NioEventLoopGroup(1);
        try {
            //建立客戶端的啟動物件 bootstrap ,不是 serverBootStrap
            Bootstrap bootstrap = new Bootstrap();
            //設定相關引數
            bootstrap.group(group) //設定執行緒組
                    .channel(NioSocketChannel.class) //設定客戶端通道的實現數 (反射)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline()
                                    .addLast(new MyMessageDecoder())
                                    .addLast(new MyMessageEncoder())
                                    .addLast(nettyClientHandler); //加入自己的處理器
                        }
                    });
            System.out.println("客戶端 ready is ok..");
            //連線伺服器
            final ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
            //對關閉通道進行監聽
//            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
//            group.shutdownGracefully();
        }
    }
}
/**
 * 由於需要在 handler 中傳送訊息給服務端,並且將服務端返回的訊息讀取後返回給消費者
 * 所以實現了 Callable 介面,這樣可以執行有返回值的執行緒
 */
public class NettyClientHandler extends ChannelInboundHandlerAdapter implements Callable {

    private ClassInfo classInfo; //傳遞資料的類
    private ChannelHandlerContext context;//上下文
    private Object result;//服務端返回的結果
    private Lock lock = new ReentrantLock();//使用鎖將 channelRead和 call 函式同步
    private Condition condition = lock.newCondition();//精準喚醒 call中的等待

    public void setClassInfo(ClassInfo classInfo) {
        this.classInfo = classInfo;
    }

    //通道連線時,就將上下文儲存下來,因為這樣其他函式也可以用
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        this.context = ctx;
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelInactive 被呼叫。。。");
    }

    //當服務端返回訊息時,將訊息複製到類變數中,然後喚醒正在等待結果的執行緒,返回結果
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        lock.lock();
        System.out.println(ctx.channel().hashCode());
        System.out.println("收到服務端傳送的訊息 " + msg);
        result = msg;
        //喚醒等待的執行緒
        condition.signal();
        lock.unlock();
    }

    //這裡面傳送資料到服務端,等待channelRead方法接收到返回的資料時,將資料返回給服務消費者
    @Override
    public Object call() throws Exception {
        lock.lock();
        ObjectMapper objectMapper = new ObjectMapper();
        final String s = objectMapper.writeValueAsString(classInfo);
        context.writeAndFlush(s);
        System.out.println("發出資料  " + s);
        //向服務端傳送訊息後等待channelRead中接收到訊息後喚醒
        condition.await();
        lock.unlock();
        return result;
    }

    //異常處理
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
    }
}
1.4 建立netty服務端以及自定義的處理器
public class NettyServer {
    //啟動netty服務端
    public static void start(int port) {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //建立服務端的啟動物件,並使用鏈式程式設計來設定引數
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup) //設定兩個執行緒組
                    .channel(NioServerSocketChannel.class)//使用NioServerSocketChannel 作為伺服器的通道實現
                    .option(ChannelOption.SO_BACKLOG, 128)//設定執行緒佇列的連線個數
                    .childOption(ChannelOption.SO_KEEPALIVE, true) //設定一直保持活動連線狀態
                    .childHandler(new ChannelInitializer<SocketChannel>() {//設定一個通道測試物件
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            //給pipeline設定通道處理器
                            ch.pipeline()
                                    .addLast(new MyMessageDecoder())
                                    .addLast(new MyMessageEncoder())
                                    .addLast(new NettyServerHandler());
                        }
                    });//給 workerGroup 的EventLoop對應的管道設定處理器
            //啟動伺服器,並繫結埠並且同步
            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();

            //給 channelFuture 註冊監聽器,監聽關心的事件,非同步的時候使用
//            channelFuture.addListener((future) -> {
//                if (future.isSuccess()) {
//                    System.out.println("監聽埠成功。。。");
//                } else {
//                    System.out.println("監聽埠失敗。。。");
//                }
//            });
            //對關閉通道進行監聽,監聽到通道關閉後,往下執行
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    public static Map<String, Class<?>> classNameMapping = new HashMap();

    public static void setClassNameMapping(Object object) {
        Class<?> clazz = object.getClass();
        Class<?>[] interfaces = clazz.getInterfaces();
        Class<?> anInterface = interfaces[0];
        setClassNameMapping(anInterface.getSimpleName(), object);
    }

    //為實現類定義標識,方便客戶端和服務端通訊呼叫
    public static void setClassNameMapping(String name, Object object) {
        Class<?> clazz = object.getClass();
        classNameMapping.put(name, clazz);
    }

    //接收客戶端傳入的值,將值解析為類物件,獲取其中的屬性,然後反射呼叫實現類的方法
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        String s = (String) msg;
        System.out.println("接收到資料 " + s);
        ObjectMapper objectMapper = new ObjectMapper();
        ClassInfo classInfo = objectMapper.readValue(s, ClassInfo.class);
        //確認是rpc呼叫才往下執行
        if(classInfo!=null && "#rpc#".equals(classInfo.getProtocol())){
            //反射呼叫實現類的方法
            String name = classInfo.getName();
            //獲取指定名稱的實現類
            Class<?> aClass = classNameMapping.get(name);
            Object o = aClass.newInstance();
            if (classInfo.getTypes().length > 0) {
                Method method = aClass.getMethod(classInfo.getMethodName(), classInfo.getTypes());
                method.setAccessible(true);
                Object invoke = method.invoke(o, classInfo.getParams());
                String s1 = objectMapper.writeValueAsString(invoke);
                ctx.writeAndFlush(s1);
            } else {
                Method method = aClass.getMethod(classInfo.getMethodName());
                method.setAccessible(true);
                Object invoke = method.invoke(o);
                String s1 = objectMapper.writeValueAsString(invoke);
                ctx.writeAndFlush(s1);
            }
        }
    }

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

2.建立服務消費者和服務提供者的公共介面和類

public interface HelloService {
    Result hello(String s);
    String str();
}
/**
 * 測試返回結果為java bean時使用的類
 */
public class Result {
    private int id;
    private String content;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

3.建立服務提供者

3.1 服務提供者實現公共介面
public class HelloServiceImpl implements HelloService {
    @Override
    public Result hello(String s) {
        System.out.println("收到消費者的請求。。" + s);
        Result result=new Result();
        result.setId(1);
        result.setContent("你好,我已經收到了你的消費請求");
        return result;
    }

    @Override
    public String str() {
        return "我是一個字串。。。";
    }
}
3.2 啟動netty框架的服務端
public class ServerBootStrap {
    public static void main(String[] args) {
        NettyServerHandler.setClassNameMapping(new HelloServiceImpl());
        NettyServer.start(9999);
    }
}

4.建立服務消費者,啟動netty框架的客戶端,然後獲取呼叫結果

/**
 * 消費者
 */
public class ClientBootStrap {
    private static String host = "127.0.0.1";
    private static int port = 9999;

    public static String getHost() {
        return host;
    }

    public static int getPort() {
        return port;
    }

    public static void main(String[] args) {
        //連線netty,並獲得一個代理物件
        HelloService bean = NettyClient.getBean(HelloService.class);
        //測試返回結果為java bean
        Result res = bean.hello("ffafa");
        System.out.println("res=====" + res.getContent());
        //測試返回結果為 String
        String str = bean.str();
        System.out.println("str=====" + str);
    }
}

程式碼託管地址:https://github.com/1612480331/NettyLearn/tree/master/code/src/main/java/com/yls/netty/dubborpc

相關文章