简易RPC框架实现——1、基于jdk序列化机制的BIO实现

本文主要实现了一个简易RPC框架的实现,项目地址为https://github.com/fzzfrjf/FZZF-RPC

本章对应的commit为43a5918,主要是基于jdk自带的序列机制以及BIO实现了远程方法调用。

概述

RPC框架,主要就是解决在实现物理隔离的不同服务之间,调用属于其他服务的方法(是不是感觉类似与feign),RPC框架与feign远程调用的最大区别就是feign需要实现http协议,而RPC框架可以不用走http协议。

在RPC中习惯于将调用请求者叫做client端,被调用者叫做server端。

借用一张guide哥的图
在这里插入图片描述
对于RPC框架的原理分析,其实就是客户端发出一个方法调用的请求,经过网络传输通信,使得服务端感知到你需要调用的请求,那么他就去调用你所需要调用的方法。当然其中还有许多细节需要深思。比如在网络传输过程中序列化机制采用什么,网络传输的方式采用什么,也需要自己设计协议编码。当然,也可以引入注册中心,来实现对于各个服务的管理,也更加方便于实现负载均衡,容错等。

通用接口

在一切工作之前,我们首先要写好我们客户端的接口:

public interface HelloService {

    String sayHello(RpcObject object);
}

在这个接口中我们需要传入一个RpcObject对象:

@Getter
@Setter
@AllArgsConstructor
public class RpcObject implements Serializable {
    private int id;
    private String message;
}

接口以及对象都是客户端可以直接获取的,而服务端则存在客户端远程调用的方法:

public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(RpcObject object) {
        return "这是id为:" + object.getId() + "发送的:" + object.getMessage();
    }
}

传输标准

在进行网络传输过程中我们需要规定一个传输以及返回数据格式方便接收。

首先思考一下客户端需要传递哪些信息给服务端才能让服务端准确知道客户端需要调用的方法?

第一个是接口的名字,以及方法的名字,这样才能大概知道是哪个接口实现类去进行调用,但是由于方法的重载,因此我们还需要知道方法的参数类型,以及方法参数。将这几个参数进行封装:

@AllArgsConstructor
@Builder
@Getter
public class RpcRequest implements Serializable {
    private String requestId;
    private String interfaceName;
    private String methodName;
    private Class<?>[] parameterTypes;
    private Object[] parameters;
}

当服务端收到这个请求进行处理过后,会向客户端返回一个方法调用的响应结果:

@Data
public class RpcResponse<T> implements Serializable {

    private int code;

    private String requestId;

    private T data;

    public <T> RpcResponse<T> success(T data,String requestId){
        RpcResponse<T> rpcResponse = new RpcResponse<>();
        rpcResponse.setCode(ResponseCode.SUCCESS.getCode());
        rpcResponse.setRequestId(requestId);
        rpcResponse.setData(data);
        return rpcResponse;
    }

    public  <T>RpcResponse<T> fail(String requestId){
        RpcResponse<T> rpcResponse = new RpcResponse();
        rpcResponse.setRequestId(requestId);
        rpcResponse.setCode(ResponseCode.FAILURE.getCode());
        return rpcResponse;
    }
}

客户端的实现

客户端的实现,首先是给一个顶层接口(因为我们后边会优化使用netty进行网络传输,所以不同的实现方式通过实现这个接口进行):

public interface CommonClient {

    Object sendRequest(RpcRequest rpcRequest,String host,int port);
}

由于客户端的接口没有具体实现,因此需要使用动态代理生成实例,在代理类方法调用里边写入需要传输的RpcRequest:

public class ClientProxy implements InvocationHandler {

    private CommonClient client;
    private String host;
    private int port;

    public ClientProxy(CommonClient client,String host,int port){
        this.client = client;
        this.host = host;
        this.port = port;
    }

