一个简单的rpc框架实现(待连载优化)

本文详细介绍了如何使用Socket实现一个简易的RPC框架,包括通信协议设计、客户端与服务端实现、动态代理与Spring集成等关键步骤。通过实例演示了从本地服务升级到远程服务的实现过程,以及如何利用Socket进行远程调用。该框架适用于需要跨进程通信的场景,简化了服务间的交互。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

系统长大后为了保证可维护性,拆分系统是一个比较常见的解决方案。系统拆分后,原来的接口直接调用方法不再可行,需要被替换成为远程调用过程。远程调用可以直接使用http协议post 一个请求到服务提供端,然后服务提供端返回一个结果给调用者。这种方案将原本数据service层的DO操作过程上升成为了web服务,我个人并不反感。第二种方案就是使用rmi 实现,但是rmi client 和server的地址耦合到一起,一旦server更换地址client端需要同步修改。 第三种方案是直接读对方的数据库,当然便利性和可维护性更差,需要把server 端的底层DAO业务冗余到client。 最后一种就是rpc 远程调用框架。

rpc框架需要很多组件:调用者,提供者,服务注册中心, 通信总线 和通信协议。 其中每一个组件都有很多技术点要谈。本文在这里只讨论一个最简单的原型:简单分隔符的通信协议,使用socket 实现通信总线。 CS直接耦合绑定在一起,后期再考虑注册中心和服务暴漏的问题。 Socket通信方式也采用最简单的直连接方式,不使用nio, 也不维护连接池。


首先先看一个简单场景

打印接口:

public interface PrintText {
    String print(String text);
}

接口实现:

public class SystemPrint implements PrintText {
    public String print(String text) {
        System.out.println(text);
        return "系统已打印:" + text;
    }
}

业务调用方:

public class SpringClient {

    public static void main(String[] args){
        BeanFactory apx = new ClassPathXmlApplicationContext("classpath:spring-config.xml");
        PrintText pt = (PrintText) apx.getBean("printText");
        System.out.println(pt.print("springClient"));
    }
}

其中图1能够很好的描述这个场景, 虚线A表示业务方并不知道实现者是谁,只知道接口的存在。

简单场景
图1


现在业务系统升级了,负责打印的bean复杂到独立成了一个系统,要从业务系统中剥离出去,该怎么办?我们选择使用socket实现远程调用。 由于控制反转的设计,业务方几乎不需要改动任何代码就能实现升级。远程调用方案如图2所示:

这里写图片描述
图2

接口PrintText的实现被代理成为socket client, 将调用请求通过socket发送出去。 服务提供者接受到请求后,解析完毕,调用具体的实现类SystemPrintText, 然后将返回值发送回socket client。 最后返回给业务方。


下面详细讲一下各个组件的实现方案:

 1. 通讯协议:使用|##|隔开字段(并不推荐这种方式,后面系列进一步讲协议的设计)

   协议设计:
   version:版本号|##|cypher:加密串|##| interfaceName:接口bean名称|##| interface:接口全路径|##|method:调用方法|##|params:参数1的类+参数1对象,单数2类+参数2对象,

   Demo:   `version:1.0.0|##|cypher:default|##|interfaceName:proxyPrintText|##|interface:com.tmall.beans.PrintText|##|method:print|##|params:java.lang.String+演示远程调用`


  2.Client客户端是一个动态代理,截获业务方的接口调用后将调用参数组装成为上述协议,发送给服务端,并将服务端的返回结果返回给业务方。代码实现如下:
public class SocketConsumerProxy implements InvocationHandler {
    private Object target;

    private RemoteDataSource dataSource;

    public SocketConsumerProxy(RemoteDataSource dataSource){
        this.dataSource = dataSource;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        StringBuffer stream = this.buildRpcRequest(proxy, method, args);

        Object object = null;
        try{
            long startTime =  System.currentTimeMillis();
            System.out.println("New rpc client send " + stream.toString() + "  time:" + startTime);
            // socket connect
            Socket socket=new Socket(dataSource.getIp(), dataSource.getPort());

            // request
            PrintWriter os = new PrintWriter(socket.getOutputStream());
            os.println(stream.toString());
            os.flush();

            // read response
            BufferedReader br = new BufferedReader(
                    new InputStreamReader(socket.getInputStream()));
            object = br.readLine();

            long endTime =  System.currentTimeMillis();
            System.out.println("client read from service:" + object + "  time:" + (endTime - startTime));
        }catch (IOException e){
            e.printStackTrace();
            object = e.toString();
        }

        return object;
    }

