手写RPC学习笔记

手写RPC

  • 一、RPC概况
    • 1.1 RPC简介
    • 1.2 系统交互
      • 1.2.1直接交互
      • 1.2.2 间接交互
    • 1.3 各种RPC框架的对比
    • 1.4 RPC核心原理
      • 1.4.1 RPC调用的原理
      • 1.4.2 Call的调用过程
    • 1.5 技术栈
  • 二、RPC手动实现
    • 2.1 创建工程、制定协议、通用工具方法
      • 2.1.1 项目类图
      • 2.1.2 项目搭建
      • 2.1.3 父依赖编写以及导入
      • 2.1.4 编译的版本控制
      • 2.1.5 子模块版本统一配置
      • 2.1.6 Lombok插件下载
      • 2.1.8 设置annotation
    • 2.2 协议模块编写
      • 2.2.1 网络通信端点类
      • 2.2.2 服务类
      • 2.2.3 请求类
      • 2.2.4 响应类
    • 2.3 通用模块编写
      • 2.3.1 JDK动态代理类
      • 2.3.2 代理类测试
    • 2.4 序列化模块编写
      • 2.4.1 编码和解码接口
      • 2.4.2 pom中引入fastjson
      • 2.4.3 序列化、反序列化接口实现
      • 2.4.4 接口实现的测试
    • 2.5 网络模块
      • 2.5.1 依赖引入
      • 2.5.2 客户端网络传输接口
      • 2.5.3 服务端网络传输接口
      • 2.5.4 请求处理类接口
      • 2.5.5 接口实现
    • 2.6 服务端模块
      • 2.6.1 依赖引入
      • 2.6.2 重写ServiceDescriptor
      • 2.6.3 写服务端配置
      • 2.6.4 写服务实例
      • 2.6.5 写服务管理
      • 2.6.6 注册与发现测试
      • 2.6.7 RPC Server代码实现
    • 2.7 客户端模块
      • 2.7.1 依赖引入
      • 2.7.2 网络连接接口以及实现
      • 2.7.3 配置类
      • 2.7.4 Rpc客户端
    • 2.8 RPC使用
      • 2.8.1 新建模块example
      • 2.8.2 引入依赖
      • 2.8.3 Client
      • 2.8.4 Server
      • 2.8.5 CascService
      • 2.8.6 CalcServiceImpl
      • 2.8.7 运行结果
  • 三、参考

本博客文章(学习笔记)导航 (点击这里访问)
在这里插入图片描述

一、RPC概况

1.1 RPC简介

RPC:远程过程调用,是分布式系统常见的一种通信方法,从跨进程到跨物理机已经有十几年的历史
优点:可以将远程调用变成像调用本地方法一样简单

1.2 系统交互

系统的交互方式有两种:直接交互 和 间接交互(中间件交互),下面介绍一下这两种交互方式

1.2.1直接交互

手写RPC学习笔记_第1张图片

1.2.2 间接交互

手写RPC学习笔记_第2张图片

1.3 各种RPC框架的对比

手写RPC学习笔记_第3张图片

1.4 RPC核心原理

1.4.1 RPC调用的原理

step1 server把自己的服务注册到registery
step2 client定于redistry,获取自己想知道的服务信息
step3 如果server信息发生了改变,registory会通知订阅者信息发生了改变
step4 client要发起调用,就可以根据从registory中获取的信息直接调用即可
手写RPC学习笔记_第4张图片

1.4.2 Call的调用过程

step1 client调用接口方法(stub中的接口方法)
step2 将调用信息序列号,以便于在网络上传输
step3 client和server之间建立网络连接
step4 server反序列化传输对象
step5 server的stub查找要调用的方法以及参数
step6 server找到实际实现类的对象,通过反射获取执行结果,再次发送到stub上
step7 stub序列化传输对象
step8 server和client建立网络连接
step9 client反序列化传输对象
step10 client获取调用结果
手写RPC学习笔记_第5张图片

1.5 技术栈

手写RPC学习笔记_第6张图片
手写RPC学习笔记_第7张图片
手写RPC学习笔记_第8张图片
手写RPC学习笔记_第9张图片

二、RPC手动实现

2.1 创建工程、制定协议、通用工具方法

2.1.1 项目类图

一共5大模块

手写RPC学习笔记_第10张图片

2.1.2 项目搭建

step1 新建项目

step2 在项目下,新建6个模块,删除src文件
client:客户端模块
server:服务端模块
codec:编码解码模块
common:通用模块
propto:协议模块
transport:网络通信模块
手写RPC学习笔记_第11张图片

