基于netty实现自有协议的RPC


本章节我们介绍下如何使用netty实现RPC

场景引入

我们先来想象一下这样一幅场景:

现在有一个用户A想买辆车,但是A对车子不是很熟悉,于是他打电话给他的朋友B进行咨询:
A:兄弟,我想买辆车
B:想买辆什么样的车啊
A:我不是很懂车子,我说几个品牌你帮我参考下。法拉利怎么样?
B:911,跑车啊,是好车!
A:那兰博基尼呢?
B:那是男人心中的梦想!
A:那我大概知道了,那就兰博基尼好了,那我买什么颜色好呢?红色咋样?
B:红色啊,红艳艳的,不错哦
A:我看还有一辆紫色的,紫色怎么样?
B:紫色高贵的很呐,紫色好
A:好唻,就选紫色了!

RPC

什么是RPC呢?简单来讲,就是节点A远程调用节点B获取数据。结合上面的场景,用户A通过电话B获取到车子品牌、颜色等信息的过程,就叫RPC。

我们对上面的过程进行抽象,用户A对应于Client,朋友B对应于Server,电话即对应于netty。

代码

下面我们分析下场景,一点点把代码编写出来。

在上面的例子中,用户A其实提了两个需求:

  1. 通过汽车品牌的获取到汽车评价
  2. 通过具体的颜色,获取B对颜色的评价

为此,我们可以抽象出两个接口:

public interface Car {
    String getCar(String carName);
}
public interface Color {
    String getColor(String color);
}

接口的返回值,可以是车辆的描述,也可以是颜色的描述。

现在接口有了,我们需要为朋友B的回复进行编码:

public class CarImpl implements Car {
    @Override
    public String getCar(String carName) {
        if ("兰博基尼".equals(carName)) {
            return "兰博基尼, 好车,快,男人的梦想";
        }
        if ("法拉利".equals(carName)) {
            return "法拉利, 跑车,911!";
        }
        return "";
    }
}
public class ColorImpl implements Color {
    @Override
    public String getColor(String color) {

        if ("红色".equalsIgnoreCase(color)) {
            return "红色,红艳艳";
        }
        if ("紫色".equalsIgnoreCase(color)) {
            return "紫色,高贵";
        }

        return "";
    }
}

至此,我们已经将服务端的业务逻辑书写完毕了。

下面我们需要定义一下我们的数据通信协议。网络传输协议就走TCP好了,这个就暂时不管了。

下面我们来定义一下Request和Response。
注:此处为了简单,我们直接定义为一个对象了,实际的通信协议并非如此,以常用的http协议为例:
在这里插入图片描述
我们可以看到head部分会有host参数,connection参数,agent参数等,其他还有session等,这些都属于head部分,后续还有body部分,返回报文也是类似。为了简便,我们统一将其定义为Request和Response对象。

我们来看下Request:

@Data
@Builder
public class Request {
    private RequestHeader header;
    private RequestBody body;
}
@Data
@Builder
public class RequestHeader implements Serializable {
    private long flag;
    private long requestID;
    private long dataLength;
}
@Data
@Builder
public class RequestBody implements Serializable {
    private String methodName;
    Class<?>[] parameterTypes;
    Object[] args;
    /**
     * 具体的接口类型
     */
    Class inter;
}

RequestBody中我们定义了需要调用的方法名称,入参信息

Response定义如下:

@Builder
@Data
public class Response {
    private ResponseHeader header;
    private ResponseBody body;
}
@Data
@Builder
public class ResponseHeader {
    private long flag;
    private long requestID;
    private long dataLength;
}
@Data
@Builder
public class ResponseBody {
    private String desc;
}

body中的描述,既可能是车辆信息的描述,也可能是颜色信息的描述。

至此,我们的通信协议已经有了,Service端的业务实现也已经有了,下面我们来定义下netty服务:

public class Service {
    public void start() {
        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup worker =  new NioEventLoopGroup(1);

        ChannelFuture bind = new ServerBootstrap()
                .group(boss, worker)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {

                        System.out.println("server accept request , client port : " + nioSocketChannel.remoteAddress().getPort());

                        ChannelPipeline pipeline = nioSocketChannel.pipeline();
                        pipeline.addLast(new ByteDecoder());
                        pipeline.addLast(new ServerRequestHandler());

                    }
                }).bind(new InetSocketAddress("127.0.0.1", 9090));

        try {
            bind.sync().channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

我们定义出两个NioEventLoopGroup,boss用于监听客户端连接,worker用于业务处理,关注的channel为NioServerSocketChannel,同时加入两个handler:ByteDecoder用于字节解析,ServerRequestHandler用于处理解析出来的request。

/**
 * @author ralap
 * @date 2021-02-07 12:29
 * 定义解码器
 */
public class ByteDecoder extends ByteToMessageDecoder {

    /**
     * 从byteBuf中将解析出来的对象放入list中
     *
     * @param channelHandlerContext
     * @param buf
     * @param list
     * @throws Exception
     */
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf buf, List<Object> list) throws Exception {

        // 根据通信协议,从buf中解析出数据,真实情况应该是按照buf的字节去读取数据,这边为了方便,直接反序列了
        if (buf != null && buf.readableBytes() >= 4) {
            String message = buf.toString(CharsetUtil.UTF_8);

            // 将buf 的 readindex索引随读取的数据向前移动
            buf.skipBytes(buf.readableBytes());

            // 解析出当前是request,将字节解码出request信息
            if (message.contains(String.valueOf(HeaderCode.REQUEST.getCode()))) {
                Request request = JsonUtil.deserialize(message, Request.class);

                if (request != null) {
                    list.add(request);
                }
            }

            // 解析出当前是response,将字节解码出response信息
            if (message.contains(String.valueOf(HeaderCode.RESPONSE.getCode()))) {
                Response response = JsonUtil.deserialize(message, Response.class);

                if (response != null) {
                    list.add(response);
                }
            }
        }
    }
}

无论是server端的从buf中解析出Reqeust,还是client端从buf中解析数Response,都通过ByteDecoder进行字节码解析。

/**
 * @author zji-Ralap
 * @date 2021/2/6 16:30
 */
public class ServerRequestHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        try {

            if (msg == null) {
                return;
            }

            Request request = (Request) msg;

            System.out.println("服务端收到请求数据 : " + JsonUtil.serialize(request));

            // 使用netty的eventLoop来处理业务和返回数据,也可以使用自己定义的线程池
            ctx.executor().execute(() -> {

                // 将受到的Request请求,分发到各自的实现类中进行业务处理
                String desc = new Dispatcher().dispatcherRequest(request);

                Response response = Response.builder()
                        .header(ResponseHeader.builder()
                                .flag(HeaderCode.RESPONSE.getCode())
                                .requestID(request.getHeader().getRequestID())
                                .build())
                        .body(ResponseBody.builder()
                                .desc(desc)
                                .build())
                        .build();

                byte[] responseBytes = JsonUtil.serialize(response).getBytes();
                ByteBuf responseBuf = PooledByteBufAllocator.DEFAULT.directBuffer(responseBytes.length);
                responseBuf.writeBytes(responseBytes);

                try {
                    ctx.writeAndFlush(responseBuf).sync();
                } catch (InterruptedException e) {
                    // e.printStackTrace();
                }
            });
        } catch (Exception ex) {

        }
    }

}

ServerRequestHandler,在解码器成功解析出Request之后,对request进行处理。这边使用Dispatcher对请求进行分发,如果是car的则分发到car的实现类carImpl,如果是color的请求,则分发到colorImpl实现类中进行实际业务处理。

public class Dispatcher {

    private List<Object> implList;

    public Dispatcher() {
        Car carImpl = new CarImpl();
        Color colorImpl = new ColorImpl();

        // 将实现类注册进去
        implList = new ArrayList<>();

        implList.add(carImpl);
        implList.add(colorImpl);
    }

    /**
     * 将请求分发到实际业务中
     *
     * @param request
     * @return
     */
    public String dispatcherRequest(Request request) {

        for (Object obj : implList) {
            if (request.getBody().getInter().isInstance(obj)) {
                Method method = getMethod(request.getBody().getMethodName(), obj.getClass(),
                        request.getBody().getParameterTypes());

                if (method != null) {
                    try {
                        return (String) method.invoke(obj, request.getBody().getArgs());
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        return "";
    }

    public static Method getMethod(String methodName, Class testClass, Class<?>... parameterTypes) {
        try {
            Method method = testClass.getDeclaredMethod(methodName, parameterTypes);
            method.setAccessible(true);

            return method;
        } catch (Exception ex) {
            return null;
        }
    }

}

现在我们已经处理完了server端的解析和具体业务处理,下面我们需要创建出一个连接,用于client和server建立通信:

public class ConnectFactory {

    private static ConnectFactory instance;

    private ConcurrentHashMap<InetSocketAddress, ConnectPool> map;

    private ConnectFactory() {
        map = new ConcurrentHashMap<>();
    }

    private int pollCount = 1;

    public static ConnectFactory getInstance() {

        if (instance == null) {
            synchronized (ConnectFactory.class) {
                if (instance == null) {
                    instance = new ConnectFactory();
                }
            }
        }

        return instance;
    }

    /**
     * 根据当前address获取channel
     *
     * @param address
     * @return
     */
    public NioSocketChannel getSocketChannel(InetSocketAddress address) throws InterruptedException {
        ConnectPool pool = map.getOrDefault(address, null);

        if (pool == null) {
            // 根据当前的address创建一个连接线程池,此处我们假设线程池中仅有一个连接
            pool = new ConnectPool(pollCount);
            map.putIfAbsent(address, pool);
        }

        // 从线程池中取出一个连接
        int index = new Random().nextInt(pollCount);

        Connect connect = pool.getConnects().get(index);

        if (connect.getChannel() != null && connect.getChannel().isActive()) {
            // 连接是可以直接使用的
            return connect.getChannel();
        }

        // 连接不可以直接使用
        synchronized (connect.getLock()) {
            // 创建出一个可用的连接
            connect.setChannel(createChannel(address));

            return connect.getChannel();
        }
    }

    private NioSocketChannel createChannel(InetSocketAddress address) throws InterruptedException {
        NioEventLoopGroup workGroup = new NioEventLoopGroup(1);

        ChannelFuture connect = new Bootstrap()
                .group(workGroup)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                        ChannelPipeline pipeline = nioSocketChannel.pipeline();

                        pipeline.addLast(new ByteDecoder());
                        pipeline.addLast(new ClientResponseHandler());
                    }
                }).connect(address);

        // 保证连接建立完成之后再返回
        return (NioSocketChannel) connect.sync().channel();
    }
}
@Data
public class ConnectPool {
    private List<Connect> connects;

