一个最简单的RPC调用Demo04

MyRPC版本三

上一篇博客的地址:https://blog.csdn.net/qq_41115379/article/details/119153197

Netty高性能网络框架的使用

Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。

针对之前提出的问题,主要就是提高这个性能,从两个方面入手,网络传输从BIO到NIO,序列化要减少字节流长度,提高序列化反序列化效率,而知名的RPC框架,dubbo和grpc都是用netty底层进行通信的

pom.xml 导入netty
    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
        <version>4.1.20.Final</version>
    </dependency>
重构客户端代码

是因为客户端代码太乱了,先进行代码重构,才有利于后面使用netty的方式实现客户端,使之不同方式网络连接的客户端有相同的结构,同样的api

假设有了两个客户端,SimpleRPCClient (使用Java BIO的方式)和NettyRPCClient(使用netty) ,他们的共性是发送请求与得到response,而建立连接与发送请求的方式不同

RPC接口以及一个实现
package RPCVersion01.MyRPCVersion03.client;

import RPCVersion01.MyRPCVersion03.common.RPCRequest;
import RPCVersion01.MyRPCVersion03.common.RPCResponse;

/**
 * 代替原来的RPCClient
 */
//直接把客户端作为一个接口
//把共性给抽取出来
public interface RPCClient {
    //共性就是 发送请求和得到response
    RPCResponse sendRequest(RPCRequest request);
}
package RPCVersion01.MyRPCVersion03.client;

import RPCVersion01.MyRPCVersion03.common.RPCRequest;
import RPCVersion01.MyRPCVersion03.common.RPCResponse;
import lombok.AllArgsConstructor;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * 代替原来的SimpleRPCClient
 */
//使用BIO方式实现这个接口
@AllArgsConstructor
public class SimpleRPCClient implements RPCClient {
    //携带的参数
    private String host;
    private int port;
    //这里客户端发起一次请求调用,Socket建立连接,发起请求request,得到相应response
    //这里request 是封装好的,不同的service需要进行不同的封装
    //客户只需要知道service接口,需要一层动态代理来根据反射封装不同的service
    public RPCResponse sendRequest(RPCRequest request) {
        try{
            //发起一次Socket连接请求
            Socket socket=new Socket(host,port);
            //之后会被SockServer的
            ObjectInputStream objectInputStream=new ObjectInputStream(socket.getInputStream());
            ObjectOutputStream objectOutputStream=new ObjectOutputStream(socket.getOutputStream());

            System.out.println(request);
            //写入request 因为写入之后要输出出去 所有是output这边
            objectOutputStream.writeObject(request);
            //然后刷新一下
            objectOutputStream.flush();

            //再通过读取来获取对应的response
            RPCResponse response = (RPCResponse) objectInputStream.readObject();
            return response;

        } catch (UnknownHostException e) {
            e.printStackTrace();
            System.out.println("UnKnowHostException====");
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IOException====");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            System.out.println("ClassNotFound====");
        }
        return null;
    }
}
重构PRCClientProxy
package RPCVersion01.MyRPCVersion03.client;

import RPCVersion01.MyRPCVersion03.common.RPCRequest;
import RPCVersion01.MyRPCVersion03.common.RPCResponse;

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

//这个类动态代理Service类,封装不同的service请求为request对象
//并且持有一个RPCClient对象,负责与客户端的通信
public class PRCClientProxy implements InvocationHandler {
    //创建一个RPCClient对象
    private RPCClient rpcClient;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //这里才是真正创建的方法 构建一个request
        //interfaceName是服务类的名字 这个名字怎么获取呢
        //基本上是不用去管 proxy都是method的问题
        //getDeclaringClass是返回 声明这个method对象表示的方法的类的class对象
        //就相当于是 A这个类有 a 和b这两个方法,通过方法的getDeclaringClass的getName就可以知道 A这个名字了
        //相当于就是知道类 getParameterTypes就是返回方法里面的参数的类型 那params就表示的是参数吧
        RPCRequest rpcRequest=RPCRequest.builder().interfaceName(method.getDeclaringClass().getName())
                .methodName(method.getName())
                .params(args).paramsTypes(method.getParameterTypes()).build();

        //通过生成的request来进行传输得到response
        RPCResponse rpcResponse=rpcClient.sendRequest(rpcRequest);
        return rpcResponse.getData();
    }
       //再写一个getProxy的方法
    <T>T getProxy(Class<T> clazz){
        Object o= Proxy.newProxyInstance(clazz.getClassLoader(),new Class[]{clazz},this);
        return (T) o;
    }
}
使用netty方式进行数据的传输,实现NettyRPCServer,NettyRPCClient
NettyRPCClient
package RPCVersion01.MyRPCVersion03.client;