2.1.3 父依赖编写以及导入


    
    <dependencyManagement>
        <dependencies>
            
            <dependency>
                <groupId>commons-iogroupId>
                <artifactId>commons-ioartifactId>
                <version>2.5version>
            dependency>
            
            <dependency>
                <groupId>org.eclipse.jettygroupId>
                <artifactId>jetty-servletartifactId>
                <version>9.4.19.v20190610version>
            dependency>
            
            <dependency>
                <groupId>com.alibabagroupId>
                <artifactId>fastjsonartifactId>
                <version>1.2.44version>
            dependency>
        dependencies>
    dependencyManagement>


    
    <dependencies>
        
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.12version>
        dependency>
        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <version>1.18.8version>
        dependency>
        
        <dependency>
            <groupId>org.slf4jgroupId>
            <artifactId>slf4j-apiartifactId>
            <version>1.7.26version>
        dependency>
        
        <dependency>
            <groupId>ch.qos.logbackgroupId>
            <artifactId>logback-classicartifactId>
            <version>1.2.3version>
        dependency>

    dependencies>

2.1.4 编译的版本控制

    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <version>3.3version>
                <configuration>
                    <source>1.8source>
                    <target>1.8target>
                configuration>
            plugin>
        plugins>
    build>

2.1.5 子模块版本统一配置

     
    <properties>
        <java.version>1.8java.version>
        <common.version>2.5common.version>
        <jetty.version>9.4.19.v20190610jetty.version>
        <fastjson.version>1.2.44fastjson.version>
        <lombok.version>1.18.8lombok.version>
        <slf4j.version>1.7.26slf4j.version>
        <logback.version>1.2.3logback.version>
        <junit.version>4.12junit.version>
    properties>

    
    <dependencyManagement>
        <dependencies>
            
            <dependency>
                <groupId>commons-iogroupId>
                <artifactId>commons-ioartifactId>
                <version>${common.version}version>
            dependency>
            
            <dependency>
                <groupId>org.eclipse.jettygroupId>
                <artifactId>jetty-servletartifactId>
                <version>${jetty.version}version>
            dependency>
            
            <dependency>
                <groupId>com.alibabagroupId>
                <artifactId>fastjsonartifactId>
                <version>${fastjson.version}version>
            dependency>
        dependencies>
    dependencyManagement>

    
    <dependencies>
        
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>${junit.version}version>
        dependency>
        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <version>${lombok.version}version>
        dependency>
        
        <dependency>
            <groupId>org.slf4jgroupId>
            <artifactId>slf4j-apiartifactId>
            <version>${slf4j.version}version>
        dependency>
        
        <dependency>
            <groupId>ch.qos.logbackgroupId>
            <artifactId>logback-classicartifactId>
            <version>${logback.version}version>
        dependency>

    dependencies>

    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.pluginsgroupId>
                <artifactId>maven-compiler-pluginartifactId>
                <version>3.3version>
                <configuration>
                    <source>${java.version}source>
                    <target>${java.version}target>
                configuration>
            plugin>
        plugins>
    build>

2.1.6 Lombok插件下载

去IDEA中的plugin中查找下载即可
手写RPC学习笔记_第12张图片

2.1.8 设置annotation

手写RPC学习笔记_第13张图片

2.2 协议模块编写

协议模块主要包括 网络结点、请求对象、响应对象、服务类

2.2.1 网络通信端点类

package com.smgeek.gkrpc;


import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * 表示网络传输的一个端点
 */
@Data
@AllArgsConstructor
public class Peer {
    private  String host;
    private int port;
}

2.2.2 服务类

package com.smgeek.gkrpc;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 表示服务
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ServiceDescriptor {
    private String clazz;
    private String method;
    private String returnType;
    private String[] parameterTypes;
}

2.2.3 请求类

package com.smgeek.gkrpc;


import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * 表示请求的类
 */

@Data
public class Request {
    private ServiceDescriptor service;
    private  Object[] parameters;
}

2.2.4 响应类

package com.smgeek.gkrpc;

import lombok.Data;

/**
 * 表示响应的类
 */
@Data
public class Response {
    /**
     * 服务返回编码
     * 0 成功
     * 非0 失败
     */
    private  int code=0;

    /**
     * 具体错误的响应信息
     */
    private  String message="ok";

    /**
     * 返回数据
     */
    private  Object data;

}

2.3 通用模块编写

通用模块只有一个动态代理

2.3.1 JDK动态代理类

动态代理

package com.smgeek.gkrpc.common.utils;


import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

/**
 * 反射工具类
 */

public class ReflectionUtils {