    private StringBuffer buildRpcRequest(Object proxy, Method method, Object[] args) {
        StringBuffer buffer = new StringBuffer();
        buffer.append(String.format("version:%s|##|cypher:%s|##|interfaceName:%s|##|interface:%s|##|method:%s|##|params:"
          , dataSource.getVersion(), dataSource.getCypher(), dataSource.getInterfaceName(), dataSource.getInterfaces(), method.getName()));

        for (Object obj : args){
            buffer.append(obj.getClass().getName() + "+" + obj.toString() + ",");
        }
        return buffer;
    }
}

对于动态代理InvocationHander的介绍这里不再论述。

 3.服务端实现:服务端监听端口,获取客户端访问。 校验数据有效性:是否为空,加密参数,版本号,调用接口,调用方法。 然后通过spring容器找到最终的实现bean,通过反射的方式调用对应方法。 最后将返回值发送给客户端。代码如下:
public class SocketProvider{
    private RemoteDataSource dataSource;

    public SocketProvider(RemoteDataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Object provide() throws Throwable {
        ServerSocket serverSocket = new ServerSocket(dataSource.getPort());
        while (true) {
            Socket socket = null;
            try {
                //接收客户连接,只要客户进行了连接,就会触发accept();从而建立连接
                socket = serverSocket.accept();
                this.getRpcRequest(socket);

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void getRpcRequest(Socket socket) {
        try {
            System.out.println("rpc client accepted " + socket.getInetAddress() + ":" + socket.getPort() + "   time:" + System.currentTimeMillis());
            // 接收服务器的反馈
            BufferedReader br = new BufferedReader(
                    new InputStreamReader(socket.getInputStream()));

            String msg = br.readLine();

            System.out.println("读到远程调用请求:" + msg);
            Object obj  = this.parseRpcClientRequest(msg);

            // 接收服务器的反馈
            PrintWriter os = new PrintWriter(socket.getOutputStream());
            os.println(obj.toString());
            os.flush();

            System.out.println("rpc server return " + obj + ":" + socket.getPort() + "   time:" + System.currentTimeMillis());
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public Object parseRpcClientRequest(String msg) {
        Object result = "";
        if (StringUtils.isEmpty(msg)){
            return result;
        }

        String[] infos = msg.split("\\|##\\|");
        Map<String, String> infoMap = new HashMap<String, String>();
        for(String info : infos){
            String[] pair = info.split(":");
            infoMap.put(pair[0], pair[1]);
        }
        if (infoMap.isEmpty()){
            return "无调用参数";
        }

        if (!dataSource.getCypher().equals(infoMap.get("cypher"))){
            return "加密串不对";
        }

        if (!dataSource.getVersion().equals(infoMap.get("version"))){
            return "服务版本号不对";
        }

        String interfaces = infoMap.get("interface");
        String interfaceName = infoMap.get("interfaceName");
        String methodName = infoMap.get("method");
        String params = infoMap.get("params");

        if(StringUtils.isEmpty(interfaces) || StringUtils.isEmpty(methodName) || StringUtils.isEmpty(interfaceName)){
            return "无调用接口或者方法 或者接口beanName";
        }

        // bean
        Object obj = RemoteDataSource.beanFactory.getBean(interfaceName);
        if(obj == null){
            return "未找到对应的服务";
        }

        // bean 和 interface对应关系
        boolean isInterfaceRight = false;
        Class<?>[] clazzArray = obj.getClass().getInterfaces();
        for (Class<?> clazz : clazzArray){
            if (clazz.getName().equals(interfaces)){
                isInterfaceRight = true;
                break;
            }
        }
        if (isInterfaceRight == false){
            return "错误的bean Name 和 interface对应关系";
        }


        // 参数对应的类和对象
        String[] paramsArray = params.split(",");
        Class<?>[] paramsClazzArray = new Class<?>[paramsArray.length];
        Object[] paramObjArray = new Object[paramsArray.length];
        try{
            for (int i = 0; i < paramsArray.length; i ++){
                String paramInfo = paramsArray[i];
                if (StringUtils.isEmpty(paramInfo)) {  // 过滤掉多余的逗号
                    continue;
                }

                String[] paramsInfos = paramInfo.split("\\+");

                Class c = Class.forName(paramsInfos[0]);
                paramsClazzArray[i] = c;
                paramObjArray[i] = paramsInfos[1];
            }
        }catch (ClassNotFoundException e){
            e.printStackTrace();
            return "未找到参数对应的类名:" +e.toString();
        }


        try {
            Method method = obj.getClass().getMethod(methodName, paramsClazzArray);
            result = method.invoke(obj, paramObjArray);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            return "未找到参数对应的方法名称:" +e.toString();
        }
        catch (IllegalAccessException e) {
            e.printStackTrace();
            return "无效的调用:" +e.toString();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
            return "无效的目标地址:" +e.toString();
        }

        result = "我是服务器代理aop, result=" + result;
        return result;
    }

    public static void main(String[] args) throws ClassNotFoundException {
        RemoteDataSource dataSource = new RemoteDataSource();
        dataSource.setInterfaceName("caishengProxyPrintText");
        dataSource.setInterfaces("com.tmall.beans.PrintText");
        dataSource.setVersion("1.0.0");

        SocketProvider provider = new SocketProvider(dataSource);

        StringBuffer buffer = new StringBuffer();
        buffer.append(String.format("version:%s|##|cypher:%s|##|interfaceName:%s|##|interface:%s|##|method:%s|##|params:"
                , dataSource.getVersion(), dataSource.getCypher(), dataSource.getInterfaceName(), dataSource.getInterfaces(), "print"));
        String[] params = new String[1];
        params[0] = "caishengRemoteTest";
        for (Object obj : params){
            buffer.append(obj.getClass().getName() + "+" + obj.toString() + ",");
        }

        Object result = provider.parseRpcClientRequest(buffer.toString());

        System.out.println(result);
    }
}
4.代理通过spring的FactoryBean实现,通过getObject方法能够中转任何接口的调用请求。具体细节请参考spring AOP和动态代理实现方案。Demo版本就非常简单,就是将业务的请求发送给2中的动态代理。 代码如下:
public class CaishengRemoteSimpleConsumerBean implements FactoryBean, InitializingBean{
    private RemoteDataSource remoteDataSource = new RemoteDataSource();  // 远程数据源


    public void afterPropertiesSet() throws Exception {
        remoteDataSource.init();
    }

    public Object getObject() throws Exception {
        return remoteDataSource.getClientObject();
    }

    public Class<?> getObjectType() {
        return remoteDataSource.getClientObject().getClass();
    }

    public boolean isSingleton() {
        return false;
    }

    public void setInterfaces(String interfaces){
        remoteDataSource.setInterfaces(interfaces);
    }

    public void setInterfaceName(String interfaces){
        remoteDataSource.setInterfaceName(interfaces);
    }

    public void setVersion(String version){
        remoteDataSource.setVersion(version);
    }

    public void setRemoteDataSource(RemoteDataSource remoteDataSource){
        this.remoteDataSource = remoteDataSource;
    }
}
 5. RemoteDataSource是远程数据源,耦合了客户端和服务端的ip地址、加密信息以及版本号。代码如下:
public class RemoteDataSource {
    private String ip = "127.0.0.1";
    private int port =  9090;              // 端口
    private String interfaces;             // 接口全路径
    private String interfaceName;          // 接口名
    private String version;
    private String cypher ="default";       // 密码
    private Class<?> interfaceClass;       // 类文件

    private SocketRemotionFactory remotionFactory;

    public void init(){
        remotionFactory = new SocketRemotionFactory(this);

        try {
            interfaceClass = Class.forName(interfaces);
        } catch (ClassNotFoundException e) {

        }
    }

    public Object getClientObject(){
        return remotionFactory.getRemoteClientProxy();
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public String getInterfaces() {
        return interfaces;
    }

    public void setInterfaces(String interfaces) {
        this.interfaces = interfaces;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getCypher() {
        return cypher;
    }

    public void setCypher(String cypher) {
        this.cypher = cypher;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public String getInterfaceName() {
        return interfaceName;
    }

    public void setInterfaceName(String interfaceName) {
        this.interfaceName = interfaceName;
    }

    public Class<?> getInterfaceClass() {
        return interfaceClass;
    }

    public void setInterfaceClass(Class<?> interfaceClass) {
        this.interfaceClass = interfaceClass;
    }
}
 6. remotionFactory是一个简单工厂,生成以PrintText为接口,2中的SocketConsumerProxy的为实现的代理对象给业务方。具体代码如下:
public class SocketRemotionFactory {
    private RemoteDataSource dataSource;
    public SocketRemotionFactory(RemoteDataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Object getRemoteClientProxy(){

        Object result = null;

        Class<?> clazz = dataSource.getInterfaceClass();
        if (clazz == null){
            return "错误的client 代理,无对应的class";
        }

        Class<?>[] clazzArray = new Class[1];
        clazzArray[0] = clazz;
        try{
            result = Proxy.newProxyInstance(clazz.getClassLoader(), clazzArray, new SocketConsumerProxy(dataSource));
        }catch (Exception e){
            e.printStackTrace();
        }

        return result;
    }
}
7.  服务端需要在spring容器加载bean时提供服务,代码如下:
public class CaishengRemoteSimpleProviderBean implements FactoryBean, InitializingBean{
    private RemoteDataSource remoteDataSource = new RemoteDataSource();  // 远程数据源

    private SocketProvider provider;

    public void init() throws Exception {
        this.afterPropertiesSet();
    }

    public void afterPropertiesSet() throws Exception {
        remoteDataSource.init();

        provider = new SocketProvider(remoteDataSource);
    }

    public Object getObject() throws Exception {
        try {
            provider.provide();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return null;
    }

    public Class<?> getObjectType() {
        return null;
    }

    public boolean isSingleton() {
        return false;
    }

    public void setInterfaces(String interfaces){
        remoteDataSource.setInterfaces(interfaces);
    }

    public void setInterfaceName(String interfaces){
        remoteDataSource.setInterfaceName(interfaces);
    }

    public void setVersion(String version){
        remoteDataSource.setVersion(version);
    }

    public void setRemoteDataSource(RemoteDataSource remoteDataSource){
        this.remoteDataSource = remoteDataSource;
    }
}
8.  Spring中bean配置为:

客户端:

<bean id="caishengSimpleRemoteProxyPrintText" class="com.tmall.proxy.remotesimple.CaishengRemoteSimpleConsumerBean">
        <property name="interfaces">
            <value>com.tmall.beans.PrintText</value>
        </property>
        <property name="interfaceName">
            <value>proxyPrintText</value>
        </property>
        <property name="version">
            <value>1.0.0</value>
        </property>
</bean>

服务端:

<bean id="printText" class="com.tmall.beans.impl.SystemPrint"></bean>

<bean id="caishengSimpleRemoteProvider" class="com.tmall.proxy.remotesimple.CaishengRemoteSimpleProviderBean" init-method="init">
        <property name="interfaces">
            <value>com.tmall.beans.PrintText</value>
        </property>
        <property name="interfaceName">
            <value>proxyPrintText</value>
        </property>
        <property name="version">
            <value>1.0.0</value>
        </property>
</bean>
9.  业务方代码:
public class Client {
    public static void main(String[] args){
        BeanFactory apx = new ClassPathXmlApplicationContext("classpath:spring-config.xml");
        PrintText pt = (PrintText) apx.getBean("caishengSimpleRemoteProxyPrintText");
        System.out.println(pt.print("远程调用演示"));
    }
}
好了,一个简单的rpc框架就实现完毕。 一起看一下调用效果吧:

服务方未启动:socket 调用异常

New rpc client send version:1.0.0|##|cypher:default|##|interfaceName:proxyPrintText|##|interface:com.tmall.beans.PrintText|##|method:print|##|params:java.lang.String+远程调用演示,  time:1464108758488
java.net.ConnectException: Connection refused: connect
java.net.ConnectException: Connection refused: connect
    at java.net.DualStackPlainSocketImpl.connect0(Native Method)
    at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:69)
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339)
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200)
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182)
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:157)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:391)
    at java.net.Socket.connect(Socket.java:579)
    at java.net.Socket.connect(Socket.java:528)
    at java.net.Socket.<init>(Socket.java:425)
    at java.net.Socket.<init>(Socket.java:208)
    at com.tmall.proxy.remotesimple.socket.SocketConsumerProxy.invoke(SocketConsumerProxy.java:33)
    at com.sun.proxy.$Proxy0.print(Unknown Source)
    at com.tmall.client.simpleRemote.Client.main(Client.java:16)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)

Process finished with exit code 0

服务方启动:

rpc client accepted /127.0.0.1:54730   time:1464108803359
读到远程调用请求:version:1.0.0|##|cypher:default|##|interfaceName:proxyPrintText|##|interface:com.tmall.beans.PrintText|##|method:print|##|params:java.lang.String+远程调用演示,
远程调用演示
rpc server return 我是服务器代理aop, result=系统已打印:远程调用演示:54730   time:1464108803380

服务方日志:

New rpc client send version:1.0.0|##|cypher:default|##|interfaceName:proxyPrintText|##|interface:com.tmall.beans.PrintText|##|method:print|##|params:java.lang.String+远程调用演示,  time:1464108803353
client read from service:我是服务器代理aop, result=系统已打印:远程调用演示  time:27
我是服务器代理aop, result=系统已打印:远程调用演示

Process finished with exit code 0

客户端日志:用时38ms, 很多地方需要优化。

介绍RCP的实现原理 目录 1. 前言 2 2. 基本概念 3 2.1. IDL 3 2.2. 代理(Proxy) 3 2.3. 存根(Stub) 4 3. 三要素 4 3.1. 网络通讯 4 3.2. 消息编解码 5 3.3. IDL编译器 5 4. flex和bison 5 4.1. 准备概念 5 4.1.1. 正则表达式(regex/regexp) 6 4.1.2. 符号∈ 6 4.1.3. 终结符/非终结符/产生式 6 4.1.4. 记号(Token) 6 4.1.5. 形式文法 7 4.1.6. 上下文无关文法(CFG) 7 4.1.7. BNF 8 4.1.8. 推导 8 4.1.9. 语法树 8 4.1.10. LL(k) 9 4.1.11. LR(k) 9 4.1.12. LALR(k) 9 4.1.13. GLR 9 4.1.14. 移进/归约 9 4.2. flex和bison文件格式 9 4.2.1. 定义部分 10 4.2.2. 规则部分 10 4.2.3. 用户子例程部分 10 4.3. flex基础 10 4.3.1. flex文件格式 11 4.3.2. 选项 11 4.3.3. 名字定义 11 4.3.4. 词法规则 12 4.3.5. 匹配规则 12 4.3.6. %option 13 4.3.7. 全局变量yytext 13 4.3.8. 全局变量yyval 13 4.3.9. 全局变量yyleng 13 4.3.10. 全局函数yylex 13 4.3.11. 全局函数yywrap 13 4.4. bison基础 14 4.4.1. bison文件格式 14 4.4.2. %union 14 4.4.3. %token 15 4.4.4. 全局函数yyerror() 15 4.4.5. 全局函数yyparse() 15 4.5. 例1:单词计数 15 4.5.1. 目的 15 4.5.2. flex词法文件wc.l 16 4.5.3. Makefile 16 4.6. 例2:表达式 17 4.6.1. 目的 17 4.6.2. flex词法exp.l 17 4.6.3. bison语法exp.y 17 4.6.4. Makefile 19 4.6.5. 代码集成 19 4.7. 例3:函数 20 4.7.1. 目的 20 4.7.2. func.h 20 4.7.3. func.c 21 4.7.4. IDL代码func.idl 22 4.7.5. flex词法func.l 22 4.7.6. bison语法func.y 24 4.7.7. Makefile 27 5. 进阶 27 5.1. 客户端函数实现 27 5.2. 服务端函数实现 28 5.2.1. Stub部分实现 28 5.2.2. 用户部分实现 29 6. 参考资料 29
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值