用netty 重寫簡版RPC 通信

前段時間手寫了一個NIO的RPC 簡易通信,今天用netty 重寫了個RPC 通信,目前 已經可以工作,不過對於netty的有些參數還不是很清楚,文章中寫了???的地方就是不清楚的,高手知道的可以在評論中說明 一下,謝謝。

 爲了方便,本文中的client和server在同一工程中,不過都是通過netty來訪問的。

另外文章中的可用服務爲了方便也是在本地的class path下搜索並保存的。

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------

現在我記錄一下重寫RPC的思路:

1.RPC 調用是在client只知道接口和方法名的情況要,通過發送給server 由server調用後返回結果的。所以我們要創建一個服務接口。

==本文中有兩個接口:IRpcHelloService, 和IRpcService, 放在包api中。

2. 有了服務接口,那就必須要有實現類,這個在實際中一定是放到server上的。

==本文中實現類RpcHelloServiceImpl, 和RpcServiceImpl, 放在包provider中。

3. 服務器要提供服務,那就要把所有的可用服務顯露出來。

==本文中由類RpcRegistry 類中的start() 通過Port 綁定在netty上來向網絡發佈服務。

==netty中必然要用到childHandler的配置,這裏面就要用到我們自定義的handler (RegistryHandler類),  重寫channelRead()方法

4.client端要請求服務,我們定義一個consumer類.在這個類中因爲client只知道 接口名,我們需要爲client端創建動態代理對象,通過代理對象來執行對應的方法.代理對象中invoke方法通過netty 向server端發送自定義的協議. 裏邊也會有自己定義的handler, 裏面也要重寫channelRead() 來處理收到的報文.

5. 在上一步驟中會有到自定義的協議,所以我們也要定義一個InvokerProtocol, 裏面肯定會接口名,方法名,方法參數.

服務器上會根據收到的接口名,找到可用的服務實現類的實例.

----------------------------------------------------------以下是代碼結構------------------------------------------------------------------------------------------

pom.xml和所有的代碼就在這裏,要練習的同學可以按以下結構構成工程,直接運行即可.

-------------

----------------------------------------------------------以下是pom.xml-------------------------------------------------------------------------------------------------------

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.netty.rpc</groupId>
    <artifactId>RPC-NETTY</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <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>


    </dependencies>

</project>

接口1:

package com.yubo.study.netty.rpc.api;

/**
 * Created by Administrator on 2019/7/15 0015.
 */
public interface IRpcHelloService {
    public  String hello(String name);
}

接口2:

package com.yubo.study.netty.rpc.api;

/**
 * Created by Administrator on 2019/7/15 0015.
 */
public interface IRpcService {
    public int add(int a, int b);
    public int sub(int a, int b);
}

接口實現:

package com.yubo.study.netty.rpc.provider;

import com.yubo.study.netty.rpc.api.IRpcHelloService;

/**
 * Created by Administrator on 2019/7/15 0015.
 */
public class RpcHelloServiceImpl implements IRpcHelloService {
    public String hello(String name) {
        return "hello: " + name;
    }
}


接口實現:
package com.yubo.study.netty.rpc.provider;

import com.yubo.study.netty.rpc.api.IRpcService;

/**
 * Created by Administrator on 2019/7/15 0015.
 */
public class RpcServiceImpl implements IRpcService {
    public int add(int a, int b) {
        return a + b;
    }

    public int sub(int a, int b) {
        return a -b;
    }
}

 


RPC 自定義協議:
package com.yubo.study.netty.rpc.protocol;


import lombok.Data;

import java.io.Serializable;
import java.util.Arrays;

/**
 * Created by Administrator on 2019/7/15 0015.
 */
@Data
public class InvokerProtocol implements Serializable {
    private   String className;
    private  String methodName;
    private  Class<?> [] paraTypes;
    private  Object [] values;

    @Override
    public String toString() {
        return "InvokerProtocol{" +
                "className='" + className + '\'' +
                ", methodName='" + methodName + '\'' +
                ", paraTypes=" + Arrays.toString(paraTypes) +
                ", values=" + Arrays.toString(values) +
                '}';
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getMethodName() {
        return methodName;
    }

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

    public Class<?>[] getParaTypes() {
        return paraTypes;
    }

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

    public Object[] getValues() {
        return values;
    }

    public void setValues(Object[] values) {
        this.values = values;
    }
}

 


服務器端發麪程序:
package com.yubo.study.netty.rpc.registry;

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.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;


/**
 * Created by Administrator on 2019/7/15 0015.
 */
public class RpcRegistry {
    private int port;
    public RpcRegistry(int port) {
        this.port = port;
    }