    /**
     * 根据class创建对象
     * @param clazz 带创建的对象类
     * @param  对象类型
     * @return 创建好的对象
     */
    public static <T>T newInstance(Class<T> clazz){
        try{
            return  clazz.newInstance();
        }catch (Exception e){
            throw new IllegalStateException(e);
        }
    }

    /**
     * 获取某个clazz的共有方法
     * @param clazz 任意类
     * @return 当前类的所有方法
     */
    public static Method[] getPublicMethods(Class clazz){
        //获取这个对象的所有方法
        Method[] methods=clazz.getDeclaredMethods();
        List<Method> pmethods=new ArrayList<>();
        for(Method m:methods){
            if(Modifier.isPublic(m.getModifiers())){
                pmethods.add(m);
            }
        }
        return pmethods.toArray(new Method[0]);

    }

    /**
     *  调用指定对象的指定方法
     *
     * @param obj 被调用方法的对象
     * @param method 被调用的方法
     * @param args 方法参数
     * @return 返回代理生成的对象
     */
    public static Object invoke(Object obj,Method method,Object... args){
        try{
            return method.invoke(obj,args);
        }catch (Exception e){
            throw  new IllegalStateException(e);
        }
    }
    
}

2.3.2 代理类测试

package com.smgeek.gkrpc.common.utils;


import org.junit.Test;

public class TestClass {


    private String a(){
        return "a";
    }


    public String b(){
        return "b";
    }


    protected  String c(){
        return "c";
    }
}
package com.smgeek.gkrpc.common.utils;

import org.junit.Test;

import java.lang.reflect.Method;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class ReflectionUtilsTest {

    @Test
    public void newInstance(){
        TestClass t=ReflectionUtils.newInstance(TestClass.class);
        assertNotNull(t);
    }

    @Test
    public void getPublicMethods(){
        Method[] methods=ReflectionUtils.getPublicMethods(TestClass.class);
        assertEquals(1,methods.length);
        String mname=methods[0].getName();
        assertEquals("b",mname);
    }

    @Test
    public void invoke(){
        Method[] methods=ReflectionUtils.getPublicMethods(TestClass.class);
        Method b=methods[0];
        TestClass t=new TestClass();
        Object r = ReflectionUtils.invoke(t, b);
        assertEquals("b",r);
    }
}

2.4 序列化模块编写

便于对象在网络上传输,需要序列化和反序列化

2.4.1 编码和解码接口

package com.smgeek.gkrpc.codec;
/**
 * 序列化 对象转化为byte数组
 */
public interface Encoder {
    byte[] encode(Object obj);
}

package com.smgeek.gkrpc.codec;
/**
 * 反序列化 二进制数组转对象
 */
public interface Decoder {
    <T>T decode(byte[] bytes,Class<T> clazz);
}

2.4.2 pom中引入fastjson

    <dependencies>
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
        dependency>
    dependencies>

2.4.3 序列化、反序列化接口实现

package com.smgeek.gkrpc.codec;
import com.alibaba.fastjson.JSON;
/**
* 基于json的序列化实现
*/
public class JSONEncoder implements Encoder {
    @Override
    public byte[] encode(Object obj) {
        return JSON.toJSONBytes(obj);
    }
}
package com.smgeek.gkrpc.codec;
import com.alibaba.fastjson.JSON;
/**
* 基于JSON 的反序列化
*/
public class JSONDecoder implements Decoder{
    @Override
    public <T> T decode(byte[] bytes, Class<T> clazz) {
        return JSON.parseObject(bytes,clazz);
    }
}

2.4.4 接口实现的测试

package com.smgeek.gkrpc.codec;


import lombok.Data;

@Data
public class TestBean {
    private  String name;
    private int age;
}

package com.smgeek.gkrpc.codec;

import org.junit.Test;

import java.lang.reflect.Method;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class JSONEncoderTest {

    @Test
    public void encode(){
        Encoder encoder=new JSONEncoder();
        TestBean bean=new TestBean();
        bean.setName("smgeek");
        bean.setAge(18);
        byte[] bytes=encoder.encode(bean);
        assertNotNull(bytes);
    }
}
package com.smgeek.gkrpc.codec;

import java.lang.reflect.Method;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;

public class JSONDecoderTest {

    @Test
    public void decode() {
        Encoder encoder=new JSONEncoder();
        TestBean bean=new TestBean();
        bean.setName("smgeek");
        bean.setAge(18);
        byte[] bytes=encoder.encode(bean);

        Decoder decoder=new JSONDecoder();
        TestBean bean2 = decoder.decode(bytes, TestBean.class);
        assertEquals(bean.getName(),bean2.getName());
        assertEquals(bean.getAge(),bean2.getAge());
    }

}