import RPCVersion01.MyRPCVersion03.common.RPCRequest;
import RPCVersion01.MyRPCVersion03.common.RPCResponse;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.AttributeKey;

/**
 * 实现RPCClient接口
 */
public class NettyRPCClient implements RPCClient{
    private static final Bootstrap bootstrap;
    private static final EventLoopGroup eventLoopGroup;
    private String host;
    private int port;
    public NettyRPCClient(String host,int port){
        this.host=host;
        this.port=port;
    }
    //这里就是这些static的初始化
    static {
        eventLoopGroup=new NioEventLoopGroup();
        bootstrap=new Bootstrap();
        //然后就是那个创建过程
        bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new NettyClientInitializer());
    }

    /**
     * 这里需要操作一下,因为netty的传输都是异步的,你发送request,会立刻返回
     * 而不是想要的相应的response
     * @param request
     * @return
     */
    //经过测试 发现不是这部分的错误
    @Override
    public RPCResponse sendRequest(RPCRequest request) {
        try{
            ChannelFuture channelFuture=bootstrap.connect(host,port).sync();
            Channel channel=channelFuture.channel();
            //发送数据
            channel.writeAndFlush(request);
            channel.closeFuture().sync();

            //阻塞的获得结果 通过给channel设计别名 获取特定名字下的channel中的内容(这个在hanlder中设置)
            //attributeKey 是线程隔离的,不会有线程安全问题
            //实际上不应通过阻塞,可以通过回调函数
            AttributeKey<RPCResponse> key= AttributeKey.valueOf("RPCResponse");
            //这里就是通过别名获取这个
            RPCResponse rpcResponse=channel.attr(key).get();
            //这个地方就是null了
            System.out.println("NettyRPCClient===="+rpcResponse);
            return rpcResponse;

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return null;
    }

}
NettyClientInitializer
package RPCVersion01.MyRPCVersion03.client;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.serialization.ClassResolver;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;

public class NettyClientInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        //要写这个初始化的方法
        ChannelPipeline pipeline=socketChannel.pipeline();
        //老规矩 为了防止粘包和拆包
        pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));
        pipeline.addLast(new LengthFieldPrepender(4));

        //这里忘记写了
        pipeline.addLast(new ObjectEncoder());
        pipeline.addLast(new ObjectDecoder(new ClassResolver() {
            @Override
            public Class<?> resolve(String className) throws ClassNotFoundException {
                return Class.forName(className);
            }
        }));

        pipeline.addLast(new NettyClientHandler());
    }
}
NettyClientHandler
package RPCVersion01.MyRPCVersion03.client;

import RPCVersion01.MyRPCVersion03.common.RPCResponse;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.EventExecutorGroup;

public class NettyClientHandler extends SimpleChannelInboundHandler<RPCResponse> {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, RPCResponse rpcResponse) throws Exception {
     //接收到response,给channel设计别名,让sendRequest 里读取response
        //这里应该是设计别名
        AttributeKey<RPCResponse> key= AttributeKey.valueOf("RPCResponse");
        //再把别名放进去 这里是set方法 对应之后的get方法
        channelHandlerContext.channel().attr(key).set(rpcResponse);
        //再把通道关闭
        channelHandlerContext.channel().close();
    }
    //另一个override的还没写
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
Netty服务端
NettyRPCServer 服务端的实现
package RPCVersion01.MyRPCVersion03.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.AllArgsConstructor;

@AllArgsConstructor
public class NettyRPCServer implements RPCServer{
    //实现这两个方法
    private ServiceProvider serviceProvider;
    @Override
    public void start(int port) {
        //netty服务线程组boss负责建立连接,work负责具体的请求
        //NioEventLoopGroup直接就是 EventLoopGroup的具体实现
        //boss word就相当于是 parent和childGroup
        // boss 或者说是parent是对port进行监听的
        //word 或者说是 ChildGroup是用来和Channel绑定的
        NioEventLoopGroup bossGroup=new NioEventLoopGroup();
        NioEventLoopGroup workGroup=new NioEventLoopGroup();
        System.out.println("Netty服务端启动了....");
        try{
            //启动服务器 是Netty的启动程序 引导类
            ServerBootstrap serverBootstrap=new ServerBootstrap();
            //初始化
            //这里没用到Handler 因为这个调用的是父类的,但childHandler则是自己的,是负责和客户端进行连接的IO交互
            serverBootstrap.group(bossGroup,workGroup).channel(NioServerSocketChannel.class)
                    .childHandler(new NettyServerInitializer(serviceProvider));


            //上面这些都是初始化,都是为了后面的操作做准备
            //同步阻塞 ChannelFuture是异步操作的"代言人”
            //.bind是为了通过端口创建,.sync是为了让主线程间接调用wait(),进而实现阻塞的效果
            //当调用bind(port)时,是异步的,因此为了保证在初始化完成后才进行操作,避免调用一个初始化未完成的句柄,sync方法是等待异步操作执行完毕.
            ChannelFuture channelFuture=serverBootstrap.bind(port).sync();

            //死循环监听
            channelFuture.channel().closeFuture().sync();


        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            //应该是关闭吧
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }

    }

