手写RPC(九) 测试

编写测试方法:

package com.info.consumer;

import com.info.api.Animal;

public class ConsumerApplication {
    
    

    public static void main(String[] args) {
    
    
        ClientProxy proxy = new ClientProxy();
        Animal animal = proxy.clientProxy(Animal.class, "localhost", 9000);
        final String runResult = animal.run();
        final String speakResult = animal.speak();
        System.out.println("runResult = " + runResult);
        System.out.println("speakResult = " + speakResult);
    }
}

一切看似都很完美,但是,当你执行main方法的时候,oh my god!!!
为什么解码了两次然后就没有然后了?经过仔细排查,定位到问题是由于LengthFieldBasedFrameDecoder未能按照规定要求读取数据(读取完所有数据还不满足我们代码的限制)

// 如果可读字节数小于消息长度,说明消息还不完整。
        if (in.readableBytes() < messageLength) {
    
    
            // 消息不够 重置读标识
            in.resetReaderIndex();
            return;
        }

最终LengthFieldBasedFrameDecoder的参数设置对应如下:

new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 4, 4, 9, 0)

对应的CustomClientInitializerCustomServerInitializer都需要进行调整。

关于LengthFieldBasedFrameDecoder不得不说的事

处理了LengthFieldBasedFrameDecoder的问题,再次运行代码,不出意外你会得到一个实例化对象失败的异常!
继续排查代码,发现是服务端在收到客户端请求并且解码完成以后,在反射调用方法时实例化对象失败,debug发现是因为传进来的是接口类型,而接口类型不能被实例化,因此在调用clz.newInstance()时抛出异常。
怎么去获取对应接口的示例呢?这里通过ServiceLoader来实现,并且ServiceLoader需要依赖相关的配置,rpc-provider模块resource目录下新建META-INF/services/com.info.api.Animal文件,文件内添加 com.info.provider.service.impl.Dog,这里需要注意的是文件名com.info.api.Animal是需要对应接口的全限定名(别问为什么,这是规定!),文件的内容是对应接口的实现类的全限定名,多个实现类的情况时,每个实现类单独写一行。配置完成以后通过ServiceLoader.load(clz)即可获取到对应接口的实现类。

修改代码CustomServerHandler中的invoke方法

// 调用目标方法
    private Object invoke(Request request) {
    
    
        final String clzName = request.getClassName();
        try {
    
    
            final Class<?> clz = Class.forName(clzName);
            final ServiceLoader<?> serviceLoader = ServiceLoader.load(clz);
            final Iterator<?> iterator = serviceLoader.iterator();
            Object instance = null;
            if (iterator.hasNext()) {
    
    
                instance = iterator.next();
            } else {
    
    
                log.error("获取 {} 实现类失败", clz);
                return null;
            }
            final Method method = clz.getDeclaredMethod(request.getMethodName(), request.getParameterTypes());
            return method.invoke(instance, request.getParams());
        } catch (ClassNotFoundException e) {
    
    
            e.printStackTrace();
        } catch (IllegalAccessException e) {
    
    
            e.printStackTrace();
        } catch (InvocationTargetException e) {
    
    
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
    
    
            e.printStackTrace();
        }
        return null;
    }

修改完成再次运行代码,反序列化失败又是什么鬼?再次排查代码,无果,对了,我们不是可以至此多种序列化方式么?试试 json!说时迟那时快,
rpc-portocol模块pom.xml文件添加依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
</dependency>

serial包下添加json的序列化支持

package com.info.protocol.serial;


import com.alibaba.fastjson.JSON;
import com.info.protocol.enums.SerializeTypeEnum;

import java.nio.charset.StandardCharsets;


public class JsonSerializer implements Serializer {
    
    
    @Override
    public <T> byte[] serialize(T obj) {
    
    
        return JSON.toJSONString(obj).getBytes(StandardCharsets.UTF_8);
    }

    @Override
    public <T> T deserialize(byte[] data, Class<T> clz) {
    
    
        final String content = new String(data);
        return JSON.parseObject(content, clz);
    }

    @Override
    public byte getType() {
    
    
        return SerializeTypeEnum.JSON.getCode();
    }
}

SerializerManager.java类添加对json序列化的管理

static {
    
    
        Serializer jdkSerializer = new JdkSerializer();
        Serializer jsonSerializer = new JsonSerializer();
        serializerMap.put((int) jdkSerializer.getType(), jdkSerializer);
        serializerMap.put((int) jsonSerializer.getType(), jsonSerializer);
    }

SerializeTypeEnum.java添加 json 的序列化方式枚举

扫描二维码关注公众号,回复: 13576954 查看本文章
JSON((byte) 2);

最后把我们使用的序列化方式改为 json,CustomInvocationHandlerinvoke方法,生成header对象时,使用 json 序列化方式

Header header = new Header(CommonConstant.MAGIC, CommonConstant.PROTOCOL_VERSION,
                SerializeTypeEnum.JSON.getCode(), 0, MessageTypeEnum.REQUEST.getCode(), requestId);

打完收工!so easy!

再次运行代码,刚刚真是 一顿操作猛如虎回头一看原地杵!,反序列化失败的异常依然存在。

最终debug定位到代码,服务端在完成反射调用方法后组装结果时,portocol对象中conent的内容应该是一个Response对象而不是反射调用方法返回的结果,修改CustomServerHandler.javachannelRead0方法

@Override
    protected void channelRead0(ChannelHandlerContext ctx, Protocol<Request> msg) throws Exception {
    
    
        Protocol protocol = new Protocol<>();
        final Header header = msg.getHeader();
        header.setMessageType(MessageTypeEnum.RESPONSE.getCode());
        // 反射调用目标方法
        Object result = invoke(msg.getContent());
        protocol.setHeader(header);
        protocol.setContent(new Response(result,"success"));
        ctx.writeAndFlush(protocol);
    }

事实证明,写代码一定要细心!
再次运行代码,不出意外的话控制台打印出了如下的结果:

runResult = 小狗跑起来一蹦一跳…
speakResult = 小狗叫起来汪汪汪…

再出意外的话,需要你细细调试了(当然也可以留言,大家一起学习),相信我,当你得到上面的结果,一定会收货颇多。

系列文章传送门如下:
手写RPC(一) 絮絮叨叨
手写RPC(二) 碎碎念
手写RPC(三) 基础结构搭建
手写RPC(四) 核心模块网络协议模块编写 ---- netty服务端
手写RPC(五) 核心模块网络协议模块编写 ---- 自定义协议
手写RPC(六) 核心模块网络协议模块编写 ---- 实现编解码器
手写RPC(七) 核心模块网络协议模块编写 ---- 实现客户端
手写RPC(八) provider、consumer 实现
手写RPC(十) 优化
关于 LengthFieldBasedFrameDecoder 不得不说的事

猜你喜欢

转载自blog.csdn.net/hxj413977035/article/details/121629031