2.5 网络模块

2.5.1 依赖引入

    <dependencies>
        <dependency>
            <groupId>commons-iogroupId>
            <artifactId>commons-ioartifactId>
        dependency>
        <dependency>
            <groupId>org.eclipse.jettygroupId>
            <artifactId>jetty-servletartifactId>
        dependency>
        
        <dependency>
            
            <groupId>hand.candygroupId>
            
            <artifactId>gk-rpc-protoartifactId>
            
            <version>${project.version}version>
        dependency>
    dependencies>

2.5.2 客户端网络传输接口

客户端主要作用

  • 1 创建连接
  • 2 发送数据 并且等待响应
  • 3 关闭连接
package com.smgeek.gkrpc.transport;

import com.smgeek.gkrpc.Peer;

import java.io.InputStream;

/**
* 1 创建连接
* 2 发送数据 并且等待响应
* 3 关闭连接
*/

public interface TransportClient {
    
    void connect(Peer peer);
    
    InputStream write(InputStream data);
    
    void close();
}

2.5.3 服务端网络传输接口

服务端主要作用

  • 1 启动 监听端口
  • 2 接受请求
  • 3 关闭监听
package com.smgeek.gkrpc.transport;


/**
* 1 启动 监听端口
* 2 接受请求
* 3 关闭监听
*/
public interface TransportServer {
    void init(int port,RequestHandler handler);
    void start();
    void stop();
}

2.5.4 请求处理类接口

package com.smgeek.gkrpc.transport;

import java.io.InputStream;
import java.io.OutputStream;

/**
* 处理网络请求的handler
*/
public interface RequestHandler {
    void onRequest(InputStream recive, OutputStream toResp);
}

2.5.5 接口实现

package com.smgeek.gkrpc.transport;

import com.smgeek.gkrpc.Peer;
import org.apache.commons.io.IOUtils;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;


/**
 * HTTP 客户端
 */
public class HTTPTransportClient implements TransportClient {

    private String url;

    @Override
    public void connect(Peer peer) {
        this.url="http://"+peer.getHost()+":"+peer.getPort();
    }