    public void start()
    {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();
        ChannelFuture future = null;
        try{
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {// 注意這是childHandler, client用的是handler, 都要重寫initChannel方法
                        @Override
                        protected void initChannel(SocketChannel sc) throws Exception { // 這裏是寫了一個ChannelInitializer 類的匿名子類,實現了initChannel 方法,當有連接過來時,纔會調用這個函數
                            System.out.println("sc:" + sc);
                            ChannelPipeline cp = sc.pipeline();
                            System.out.println("111111:");
                            cp.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0, 4, 0, 4));// ???
                            System.out.println("222:");
                            cp.addLast(new LengthFieldPrepender(4)); // ???
                            System.out.println("333");
                            cp.addLast("encoder", new ObjectEncoder()); // ???
                            System.out.println("44444:");
                            cp.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));// ???
                            System.out.println("555555");
                            // 這個纔是最重要的, 我們處理從channel 中讀取出來的數據, 有連接過來時,就會new RegistryHandler,
                            // 這個對象中會從本地搜索所有可用的服務並存到map中。k: interface , v : 實現類的實例
                            // 當server中的netty 的channelRead()函數中收到自定義協議對象後,就會用msg中的interface Name找到 服務器上對應的實現類的實例,然後用這個實例通過反射調用對應的方法,並把結果寫到client的ctx中。
                            cp.addLast(new RegistryHandler());
                            System.out.println("6666666");
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128) // 來不及處理的連接放在緩存中的數目最大數
                    .childOption(ChannelOption.SO_KEEPALIVE, true); // 保持連接
            future = b.bind(port).sync();
            System.out.println("GP registry start listen on port:" + port);
            future.channel().closeFuture().sync();// ???
        }catch (Exception ex){ // 異常了就關閉所有的工作組
            ex.printStackTrace();
            if(null != future){
                try {
                    future.channel().closeFuture().sync();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        new RpcRegistry(8083).start(); // 發佈註冊
    }
}

服務器端用的handler (netty中pipline中加入的)
package com.yubo.study.netty.rpc.registry;

import com.yubo.study.netty.rpc.protocol.InvokerProtocol;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by Administrator on 2019/7/15 0015.
 */
public class RegistryHandler extends ChannelInboundHandlerAdapter { // handler 都是繼承這個類ChannelInboundHandlerAdapter, 並重寫channelRead 和exceptionCaught()兩個方法
    public static ConcurrentHashMap<String, Object> registryMap = new ConcurrentHashMap<String, Object>();
    private List<String> classNames = new ArrayList<String>();

    public RegistryHandler() { // RegistryHandler是用來處理client發來的請求的,所以他必須要知道哪些服務可用,所在在構造函數裏,這把所有要用的服務初始化好。
        doScan("com.yubo.study.netty.rpc.provider"); // 這裏爲了方便,是直接從本地的class 路徑下搜索可用的服務,實際使用中可能是從另外的地方獲取
        doRegistry();
    }

    public void doScan(String packagePath) {
        URL url = this.getClass().getClassLoader().getResource(packagePath.replaceAll("\\.", "/")); // 通過把 xx.xx.xx 替換成/xx/xx/xx/xx
        System.out.println("url:" + url);
        File dir = new File(url.getFile());// /xx/xx/xx 變成 e:\xx\xx\xx
        System.out.println("dir:" + dir);
        for (File f : dir.listFiles()) {
            if (f.isDirectory()) {
                doScan(packagePath + "." + f.getName()); // 如果是目錄 ,就遞歸調用
            } else {
                classNames.add(packagePath + "." + f.getName().replace(".class", "").trim()); // 如果是.class文件,就把class的全名放到list中
            }
        }
    }

    public void doRegistry()  {
        if (classNames.size() == 0)
            return;

        try {
            for (String className : classNames) { // 把上邊可用的所有服務(服務的實現類),全部映射成 k: 未實現的接口,v: 實現類的實例 的map 存起來
                System.out.println("className:" + className);
                Class<?> clazz = Class.forName(className);
                Class<?> i = clazz.getInterfaces()[0];
                registryMap.put(i.getName(), clazz.newInstance());
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    @Override
    public void channelRead (ChannelHandlerContext ctx, Object msg)throws Exception { // channel.pipeline.addLast(handler中), 加了這個handler, 那就會自動調用 channelRead()方法,這裏就是最核心的調用地方
        //super.channelRead(ctx, msg);
        Object result = new Object();
        InvokerProtocol request = (InvokerProtocol) msg;
        System.out.println("request:" + request);
        if(registryMap.containsKey(request.getClassName())) // 從收到的msg中提取出接口名,方法名,方法形參,方法實參,然後找到對應的實現類的實例,用反射調用方法
        {
            Object clazzInstance = registryMap.get(request.getClassName());
            Method method = clazzInstance.getClass().getMethod(request.getMethodName(), request.getParaTypes());
            result = method.invoke(clazzInstance, request.getValues());
            System.out.println("result:" + result);
            ctx.writeAndFlush(result); // 將反射執行後的結果寫回給client
            ctx.flush();
            ctx.close();
        }
    }

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

}

 


client端的調用 程序
package com.yubo.study.netty.rpc.consumer;

import com.yubo.study.netty.rpc.api.IRpcHelloService;
import com.yubo.study.netty.rpc.api.IRpcService;
import com.yubo.study.netty.rpc.consumer.proxy.RpcProxy;

/**
 * Created by Administrator on 2019/7/16 0016.
 */
public class RpcConsumer {
    public static void main(String[] args) {
        // client 要調用遠程的服務,他本身只知道接口名,所以他本身如何讓server知道 自己的需要呢
        // 就要把接口名和方法名傳給server。
        // 但是在本地呢,他還是調用原來的方法名。
        // 還有就是在本地只有接口,不能實例化,那我們就只能給他創建一個代理對象
        // 根據動態代理的原理,我們先要創建一個代理類,實現InvocationHandler, 並重寫invoke方法
        // 在invoke方法裏,把我們自定義的協議對象發給server, 然後再讀取server的返回,然後再把這個返回值返回給本地調用的方法
        IRpcHelloService rpcHello = RpcProxy.createProxy(IRpcHelloService.class);
        System.out.println("out:" + rpcHello.hello("yubo"));

        IRpcService service = RpcProxy.createProxy(IRpcService.class);
        int r = service.add(1,1);
        System.out.println("result 1 + 1 =" + r);
    }
}



----------------------------------------------------------以下是本在代理類的代碼-------------------------------------------------------------------------------------------------------
package com.yubo.study.netty.rpc.consumer.proxy;

import com.yubo.study.netty.rpc.protocol.InvokerProtocol;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * Created by Administrator on 2019/7/15 0015.
 */
public class RpcProxy {
    public  static <T> T createProxy(Class<T> clazz){
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new MethodProxy(clazz));
    }

    private 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{
                // invoke rpc server to execute method
                return rpcInvoke(proxy, method, args);
            }
        }

        public Object rpcInvoke(Object proxy, Method method, Object[] args){ // 遠程調用 部分用netty來實現
            // Package the msg and send to server
            InvokerProtocol myMsg = new InvokerProtocol(); // 構造自定義協議報文
            myMsg.setClassName(this.clazz.getName());
            myMsg.setMethodName(method.getName());
            myMsg.setParaTypes(method.getParameterTypes());
            myMsg.setValues(args);

            final RpcProxyHandler consumerHandler = new RpcProxyHandler();// netty 中不可少的handler, 繼承自ChannelInboundHandlerAdaptor, 重寫channelRead() exceptionCaught()
            EventLoopGroup workGroup = new NioEventLoopGroup(); // 創建工作組
            try{
                Bootstrap b = new Bootstrap(); // bootStrap 不可少,server端用的是ServerBootStrap
                b.group(workGroup)
                        .channel(NioSocketChannel.class) // server用的是NioServerSocketChannel
                        .option(ChannelOption.TCP_NODELAY, true)
                        .handler(new ChannelInitializer<SocketChannel>() { // handler 設置不可少, pipeline 加handler. server用的是childHandler()
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                ChannelPipeline pipeline = ch.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",  consumerHandler); // 最後添加我們自己的handler
                            }
                        });

                try {
                    ChannelFuture future = b.connect("localhost", 8083).sync(); //連接
                    System.out.println("write:" + myMsg);
                    future.channel().writeAndFlush(myMsg);// 寫報文
                    future.channel().closeFuture().sync();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
            catch (Exception ex){
                ex.printStackTrace();
            }finally {
                workGroup.shutdownGracefully();
            }

            return consumerHandler.getResponse(); // 把server 返回的結果,返回給client調用的地方
        }
    }
}



client中netty 用的handler

package com.yubo.study.netty.rpc.consumer.proxy;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * Created by Administrator on 2019/7/16 0016.
 */
public class RpcProxyHandler extends ChannelInboundHandlerAdapter {
    private Object response;

    public Object getResponse() {
        return response;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        this.response = msg;// 服務器寫回來的東西,直接保存起來
//        super.channelRead(ctx, msg);
    }

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

 ----------------------------------先執行server 端, 結果如下--------------------------------------------------

 

---------------------------------------再執行client 結果如下------------------------------------

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章