    @Override
    public void stop() {

    }
}

NettyServerInitializer 初始化
package RPCVersion01.MyRPCVersion03.server;

import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.serialization.ClassResolver;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import lombok.AllArgsConstructor;

//他的作用就是生成一个ChannelHandler

/**
 * 初始化,主要负责序列化的编码和解码,需要解决netty的粘包问题
 */
@AllArgsConstructor
public class NettyServerInitializer extends ChannelInitializer<SocketChannel> {
    private ServiceProvider serviceProvider;
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        //意思是 初始化Channel
        //创建的这个pipeline是可以把channel依次进去的
        ChannelPipeline pipeline=socketChannel.pipeline();

        /**
         * 后面这两个相当于是Netty自带的格式功能,总的来说就是为了解决粘包和拆包的问题
         */
        //消息格式[长度][消息体],解决粘包问题
        //LengthFieldBasedFrameDecoder是通用拆包器,几乎所有和长度相关的二进制协议都可以用他来实现
        //第一个参数是表示包的最大长度,第二个参数是 长度域的偏移量,0表示无偏移。第三个参数是 长度域的长度
        //第五个参数 是4的话,表示获取完一个完整的数据包之后,忽略前面的四个字节
        //第四个参数 lengthAdjustment是对包体长度调整的大小
        pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));
        //计算过当前待发送消息的长度 写入到前四个字节中
        pipeline.addLast(new LengthFieldPrepender(4));
        //这里还是使用的Java的序列化方法,netty的自带的解码编码支持传输
        pipeline.addLast(new ObjectEncoder());
        pipeline.addLast(new ObjectDecoder(new ClassResolver() {
            @Override
            public Class<?> resolve(String className) throws ClassNotFoundException {
                return Class.forName(className);
            }
        }));
        pipeline.addLast(new NettyPRCServerHandler(serviceProvider));


    }
}

NettyRPCServerHandler netty server具体的handler

package RPCVersion01.MyRPCVersion03.server;

import RPCVersion01.MyRPCVersion03.common.RPCRequest;
import RPCVersion01.MyRPCVersion03.common.RPCResponse;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.concurrent.EventExecutorGroup;
import lombok.AllArgsConstructor;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

@AllArgsConstructor
public class NettyPRCServerHandler extends SimpleChannelInboundHandler<RPCRequest> {
    private ServiceProvider serviceProvider;

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, RPCRequest rpcRequest) throws Exception {
        System.out.println("channelRead0===="+rpcRequest);
        RPCResponse rpcResponse=getResponse(rpcRequest);
        channelHandlerContext.writeAndFlush(rpcResponse);
        channelHandlerContext.close();
    }


    private RPCResponse getResponse(RPCRequest rpcRequest) {
        //得到服务名
        String interfaceName=rpcRequest.getInterfaceName();
        //得到服务端相应服务实现类
        Object service = serviceProvider.getService(interfaceName);
        //反射调用方法
        Method method=null;
        try{
            method=service.getClass().getMethod(rpcRequest.getMethodName(),rpcRequest.getParamsTypes());
            Object invoke = method.invoke(service, rpcRequest.getParams());
            return RPCResponse.success(invoke);
        } catch (NoSuchMethodException |IllegalAccessException |InvocationTargetException e) {
            System.out.println("方法执行错误");
            e.printStackTrace();
            return RPCResponse.fail();
        }

    }
    //一个overrider
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
客户端和服务端的测试代码
服务端的测试代码 TestServe
package RPCVersion01.MyRPCVersion03.server;

import RPCVersion01.MyRPCVersion03.service.BlogService;
import RPCVersion01.MyRPCVersion03.service.BlogServiceImpl;
import RPCVersion01.MyRPCVersion03.service.UserService;
import RPCVersion01.MyRPCVersion03.service.UserServiceImpl;