    @Override
    public InputStream write(InputStream data) {
        try {
            HttpURLConnection httpConn =(HttpURLConnection)new URL(url).openConnection();
            httpConn.setDoOutput(true);
            httpConn.setDoInput(true);
            httpConn.setRequestMethod("POST");
            httpConn.connect();
            IOUtils.copy(data,httpConn.getOutputStream());
            int resultCode=httpConn.getResponseCode();

            if(resultCode==HttpURLConnection.HTTP_OK){
                return httpConn.getInputStream();
            }else{
                return  httpConn.getErrorStream();
            }

        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

    @Override
    public void close() {

    }
}

package com.smgeek.gkrpc.transport;


import lombok.extern.slf4j.Slf4j;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * HTTP 服务端
 */
@Slf4j
public class HTTPTransportServer implements TransportServer{

    private RequestHandler handler;
    private Server server;


    @Override
    public void init(int port, RequestHandler handler) {
        this.handler=handler;
        this.server=new Server(port);

        //servlet 接收请求
        ServletContextHandler ctx=new ServletContextHandler();
        server.setHandler(ctx);

        ServletHolder holder=new ServletHolder(new ResquestServlet());
        ctx.addServlet(holder,"/*");


    }

    @Override
    public void start() {
        try {
            server.start();
            server.join();
        } catch (Exception e) {
            log.error(e.getMessage(),e);
        }

    }

    @Override
    public void stop() {
        try {
            server.stop();
        } catch (Exception e) {
            log.error(e.getMessage(),e);
        }
    }


    class  ResquestServlet extends HttpServlet{

        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws  IOException {
            log.info("client connect");

            InputStream in=req.getInputStream();
            OutputStream out=resp.getOutputStream();

            if(handler!=null){
                handler.onRequest(in,out);
            }

            out.flush();
        }
    }
}

2.6 服务端模块

2.6.1 依赖引入

<dependencies>
  <dependency>
    <groupId>hand.candygroupId>
    <artifactId>gk-rpc-protoartifactId>
    <version>1.0-SNAPSHOTversion>
  dependency>
  <dependency>
    <groupId>hand.candygroupId>
    <artifactId>gk-rpc-codecartifactId>
    <version>1.0-SNAPSHOTversion>
  dependency>
  <dependency>
    <groupId>hand.candygroupId>
    <artifactId>gk-rpc-transportartifactId>
    <version>1.0-SNAPSHOTversion>
  dependency>
  <dependency>
    <groupId>hand.candygroupId>
    <artifactId>gk-rpc-commonartifactId>
    <version>1.0-SNAPSHOTversion>
  dependency>
  <dependency>
    <groupId>commons-iogroupId>
    <artifactId>commons-ioartifactId>
  dependency>
 dependencies>

2.6.2 重写ServiceDescriptor

package com.smgeek.gkrpc;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;

/**
 * 表示服务
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ServiceDescriptor {
    private String clazz;
    private String method;
    private String returnType;
    private String[] parameterTypes;

    public static  ServiceDescriptor from(Class clazz, Method method){
        ServiceDescriptor sdp=new ServiceDescriptor();
        sdp.setClazz(clazz.getName());
        sdp.setMethod(method.getName());
        sdp.setReturnType(method.getReturnType().getName());
        Class[] parameterClasses=method.getParameterTypes();
        String[] parameterTypes=new String[parameterClasses.length];
        for(int i=0;i<parameterClasses.length;i++){
            parameterTypes[i]=parameterClasses[i].getName();
        }
        sdp.setParameterTypes(parameterTypes);
        return sdp;
    }

    @Override
    public String toString() {
        return "ServiceDescriptor{" +
                "clazz='" + clazz + '\'' +
                ", method='" + method + '\'' +
                ", returnType='" + returnType + '\'' +
                ", parameterTypes=" + Arrays.toString(parameterTypes) +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ServiceDescriptor that = (ServiceDescriptor) o;
        return Objects.equals(clazz, that.clazz) &&
                Objects.equals(method, that.method) &&
                Objects.equals(returnType, that.returnType) &&
                Arrays.equals(parameterTypes, that.parameterTypes);
    }

    @Override
    public int hashCode() {
        return toString().hashCode();
    }
}

2.6.3 写服务端配置

package com.smgeek.gkrpc.server;


import com.smgeek.gkrpc.codec.Decoder;
import com.smgeek.gkrpc.codec.Encoder;
import com.smgeek.gkrpc.codec.JSONEncoder;
import com.smgeek.gkrpc.transport.HTTPTransportServer;
import com.smgeek.gkrpc.transport.TransportServer;
import lombok.Data;

/**
 * 配置
 */
@Data
public class RpcServerConfig {

    private Class<? extends TransportServer> transportClass = HTTPTransportServer.class;
    private  Class<? extends Encoder> encoderClass= JSONEncoder.class;
    private  Class<? extends Decoder> decoderClass=Decoder.class;
    private  int port=3000;

}

2.6.4 写服务实例

package com.smgeek.gkrpc.server;


import lombok.AllArgsConstructor;
import lombok.Data;

import java.lang.reflect.Method;

/**
 * 表示一个具体服务
 */
@Data
@AllArgsConstructor
public class ServiceInstance {

    private  Object target;
    private Method method;

}

2.6.5 写服务管理

package com.smgeek.gkrpc.server;


import com.smgeek.gkrpc.Request;
import com.smgeek.gkrpc.ServiceDescriptor;
import com.smgeek.gkrpc.common.utils.ReflectionUtils;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 管理rpc暴漏的服务
 */
@Slf4j
public class ServiceManager {

    private Map<ServiceDescriptor ,ServiceInstance> sercices;

    public ServiceManager(){
        this.sercices=new ConcurrentHashMap<>();
    }

    public <T> void register(Class<T> interfaceClass,T bean ){
        Method[] methods = ReflectionUtils.getPublicMethods(interfaceClass);

        for(Method method:methods){
            ServiceInstance sis=new ServiceInstance(bean,method);
            ServiceDescriptor sdp=ServiceDescriptor.from(interfaceClass,method);
            sercices.put(sdp,sis);
            log.info("register service: {} {}",sdp.getClazz(),sdp.getMethod());
        }
    }

    public ServiceInstance lookup(Request request){
        ServiceDescriptor sdp=request.getService();
        return sercices.get(sdp);
    }

}

2.6.6 注册与发现测试

package com.smgeek.gkrpc.server;

public interface TestInterface {
    void hello();
}

package com.smgeek.gkrpc.server;

public class TestClass implements TestInterface {
    @Override
    public void hello() {

    }
}

package com.smgeek.gkrpc.server;

import com.smgeek.gkrpc.Request;
import com.smgeek.gkrpc.ServiceDescriptor;
import com.smgeek.gkrpc.common.utils.ReflectionUtils;
import org.junit.Before;
import org.junit.Test;

import java.lang.reflect.Method;

import static org.junit.Assert.assertNotNull;

public class ServiceManagerTest {
    
    ServiceManager sm;

    @Before
    public void init(){
        sm=new ServiceManager();
        TestInterface bean=new TestClass();
        sm.register(TestInterface.class,bean);
    }

    @Test
    public void register(){
        TestInterface bean=new TestClass();
        sm.register(TestInterface.class,bean);
    }
    
    @Test
    public void lookup(){
        Method method = ReflectionUtils.getPublicMethods(TestInterface.class)[0];
        ServiceDescriptor sdp=ServiceDescriptor.from(TestInterface.class,method);
        Request request=new Request();
        request.setService(sdp);
        ServiceInstance sis = sm.lookup(request);
        assertNotNull(sis);

    }
    
}

2.6.7 RPC Server代码实现

package com.smgeek.gkrpc.server;

import com.smgeek.gkrpc.Request;
import com.smgeek.gkrpc.Response;
import com.smgeek.gkrpc.ServiceDescriptor;
import com.smgeek.gkrpc.codec.Decoder;
import com.smgeek.gkrpc.codec.Encoder;
import com.smgeek.gkrpc.common.utils.ReflectionUtils;
import com.smgeek.gkrpc.transport.RequestHandler;
import com.smgeek.gkrpc.transport.TransportServer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;

/**
 *
 * @author CandyDingDing
 * @version 1.1.0
 * @date 2022/4/3
 */
@Slf4j
public class RpcServer {
    private RpcServerConfig config;
    private TransportServer net;
    private Encoder encoder;
    private Decoder decoder;
    private ServiceManager serviceManager;
    private ServiceInvoker serviceInvoker;

    public RpcServer(RpcServerConfig config) {
        this.config = config;
        this.net = ReflectionUtils.newInstance(config.getTransportClass());
        this.net.init(config.getPort(),handler);
        this.encoder = ReflectionUtils.newInstance(config.getEncoderClass());;
        this.decoder = ReflectionUtils.newInstance(config.getDecoderClass());
        this.serviceManager = new ServiceManager();
        this.serviceInvoker = new ServiceInvoker();
    }

    public <T> void register(Class<T> interfaceClass,T bean ){
        serviceManager.register(interfaceClass,bean);
    }

    public void start(){
        this.net.start();;
    }

    public void stop(){
        this.net.stop();
    }

    private RequestHandler handler= new RequestHandler() {
        @Override
        public void onRequest(InputStream recive, OutputStream toResp) {
            Response resp=new Response();

            try {
                byte[] inBytes= IOUtils.readFully(recive,recive.available());
                Request request=decoder.decode(inBytes,Request.class);
                log.info("get request:{}",request);

                ServiceInstance sis=serviceManager.lookup(request);
                Object ret=serviceInvoker.invoke(sis,request);
                resp.setData(ret);

            } catch (Exception e) {
                log.warn(e.getMessage(),e);
                resp.setCode(1);
                resp.setMessage("RescServer got error:"+e.getClass().getName()+":"+e.getMessage());
            }finally {
                try {
                    byte[] outBytes=encoder.encode(resp);
                    toResp.write(outBytes);
                } catch (IOException e) {
                   log.warn(e.getMessage(),e);
                }

            }
        }
    };
}

2.7 客户端模块

2.7.1 依赖引入

    <dependencies>
        <dependency>
            <groupId>hand.candygroupId>
            <artifactId>gk-rpc-protoartifactId>
            <version>${project.version}version>
        dependency>
        <dependency>
            <groupId>hand.candygroupId>
            <artifactId>gk-rpc-codecartifactId>
            <version>${project.version}version>
        dependency>
        <dependency>
            <groupId>hand.candygroupId>
            <artifactId>gk-rpc-transportartifactId>
            <version>${project.version}version>
        dependency>
        <dependency>
            <groupId>hand.candygroupId>
            <artifactId>gk-rpc-commonartifactId>
            <version>${project.version}version>
        dependency>
        <dependency>
            <groupId>commons-iogroupId>
            <artifactId>commons-ioartifactId>
        dependency>
    dependencies>

2.7.2 网络连接接口以及实现

package com.smgeek.gkrpc.client;

import com.smgeek.gkrpc.Peer;
import com.smgeek.gkrpc.transport.TransportClient;

import java.util.List;

/**
* @Describe 选择哪个Server来服务
* @Author CandyDingDing
* @Version 1.0
* @Motto 且视他人之疑目如盏盏鬼火,大胆地去走吾之夜路
* @Date 2022/4/3
*/
public interface TransportSelector {
    
    /**
    * 初始化selector
    * @param peers 可以链接的server端点信息
    * @param count client和server的建立多少连接
    * @param clazz client实现的class
    */
    void init(List<Peer> peers, int count,Class<? extends TransportClient> clazz);
    
    /**
    * 选择一个transport 与 server交互
    *
    * @return 网络client
    */
    TransportClient select();
    
    
    /**
    * 释放用完的client
    * @param client TransportClient
    */
    void release(TransportClient client);
    
    void close();
    
    
}

package com.smgeek.gkrpc.client;

import com.smgeek.gkrpc.Peer;
import com.smgeek.gkrpc.common.utils.ReflectionUtils;
import com.smgeek.gkrpc.transport.TransportClient;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;


@Slf4j
public class RandomTransportSelector implements TransportSelector {


    /**
     * 存储连介绍的客户端
     */
    private  List<TransportClient> clients;

    public RandomTransportSelector() {
        clients=new ArrayList<>();
    }

    @Override
    public synchronized void init(List<Peer> peers, int count, Class<? extends TransportClient> clazz) {
        count=Math.max(count,1);

        for(Peer peer:peers){
            for(int i=0;i<count;i++){
                TransportClient client= ReflectionUtils.newInstance(clazz);
                client.connect(peer);
                clients.add(client);
                log.info("connect server:{} ",peer);
            }
        }
    }

    @Override
    public synchronized TransportClient select() {
        int i=new Random().nextInt(clients.size());
        return  clients.remove(i);
    }

    @Override
    public synchronized void release(TransportClient client) {
        clients.add(client);
    }

    @Override
    public synchronized void close() {
        for(TransportClient client:clients){
            client.close();
        }
        clients.clear();
    }
}

2.7.3 配置类

package com.smgeek.gkrpc.client;


import com.smgeek.gkrpc.Peer;
import com.smgeek.gkrpc.codec.Decoder;
import com.smgeek.gkrpc.codec.Encoder;
import com.smgeek.gkrpc.codec.JSONDecoder;
import com.smgeek.gkrpc.codec.JSONEncoder;
import com.smgeek.gkrpc.transport.HTTPTransportClient;
import com.smgeek.gkrpc.transport.TransportClient;
import lombok.Data;

import java.util.Arrays;
import java.util.List;

/**
 * @Author CandyDingDing
 * @Version 1.0
 * @Motto 且视他人之疑目如盏盏鬼火,大胆地去走吾之夜路
 * @Describe
 * @Date 2022/4/4
 */
@Data
public class RpcClientConfig {

    private Class<? extends TransportClient> transportClass= HTTPTransportClient.class;

    private  Class<? extends Encoder> encoderClass =JSONEncoder.class;

    private  Class<? extends Decoder> decoderClass = JSONDecoder.class;

    private Class<? extends TransportSelector> selectorClass=RandomTransportSelector.class;

    private  int connectCount=1;

    private List<Peer> severs=Arrays.asList(new Peer("127.0.0.1",3000));

}

2.7.4 Rpc客户端

package com.smgeek.gkrpc.client;


import com.smgeek.gkrpc.codec.Decoder;
import com.smgeek.gkrpc.codec.Encoder;
import com.smgeek.gkrpc.common.utils.ReflectionUtils;

import java.lang.reflect.Proxy;

/**
 * @Author CandyDingDing
 * @Version 1.0
 * @Motto 且视他人之疑目如盏盏鬼火,大胆地去走吾之夜路
 * @Describe
 * @Date 2022/4/4
 */
public class RpcClient {

    private  RpcClientConfig config;
    private Encoder encoder;
    private Decoder decoder;
    private  TransportSelector selector;

    public RpcClient() {

    }

    public RpcClient(RpcClientConfig config) {
        this.config = config;
        this.encoder= ReflectionUtils.newInstance(this.config.getEncoderClass());
        this.decoder= ReflectionUtils.newInstance(this.config.getDecoderClass());
        this.selector= ReflectionUtils.newInstance(this.config.getSelectorClass());
        this.selector.init(this.config.getSevers(),this.config.getConnectCount(),this.config.getTransportClass());
    }

    public <T> T getProxy(Class<T> clazz){
        return (T) Proxy.newProxyInstance(
                getClass().getClassLoader(),
                new Class[]{clazz},
                new RemoteInvoker(clazz,encoder,decoder,selector)
                );
    }
}

package com.smgeek.gkrpc.client;

import com.smgeek.gkrpc.Request;
import com.smgeek.gkrpc.Response;
import com.smgeek.gkrpc.ServiceDescriptor;
import com.smgeek.gkrpc.codec.Decoder;
import com.smgeek.gkrpc.codec.Encoder;
import com.smgeek.gkrpc.transport.TransportClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;


/**
 * @Author CandyDingDing
 * @Version 1.0
 * @Motto 且视他人之疑目如盏盏鬼火,大胆地去走吾之夜路
 * @Describe 调用远程服务的代理类
 * @Date 2022/4/4
 */
@Slf4j
public class RemoteInvoker implements InvocationHandler {

    private Class clazz;
    private Encoder encoder;
    private Decoder decoder;
    private  TransportSelector selector;

    public  RemoteInvoker(Class clazz, Encoder encoder, Decoder decoder,TransportSelector selector){
        this.decoder=decoder;
        this.encoder=encoder;
        this.selector=selector;
        this.clazz=clazz;
    };

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Request request=new Request();
        request.setService(ServiceDescriptor.from(clazz,method));
        request.setParameters(args);

        Response resp = invokeRemote(request);
        if(resp==null || resp.getCode()!=0){
            throw  new IllegalStateException("fail to invoke remotr"+resp);
        }

        return resp.getData();
    }

    private Response invokeRemote(Request request) {
        TransportClient client=null;
        Response resp=null;
        try{
            client=selector.select();
            byte[] outBytes=encoder.encode(request);
            InputStream revice = client.write(new ByteArrayInputStream(outBytes));

            byte[]  inBytes=IOUtils.readFully(revice,revice.available());
            resp=decoder.decode(inBytes,Response.class);


        }catch (Exception e){
            log.warn(e.getMessage(),e);
            resp=new Response();
            resp.setCode(1);
            resp.setMessage("RpcClient got error :"+e.getClass()+":"+e.getMessage());

        } finally {
            if(client!=null){
                selector.release(client);
            }

        }


        return  resp;
    }
}

2.8 RPC使用

2.8.1 新建模块example

手写RPC学习笔记_第14张图片

2.8.2 引入依赖