    public ConnectPool(int poolSize) {

        connects = new ArrayList<>(poolSize);

        for (int index = 0; index < poolSize; index++) {
            Connect connect = Connect.builder()
                    .channel(null)
                    .lock(new Object())
                    .build();

            connects.add(connect);

        }
    }
}

我们通过ConnectFactory从连接池中随机找出一条可用的channel进行通信。

最后便是我们的client端实现,通过动态代理的方式实现car和color的统一调用:

public class MyProxy {

    /**
     * 获取接口的动态代理
     *
     * @param classz
     * @param <T>
     * @return
     */
    public <T> T getProxy(Class<T> classz) {
        ClassLoader loader = classz.getClassLoader();
        Class<?>[] methodInfo = {classz};

        return (T) Proxy.newProxyInstance(loader, methodInfo, (proxy, method, args) -> {
            Request request = createRequest(method, args, classz);

            byte[] requestBytes = JsonUtil.serialize(request).getBytes(CharsetUtil.UTF_8);

            // 从连接池中获取一个连接
            InetSocketAddress address = new InetSocketAddress("127.0.0.1", 9090);
            NioSocketChannel socketChannel = ConnectFactory.getInstance().getSocketChannel(address);

            // 将数据发送出去
            ByteBuf byteBuf = PooledByteBufAllocator.DEFAULT.directBuffer(requestBytes.length);

            byteBuf.writeBytes(requestBytes);

            ChannelFuture channelFuture = socketChannel.writeAndFlush(byteBuf);
            channelFuture.sync();

            // 因为是同步调用,睡1s,模拟等待netty服务端处理数据
            Thread.sleep(1000);

            Response response = (Response) ResponseProcess.get(request.getHeader().getRequestID());
            return response.getBody().getDesc();
        });
    }

    private Request createRequest(Method method, Object[] args, Class classz) {
        // 构建请求的主题
        RequestBody body = RequestBody.builder()
                .args(args)
                .methodName(method.getName())
                .parameterTypes(method.getParameterTypes())
                .inter(classz)
                .build();

        // 构建请求head
        RequestHeader header = RequestHeader.builder()
                .flag(HeaderCode.REQUEST.getCode())
                .dataLength(JsonUtil.serialize(body).getBytes().length)
                .requestID(UUID.randomUUID().getLeastSignificantBits())
                .build();

        return Request.builder()
                .header(header)
                .body(body)
                .build();
    }
}
public class CarClient {
    public void getCarInfo(String carName) {
        Car car = new MyProxy().getProxy(Car.class);
        String response = car.getCar(carName);

        System.out.println("CarClient receive :" + JsonUtil.serialize(response));
    }
}
public class ColorClient {
    public void getColor(String color) {
        Color car = new MyProxy().getProxy(Color.class);
        String response = car.getColor(color);

        System.out.println("ColorClient receive :" + JsonUtil.serialize(response));
    }
}

到此处,我们已经写完了全部逻辑。
我们再写一个方法,让功能跑起来试下:

public class Main {
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            Service service = new Service();
            service.start();
        }).start();

        Thread.sleep(100);

        new Thread(() -> {
            CarClient client = new CarClient();
            client.getCarInfo("兰博基尼");
        }).start();


        new Thread(() -> {
            CarClient client = new CarClient();
            client.getCarInfo("法拉利");
        }).start();

        new Thread(() -> {
            ColorClient client = new ColorClient();
            client.getColor("红色");
        }).start();

        new Thread(() -> {
            ColorClient client = new ColorClient();
            client.getColor("紫色");
        }).start();

    }
}

先启动server端服务,然后发起四次问询,运行结果如下:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值