public class TestServer {
    public static void main(String[] args) {
        UserService userService=new UserServiceImpl();
       // BlogService blogService=new BlogServiceImpl();
        //再使用Map把服务添加进去
    /*    Map<String,Object> serviceProvide=new HashMap<>();

        //暴露两个服务接口 在RPCServer中加入一个HashMap
        serviceProvide.put("PRCVersion01.UserService",userService);
        serviceProvide.put("PRCVersion01.BlogService",blogService);*/

        //因为直接写了一个ServiceProvider所以可以进行修改
        ServiceProvider serviceProvider = new ServiceProvider();
        serviceProvider.provideServiceInterface(userService);
       // serviceProvider.provideServiceInterface(blogService);
        //MyRPC 版本2的   RPCServer RPCServer = new SimpleRPCRPCServer(serviceProvider) 会报错呀
        //这里修改了一下 不知道有没有用
       // RPCServerVersion2 rpcServerVersion2=new SimpleRPCServerVersion2(serviceProvider.getInterfaceProvider());
       //线程池版
        RPCServer rpcServerVersion2=new NettyRPCServer(serviceProvider);
       rpcServerVersion2.start(8899);
    }
}
服务端的测试代码 TestClient
package RPCVersion01.MyRPCVersion03.client;


import RPCVersion01.MyRPCVersion03.common.User;
import RPCVersion01.MyRPCVersion03.service.UserService;

public class TestClient {
    public static void main(String[] args) {
        //创建一个client
        System.out.println("TestClient开始了====");
        RPCClient nettyRPCClient=new NettyRPCClient("127.0.0.1",8899);
      //  System.out.println("nettyRPCClient===="+nettyRPCClient);
        //把客户端传入到代理客户端中
        RPCClientProxy rpcClientProxy=new RPCClientProxy(nettyRPCClient);
      //  System.out.println("rpcClientProxy===="+rpcClientProxy);
        //代理客户端根据不同的服务,获得一个代理类
        //经过测试 是这个部分出现了问题
        UserService proxy = rpcClientProxy.getProxy(UserService.class);
        //问题就处在这  proxy不能先调用的
      // System.out.println("proxy===="+proxy);

        //调用方法
        User userByUserId = proxy.getUserByUserId(10);
        System.out.println("userByUserId===="+userByUserId);


    }
}
运行结果

在这里插入图片描述

代码问题

这里关于动态代理,出现了一个问题,让我代码一直运行不起来

经过debug之后,发现错误出现这个地方

正确的代码部分:

\

错误的代码:

在这里插入图片描述

也就是多写了一个

System.out.println("proxy===="+proxy);

在debug的情况下看看到了这部分会出现什么情况

首先

RPCClientProxy rpcClientProxy=new RPCClientProxy(nettyRPCClient);

这一步只是进行了 RPCClientProxy部分的注解@AllArgsConstructor,也就是进行了创建构造函数

这个时候下面的方法都还没开始调用

然后到这一步

在这里插入图片描述

调用了Proxy下的 getproxy方法
在这里插入图片描述

这个时候才创建好一个proxy

然后是这一步

在这里插入图片描述

首先他会报出一个异常:

Method threw ‘java.lang.NullPointerException’ exception. Cannot evaluate com.sun.proxy.$Proxy0.toString()

https://blog.csdn.net/weixin_43979923/article/details/107151410

但这个异常是可以暂时忽略不计的

进入这个之后 就先进入到了RPCReuqest的build方法,也就是request的初始化

随即进入到invoke当中

问题就在于 这个时候的proxy还没有指定对应的方法

在这里插入图片描述

这样子生成的request是不带任何参数的

在这里插入图片描述

然后调用了 RPCClient的sendRequest

在这里插入图片描述

所以导致这个时候输出的是null了

在这里插入图片描述

而正常情况下

到这一步的时候

首先进行RPCRequest的builder

在这里插入图片描述

然后进入到invoke当中

在这里插入图片描述

注意,这个时候的method就是有方法的

这样子生成的request就是有值的

RPCRequest(interfaceName=RPCVersion01.MyRPCVersion03.service.UserService, methodName=getUserByUserId, params=[10], paramsTypes=[class java.lang.Integer])

并且这样子输出的response也不是null 了

在这里插入图片描述

所以问题还是出在了 这个输出上面

总结

这个版本完成了客户端的重构,使之能够支持多种版本客户端的扩展,实现了RPCClient接口

并使用了Netty实现了客户端和服务端的通信

痛点

使用了Java自带的序列化方式,Java序列化写入不仅是完整的类名,也包含整个类的定义,包含所有被引用的类,不够通用,不够高效

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值