zoukankan      html  css  js  c++  java
  • RPC远程过程调用学习之路(一):用最原始代码还原PRC框架

    RPC: Remote Procedure Call 远程过程调用,即业务的具体实现不是在自己系统中,需要从其他系统中进行调用实现,所以在系统间进行数据交互时经常使用。

    rpc的实现方式有很多,可以通过http和tcp协议进行实现

    通过http协议的主要有:  

    1. webService    可以参考我之前的博客  WebService 学习之路(一):了解并使用webService

                                                                webService学习之路(二):springMVC集成CXF快速发布webService

                                                                webService学习之路(三):springMVC集成CXF后调用已知的wsdl接口

    1. restful           可以参考我之前的博客  Restful 介绍及SpringMVC+restful 实例讲解

    而今天要讲的是通过TCP协议实现的远程调用。

    为啥已经掌握了webservice和restful等通过http协议实现rpc技术外,还要研究tcp协议实现rpc呢?

    因为网络七层协议中,http位于tcp之上,而从传输上而言,越底层同等条件下传输速度更快

    另外影响rpc调用的除了传输方式外,另一个就是序列化,而java的阻塞式IO往往成为瓶颈,所以这里设计到了NIO,

    NIO知识点就多了,请自己搜索学习。

    言归正传,今天不借助其他仁和框架,用简单的代码还原rpc的过程。

    大致可以分为下面几部(先了解过程,再看代码更容易理解):

    1. 书写好服务接口和实现,就是我们项目中的业务层,看着service,service.imp就熟悉了 o(∩_∩)o
    2. 把1写好的接口暴露给其他系统,以便调用
    3. 根据暴露了接口的地址和接口信息,进行调用

    是不是感觉和调用本地的service一样, 最终就是要达到的这个效果。

    文采不好,就要开始贴代码了:

    首先是写接口和接口的实现类,和平时看见的、写的没任何区别

    接口定义

    package com.xiaochangwei.rpc;
    
    public interface RpcTestService {
        String testRpcCall(int count);
    }

    接口实现类

    package com.xiaochangwei.rpc;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class RpcTestServiceImpl implements RpcTestService {
    
        public final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:sss");
        
        @Override
        public String testRpcCall(int count) {
            return dateFormat.format(new Date())+" 调用rpc次数为:" + count;
        }
    
    }

    然后就是rpc的精髓了,怎么把服务暴露给其他系统的

    其实说白了就是使用java自带的

    import java.net.ServerSocket;
    import java.net.Socket;

    网络编程相关的东西socket和对应的IO,因为我们要向服务器发送请求,然后服务器又要返回数据给请求方,

    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;

    同时还要用到反射,因为我们不可能每个接口实现类的暴露都去写一套方法吧,得共通化吧, 先简单理解为泛型把    代码中看见过Class T 吧 o(∩_∩)o 

    先简单理解为过程和socket调用过程一样吧:

    1. 根据约定的端口,服务端起一个ServerSocket,并一直监听该端口
    2. 监听到有请求时,server端通过inputStream取得请求的相关信息
    3. 根据请求信息调用相应方法处理,并返回结果

    简易PRC框架

    package com.xiaochangwei.rpc;
    
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    /**
     * rpc简易框架
     */
    public class RpcFramework {
    
        /**
         * 暴露服务
         */
        @SuppressWarnings("resource")
        public static void export(final Object service, int port) throws Exception {
            if (service == null)
                throw new IllegalArgumentException("服务实例为空");
            if (port <= 0 || port > 65535)
                throw new IllegalArgumentException("端口号不正确");
            System.out.println("通过端口 [" + port +"] 暴露服务[" + service.getClass().getName() + "]" );
            
            ServerSocket server = new ServerSocket(port);
            while(true){
                try {
                    final Socket socket = server.accept();
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                try {
                                    ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
                                    try {
                                        String methodName = inputStream.readUTF();
                                        Class<?>[] parameterTypes = (Class<?>[]) inputStream.readObject();
                                        Object[] arguments = (Object[]) inputStream.readObject();
                                        ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
                                        try {
                                            Method method = service.getClass().getMethod(methodName,parameterTypes);
                                            Object result = method.invoke(service,arguments);
                                            output.writeObject(result);
                                        } catch (Throwable t) {
                                            output.writeObject(t);
                                        } finally {
                                            output.close();
                                        }
                                    } finally {
                                        inputStream.close();
                                    }
                                } finally {
                                    socket.close();
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }).start();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 引用服务
         */
        @SuppressWarnings("unchecked")
        public static <T> T refer(final Class<T> interfaceClass, final String host,
                final int port) throws Exception {
            if (interfaceClass == null)
                throw new IllegalArgumentException("接口为空");
            if (!interfaceClass.isInterface())
                throw new IllegalArgumentException(interfaceClass.getName() + " 不是接口");
            if (host == null || host.length() == 0)
                throw new IllegalArgumentException("主机地址为空");
            if (port <= 0 || port > 65535)
                throw new IllegalArgumentException("端口不正确" + port);
            System.out.println("从服务器[" + host + ":" + port + "]取得远程服务[" + interfaceClass.getName() + "]" );
            
            return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),
                    new Class<?>[] { interfaceClass }, new InvocationHandler() {
                        public Object invoke(Object proxy, Method method,Object[] arguments) throws Throwable {
                            Socket socket = new Socket(host, port);
                            try {
                                ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
                                try {
                                    output.writeUTF(method.getName());
                                    output.writeObject(method.getParameterTypes());
                                    output.writeObject(arguments);
                                    ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
                                    try {
                                        Object result = input.readObject();
                                        if (result instanceof Throwable) {
                                            throw (Throwable) result;
                                        }
                                        return result;
                                    } finally {
                                        input.close();
                                    }
                                } finally {
                                    output.close();
                                }
                            } finally {
                                socket.close();
                            }
                        }
                    });
        }
    
        /**
         * 暴露RPC服务主调函数
         */
        public static void main(String[] args) throws Exception {
            RpcTestService rpcTestService = new RpcTestServiceImpl();
            RpcFramework.export(rpcTestService, 3125);
        }
    }

    指定其中的main后,我们的接口就通过指定的端口暴露给其他系统了

    其他系统调用也很简单

    package com.xiaochangwei.rpc;
    
    public class RpcConsumer {
        public static void main(String[] args) throws Exception {
            RpcTestService rpcTestService = RpcFramework.refer(RpcTestService.class,"127.0.0.1", 3125);
            for (int i = 0; i < 10; i++) {
                String response = rpcTestService.testRpcCall(i);
                System.out.println(response);
                Thread.sleep(1000);
            }
        }
    }

    是不是感觉和本地调用一样 o(∩_∩)o 

    看下效果吧,先暴露接口,再调用

    执行调用

    测试发现,调用是成功的 o(∩_∩)o  

  • 相关阅读:
    Building Java Projects with Gradle
    Vert.x简介
    Spring及Spring Boot 国内快速开发框架
    dip vs di vs ioc
    Tools (StExBar vs Cmder)which can switch to command line window on context menu in windows OS
    SSO的定义、原理、组件及应用
    ModSecurity is an open source, cross-platform web application firewall (WAF) module.
    TDD中测试替身学习总结
    Spring事务银行转账示例
    台式机(华硕主板)前面板音频接口(耳机和麦克风)均无声的解决办法
  • 原文地址:https://www.cnblogs.com/xiaochangwei/p/5419404.html
Copyright © 2011-2022 走看看