  <dependencies>
        <dependency>
            <groupId>hand.candygroupId>
            <artifactId>gk-rpc-clientartifactId>
            <version>${project.version}version>
        dependency>
        <dependency>
            <groupId>hand.candygroupId>
            <artifactId>gk-rpc-serverartifactId>
            <version>${project.version}version>
        dependency>
    dependencies>

2.8.3 Client

package single.rpc.example;

import single.rpc.client.RpcClient;

/**
 * @Author CandyDingDing
 * @Version 1.0
 * @Motto 且视他人之疑目如盏盏鬼火,大胆地去走吾之夜路
 * @Describe 
 * @Date 2022/4/4
 */
public class Client {
    public static void main(String[] args) {
        RpcClient client = new RpcClient();
        CalcService service = client.getProxy(CalcService.class);

        int add = service.add(1, 2);
        int minus = service.minus(1, 2);
        System.out.println(add);
        System.out.println(minus);
    }
}

2.8.4 Server

package single.rpc.example;

import single.rpc.server.RpcServer;
import single.rpc.server.RpcServerConfig;


/**
 * @Author CandyDingDing
 * @Version 1.0
 * @Motto 且视他人之疑目如盏盏鬼火,大胆地去走吾之夜路
 * @Describe 
 * @Date 2022/4/4
 */
public class Server {
    public static void main(String[] args) {
        RpcServer server = new RpcServer(new RpcServerConfig());
        server.register(CalcService.class, new CalcServiceImpl());
        server.start();
    }
}

2.8.5 CascService

package single.rpc.example;

/**
 * @Author CandyDingDing
 * @Version 1.0
 * @Motto 且视他人之疑目如盏盏鬼火,大胆地去走吾之夜路
 * @Describe 
 * @Date 2022/4/4
 */
public interface CalcService {
    int add(int a, int b);
    int minus(int a, int b);
}

2.8.6 CalcServiceImpl

package single.rpc.example;

/**
 * @Author CandyDingDing
 * @Version 1.0
 * @Motto 且视他人之疑目如盏盏鬼火,大胆地去走吾之夜路
 * @Describe  git镜像:https://hub.fastgit.xyz/
 * @Date 2022/4/4
 */
public class CalcServiceImpl implements CalcService {
    @Override
    public int add(int a, int b) {
        return a + b;
    }

    @Override
    public int minus(int a, int b) {
        return a - b;
    }
}

2.8.7 运行结果

视频课里面的代码有问题:

修正后的代码可以参考https://https://gitee.com/candydingding/rpc

先启动server 再启动client

server结果

手写RPC学习笔记_第15张图片
client结果

手写RPC学习笔记_第16张图片

三、参考

视频连接:https://www.imooc.com/video/20219
文档笔记:https://www.yuque.com/lililil-9bxsv/kb/tg9xha
代码地址:https://gitee.com/candydingding/rpc

你可能感兴趣的:(rpc,java)