用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 结果如下------------------------------------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值