    public Object getProxy(Class<?> clazz){
        return Proxy.newProxyInstance(clazz.getClassLoader(),new Class[]{clazz},this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        RpcRequest rpcRequest = RpcRequest.builder()
                .requestId(UUID.randomUUID().toString())
                .interfaceName(method.getDeclaringClass().getName())
                .methodName(method.getName())
                .parameterTypes(method.getParameterTypes())
                .parameters(args)
                .build();
        CompletableFuture<RpcResponse> result = null;
        if(client instanceof SocketClient){
            return ((RpcResponse) client.sendRequest(rpcRequest,host,port)).getData();
        }
        if(client instanceof NettyClient){
            result = (CompletableFuture<RpcResponse>) client.sendRequest(rpcRequest,host,port);
        }
        return result.get();
    }
}

在代理类的invoke中会去调用socketClient中定义的sendRequest方法,该方法主要就是利用jdk自带的BIO进行网络传输:

public class SocketClient implements CommonClient{
    private static final Logger logger = LoggerFactory.getLogger(SocketClient.class);
    @Override
    public Object sendRequest(RpcRequest rpcRequest,String host,int port) {
        try(Socket socket = new Socket(host,port)){
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            oos.writeObject(rpcRequest);
            oos.flush();
            return ois.readObject();
        }catch (IOException | ClassNotFoundException e){
            logger.error("调用时有错误发生:",e);
            return null;
        }
    }
}

至此,客户端这边就完成了一个简单的发送逻辑。

服务端的实现

服务端与客户端类似,首先提供一个顶层接口,然后用SocketServer去实现接口完善功能,在服务端采用的是利用一个线程池,接受到来的请求并利用workThread完成方法的调用并解析传回rpcResponse:

public class SocketServer implements CommonServer{

    private final ExecutorService threadPool;
    private static final Logger logger = LoggerFactory.getLogger(SocketServer.class);

    public SocketServer(){
        int corePoolSize = 5;
        int maximumPoolSize = 50;
        int keepAliveTime = 60;
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100);
        threadPool = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime, TimeUnit.SECONDS,workQueue,Executors.defaultThreadFactory());
    }

    @Override
    public void register(Object service, int port) {
        try(ServerSocket serverSocket = new ServerSocket(port)){
            logger.info("服务器正在启动中.....");
            Socket socket ;
            while( (socket = serverSocket.accept()) != null){
                logger.info("连接成功,客户端ip为:" + socket.getInetAddress());
                threadPool.execute(new WorkThread(service,socket));
            }
        }catch (IOException e){
            logger.error("连接时有错误发生:{}",e);
        }
    }
}

workThread里边就是利用传递过来的RpcRequest的参数进行发射调用方法,并将方法返回的结果传递回去:

public class WorkThread implements Runnable{

    private static final Logger logger = LoggerFactory.getLogger(WorkThread.class);

    private Object service;
    private Socket socket;
    public WorkThread(Object service,Socket socket){
        this.socket = socket;
        this.service = service;
    }
    @Override
    public void run() {
        try(ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream())){
            RpcRequest rpcRequest = (RpcRequest) objectInputStream.readObject();
            Method method = service.getClass().getMethod(rpcRequest.getMethodName(),rpcRequest.getParameterTypes());
            Object result = method.invoke(service,rpcRequest.getParameters());
            objectOutputStream.writeObject(new RpcResponse().success(result,rpcRequest.getRequestId()));
            objectOutputStream.flush();
        }catch (IOException | ClassNotFoundException | InvocationTargetException |NoSuchMethodException | IllegalAccessException e){
            logger.error("调用或发送时有错误发生:",e);
        }
    }
}

至此,一个简单的基于jdk序列化机制的利用BIO网络传输的RPC框架大致完成,接下来是测试一下结果。

测试

客户端

public class SocketClientTest {
    public static void main(String[] args) {
        SocketClient socketClient = new SocketClient();
        ClientProxy proxy = new ClientProxy(socketClient,"127.0.0.1",9000);
        HelloService service = (HelloService)proxy.getProxy(HelloService.class);
        RpcObject rpcObject = new RpcObject(1,"This is SocketClient!");
        String s = service.sayHello(rpcObject);
        System.out.println(s);
    }
}

新建一个Socket服务端,将端口固定为9000,使用代理类获取代理对象,之后进行方法调用返回结果。

服务端

public class SocketServerTest {
    public static void main(String[] args) {
        SocketServer socketServer = new SocketServer();
        HelloService helloService = new HelloServiceImpl();
        socketServer.register(helloService,9000);
    }
}

最后的测试结果如下:
服务端输出:

[main] INFO cn.fzzfrjf.core.SocketServer - 服务器正在启动中.....
[main] INFO cn.fzzfrjf.core.SocketServer - 连接成功,客户端ip为:/127.0.0.1

客户端输出:

这是id为:1发送的:This is SocketClient!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值