远程通信协议RMI原理

目录

1、前言

2、WebService实现远程通信调用

3、HttpClient远程通信调用

3.1 POM依赖

3.2 web配置

3.3 服务提供方

3.4 服务调用方

3.5 测试

3、RMI实现远程通信调用

3.1 Java原生实现

3.2 Spring集成实现

4、RMI和Http区别和联系

5、RMI远程通信协议原理

5.1 RMI通信执行流程

5.2 RMI源码解析

5.3 自定义RMI协议框架


1、前言

在分布式服务框架中,一个最基础的问题就是远程服务是怎么通讯的,在Java领域中有很多可实现远程通讯的技术,例如:RMI、MINA、ESB、Burlap、Hessian、SOAP、EJB和JMS 等,这些名词之间到底是些什么关系呢,它们背后到底是基于什么原理实现的呢,了解这些是实现分布式服务框架的基础知识,而如果在性能上有高的要求的话,那深入了解这些技术背后的机制就是必须的了

RPC,全称为Remote Procedure Call,即远程过程调用,它是一个计算机通信协议。它允许像调用本地服务一样调用远程服务。它可以有不同的实现方式。如RMI(远程方法调用)、Hessian、Http invoker等。RPC是与语言无关的。直观说法就是A通过网络调用B的过程方法。也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。

1、首先要解决寻址的问题,也就是说,A服务器上的应用怎么告诉底层的RPC框架,B服务器的IP,以及应用绑定的端口,还有方法的名称,这样才能完成调用

2、方法的参数需要通过底层的网络协议如TCP传递到B服务器,由于网络协议是基于二进制的,内存中的参数的值要序列化成二进制的形式

3、在B服务器上完成寻址后,需要对参数进行反序列化,恢复为内存中的表达方式,然后找到对应的方法进行本地调用,然后得到返回值,

4、返回值还要发送回服务器A上的应用,也要经过序列化的方式发送,服务器A接到后,再反序列化,恢复为内存中的表达方式,交给应用

PS:Dubbo框架与Dubbo协议,如果只说Dubbo一词,其实是有歧义的,因为Dubbo可以是框架也可以是协议。Dubbo框架默认使用的是Dubbo协议,这个协议是阿里巴巴自己实现的一种应用层协议,传输层还是TCP。所以Dubbo协议与HTTP、FTP,SMTP这些应用层协议是并列的概念。除了默认的Dubbo协议,Dubbo框架还支持RMI、Hessian、HTTP等协议。

 

2、WebService实现远程通信调用

我们以CXF框架来实现webservice调用

引入pom依赖:

 <dependency>
      <groupId>org.apache.cxf</groupId>
      <artifactId>cxf-rt-frontend-jaxws</artifactId>
      <version>3.3.6</version>
    </dependency>
​
    <dependency>
      <groupId>org.apache.cxf</groupId>
      <artifactId>cxf-rt-transports-http-jetty</artifactId>
      <version>3.3.6</version>
    </dependency>

写一个业务接口及其实现,注意需要添加WebService相关注解:

package com.ydt.service;
​
import javax.jws.WebMethod;
import javax.jws.WebService;
​
@WebService
public interface RoleService {
​
    @WebMethod
    public String saySeeYou(String roleName);
}
​
package com.ydt.service.impl;
​
import com.ydt.service.RoleService;
​
public class RoleServiceImpl implements RoleService {
​
    public String saySeeYou(String roleName) {
        return "See You later " + roleName;
    }
}
​

创建服务端

package com.ydt.mainserver;
​
import com.ydt.service.RoleService;
import com.ydt.service.impl.RoleServiceImpl;
​
import javax.xml.ws.Endpoint;
​
public class WSServer {
​
    public static void main(String[] args) throws Exception{
        RoleService roleService = new RoleServiceImpl();
        String address = "http://127.0.0.1:7070/RoleService";
        Endpoint.publish(address,roleService);//实例和访问地址的绑定发布
        System.out.println("server start ...");
    }
}

创建客户端进行访问:

package com.ydt.mainclient;
​
import com.ydt.service.RoleService;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxyFactoryBean;
import org.apache.cxf.jaxws.JaxWsClientFactoryBean;
​
public final class WSClient {
​
    public static void main(String[] args) throws Exception {
        JaxWsClientFactoryBean clientFactory = new JaxWsClientFactoryBean();
        clientFactory.setServiceClass(RoleService.class);//设置需要转换的服务接口
        clientFactory.setAddress("http://127.0.0.1:7070/RoleService");//访问地址
        Client client = clientFactory.create();
        Object[] obj = client.invoke("saySeeYou", "大老板");//调用对应的方法,传入对应的参数
        System.out.println(obj[0]);
    }
​
}
​
//测试结果:See You later 大老板

底层URL类实现了java.io.Serializable序列化接口

3、HttpClient远程通信调用

HttpClient需要依赖于容器,我们这里使用Spring MVC来实现

3.1 POM依赖

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.7.RELEASE</version>
    </dependency>

3.2 web配置

web.xml

 <!--前端控制器,加载spring-mvc配置文件-->
    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
​
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

spring-mvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">
​
    <context:component-scan base-package="com.ydt"></context:component-scan>
    <mvc:annotation-driven/>
​
    <bean id="restTemplate" class="org.springframework.web.client.RestTemplate"></bean>
</beans>

3.3 服务提供方

package com.ydt.mainserver;
​
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
​
import javax.annotation.Resource;
import java.rmi.RemoteException;
​
@RestController
public class ServerController {
​
    @RequestMapping("/sayHello")
    public String sayHello() {
        return "Hello Spring HttpClient !";
    }
}

3.4 服务调用方

package com.ydt.mainclient;
​
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;
​
import java.util.List;
import java.util.Map;
​
@Controller
public class ClientController {
    //注入由spring提供的RestTemplate对象
    @Autowired
    private RestTemplate restTemplate;
    /**
     * 发送远程的http请求, 消费http服务
     * 获得订单对象的集合
     */
    @RequestMapping("/testHttpClient")
    @ResponseBody
    public void testHttpClient(){
        //发送远程http请求的url
        String url = "http://localhost:8080/sayHello.do";
        long starTime = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            //通过RestTemplate对象发送get请求
            restTemplate.getForEntity(url, null);
            System.out.println("hello world JAVA httpclient laohu");
        }
        long endTime = System.currentTimeMillis();
        System.out.println("共计耗时:" + (endTime-starTime));
    }
}

3.5 测试

 

3、RMI实现远程通信调用

3.1 Java原生实现

先编写一个业务接口和业务实现类

package com.ydt.service;
​
import java.rmi.Remote;
import java.rmi.RemoteException;
//接口需要继承Remote,不然无法将远程服务代理对象转换为客户端接口对象
public interface UserService extends Remote {
    
    public String sayHello(String username) throws RemoteException;
​
}
​
package com.ydt.service.impl;
​
import com.ydt.service.UserService;
​
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
​
//必须继承UnicastRemoteObject,否则会报没有序列化异常,连接被拒绝,不能用于网络通信。java.io.Serializable
public class UserServiceImpl extends UnicastRemoteObject implements UserService {
​
    private static final long serialVersionUID = 1L;
​
    //必须实现无参构造方法,服务端用于对象注册
    public UserServiceImpl() throws RemoteException {
​
    }
    public String sayHello(String username) {
        return "hello world JAVA RMI " + username;
    }
}
​

 

创建一个服务端,进行服务提供者对象和RMI协议访问路径绑定和端口设置:

package com.ydt.mainserver;
​
import com.ydt.service.UserService;
import com.ydt.service.impl.UserServiceImpl;
​
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
​
public class JdkMainServer {
    
    public static void main(String[] args) {
        try {
            UserService userServer = new UserServiceImpl();
            LocateRegistry.createRegistry(8080);//注册服务端口
            Naming.bind("rmi://127.0.0.1:8080/UserService", userServer);//绑定服务接口对象和访问url
            System.out.println("server running......");
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
​
}
​

 

创建一个客户端进行访问:

package com.ydt.mainclient;
​
import com.ydt.service.UserService;
​
import java.rmi.Naming;
​
public class JdkMainClient {
    public static void main(String[] args) {
        
        try {
            //stub
            UserService userService = (UserService)Naming.lookup("rmi://127.0.0.1:8080/UserService");
            System.out.println(userService.sayHello("laohu"));
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
​
}
​

 

3.2 Spring集成实现

Spring也提供了RMI的实现,底层实现还是JDK那一套,只是换了个称呼:Spring-Remoting

 

引入依赖:

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.6.RELEASE</version>
</dependency>

服务端spring配置:

    <bean id="orderService" class="com.ydt.service.impl.OrderServiceImpl"></bean>
​
    <bean class="org.springframework.remoting.rmi.RmiServiceExporter">
        <!--注册实例-->
        <property name="service" ref="orderService"/>
        <!--服务接口名,相当于"rmi://127.0.0.1:8080/UserService"中的UserService-->
        <property name="serviceName" value="OrderService"/>
        <!--对外暴露的服务接口,相当于"rmi://127.0.0.1:8080/UserService"中的UserService-->
        <property name="serviceInterface" value="com.ydt.service.OrderService"/>
        <!--注册端口-->
        <property name="registryPort" value="9090"/>
    </bean>

服务启动类:

package com.ydt.mainserver;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringServerMain {

    public static void main(String[] args) throws Exception{
        
        new ClassPathXmlApplicationContext("applicationContext-server.xml");
        System.out.println("Server has been started...");
    }

}

客户端Spring配置:

	<bean id="orderService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
        <!--ip、端口以及接口不要写错了-->
        <property name="serviceUrl" value="rmi://127.0.0.1:9090/OrderService"/>
        <!--如果是实际环境,该处接口服务端和客户端都要有-->
        <property name="serviceInterface" value="com.ydt.service.OrderService"/>
    </bean>

客户端启动访问类:

package com.ydt.mainclient;

import com.ydt.service.OrderService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.rmi.RemoteException;

public class SpringClientMain {

    public static void main(String[] args) throws RemoteException {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext-client.xml");
        OrderService orderService = (OrderService) ac.getBean("orderService");
        System.out.println(orderService.sayByeBye("123456789"));
    }

}

//测试结果:Bye Bye Spring RMI 123456789

 

4、RMI和Http区别和联系

相同点:都是远程调用技术。

区别:

RMI是在TCP协议上传递可序列化的java对象(使用Stream 技术(Serialize)传输),只能用在JVM上,绑定语言:RMI的客户端 和服务端都必须是java;

webservice是在http协议上传递xml文本文件,与语言和平台无关;

RMI性能优于webservice;大概相差10倍以上!如果是spring集成的情况下更高!

为什么?可以从OSI七层模型的单位就可以看出来:

            物理层:比特
            数据链路层:帧
            网络层:分组
            传输层:数据段
            应用层:报文  
            
            越往下,我们要包装的协议和头信息越多!集线器》网卡》路由

RMI多用于开发分布式应用系统;性能快当然用于分布式应用系统

RMI为Java平台的分布式计算提供了一个简单而直接的模型。因为Java的RMI技术是基于Java平台的,所以它将Java平台的安全 性和可移植性等优点带到了分布式计算中。RMI大大扩展Java的网络计算能力,它为编写基于分布式对象技术的企业Internet/Intranet 应用提供了强大的系统平台支持。

此外,如果系统的某一个功能必须采用指定的语言(非java)才能完成,也必须对系统进行组件拆分,这时候系统之间的通信就不能使用RMI了,因为RMI只能用于java语言的系统,这时候可以考虑其他的系统集成方案,比如webservice技术,微软的DCOM来完成系统组件之间的通信。

RMI是Java的特性,那么它必须基于JVM运行,也就是说RMI无法跨语言运行。而WebService就不同了,Soap是基于标准Http协议的,是一种与语言无关的字符级协议,所以它可以更好的实现异构系统的通信

而webservice则多用于以HTTP协议传输数据的形式给客户提供可在公网上远程调用的能完成某种功能需求的程序接口。客户系统和提供服务的系统往往是不同编程语言的异构系统(当然也包括同种编程语言的异构系统), Webservice能够实现不同编程语言异构系统之间进行通信。并且以webservice的形式提供服务,可以很好的保障服务系统的安全性.

web service提供的服务是基于web容器的,底层使用http协议,这都是基于一种请求应答的机制,是跨系统跨平台的。

Web Servcie最主要的优点是: 即跨语言,跨平台的不同系统之间的通信。 现在企业内部的很多系统集成,企业和企业之间的系统集成的问题。Web Service是主要的解决方案(服务重用,降低开发成本,只开放自己愿意开放的服务)。l多用于为客户或用户提供远程的服务,比如天气预报服务,气象台服务器对各地客户端提供天气预报接口API;再比如违章系统查询接口等。

 

 

5、RMI远程通信协议原理

5.1 RMI通信执行流程

我们先来看一下RMI远程方法调用实现步骤:

  • 服务端初始化远程服务对象

  • 当客户端通过RMI注册表找到一个远程接口的时候,所得到的其实是远程接口的一个动态代理对象。

  • 当客户端调用其中的方法的时候,方法的参数对象会在序列化之后,传输到服务器端。

  • 服务器端接收到之后,进行反序列化得到参数对象。

  • 并使用这些参数对象,在服务器端调用实际的方法。

  • 调用的返回值Java对象经过序列化之后,再发送回客户端。

  • 客户端再经过反序列化之后得到Java对象,返回给调用者。

  • 这中间的序列化过程对于使用者来说是透明的,由动态代理对象自动完成

除了序列化之外,RMI还使用了动态类加载技术

  • 当需要进行反序列化的时候,如果该对象的类定义在当前JVM中没有找到,RMI会尝试从远端下载所需的类文件定义

  • 可以在RMI程序启动的时候,通过JVM参数java.rmi.server.codebase来指定动态下载Java类文件的URL

RMI其实也是帮我们封装了一些细节而通用的部分,比如序列化和反序列化,连接的建立和释放等,下面是RMI的具体流程:

这里涉及到几个新概念:

Stub和Skeleton:这两个的身份是一致的,都是作为代理的存在。客户端的称作Stub(你可以理解为代理存根),服务端的称作Skeleton(你可以理解为代理骨架)。要做到对程序员屏蔽远程方法调用的细节,这两个代理是必不可少的,包括网络连接等细节。

Registry:顾名思义,可以认为Registry是一个“注册所”,提供了服务名到服务的映射。如果没有它,意味着客户端需要记住每个服务所在的端口号,这种设计显然是不优雅的。

5.2 RMI源码解析

5.2.1 服务端启动Registry服务

Registry registry=LocateRegistry.createRegistry(8080);

public RegistryImpl(final int var1) throws RemoteException {
    	//如果服务端指定的端口号是1099(tomcat默认的jmx监听端口)并且系统开启了安全管理器,那么可以在限定的权限集内(listen和		  accept)绕过系统的安全校验
        if (var1 == 1099 && System.getSecurityManager() != null) {
            try {
                AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
                    public Void run() throws RemoteException {
                        LiveRef var1x = new LiveRef(RegistryImpl.id, var1);
                        RegistryImpl.this.setup(new UnicastServerRef(var1x, (var0) -> {
                            return RegistryImpl.registryFilter(var0);
                        }));
                        return null;
                    }
                }, (AccessControlContext)null, new SocketPermission("localhost:" + var1, "listen,accept"));
            } catch (PrivilegedActionException var3) {
                throw (RemoteException)var3.getException();
            }
        } else {
            LiveRef var2 = new LiveRef(id, var1);
            //以上纯粹是为了效率起见。真正做的事情在setUp()方法中
            this.setup(new UnicastServerRef(var2, RegistryImpl::registryFilter));
        }
    }
	private void setup(UnicastServerRef var1) throws RemoteException {
        //将指向正在初始化的RegistryImpl对象的远程引用ref(RemoteRef)赋值为传入的UnicastServerRef对象,这里涉及了向上转型(后续会用到LiveRef)
        this.ref = var1;
        //继续执行UnicastServerRef的exportObject()方法
        var1.exportObject(this, (Object)null, true);
    }
	public Remote exportObject(Remote var1, Object var2, boolean var3) throws RemoteException {
        //获取RegistryImpl的class对象--Skeleton类型
        Class var4 = var1.getClass();

        Remote var5;
        try {
            //根据class对象生成代理对象,用来服务于客户端RegistryImpl的Stub对象
            var5 = Util.createProxy(var4, this.getClientRef(), this.forceStubUse);
        } catch (IllegalArgumentException var7) {
            throw new ExportException("remote object implements illegal remote interface", var7);
        }

        if (var5 instanceof RemoteStub) {
            //将UnicastServerRef的skel(skeleton)对象设置为当前RegistryImpl对象
            this.setSkeleton(var1);
        }
		//用skeleton、stub、UnicastServerRef对象、id和一个boolean值构造了一个Target对象,也就是这个Target对象基本上包含了			全部的信息
        Target var6 = new Target(var1, this, var5, this.ref.getObjID(), var3);
        //调用UnicastServerRef的ref(LiveRef)变量的exportObject()方法,传入Target对象
        this.ref.exportObject(var6);
        this.hashToMethod_Map = (Map)hashToMethod_Maps.get(var4);
        return var5;
    }

到上面为止,我们看到的都是一些变量的赋值和创建工作,还没有到传输层,这些引用对象将会被Stub和Skeleton对象使用。接下来就是传输层上的了。追溯LiveRef的exportObject()方法,很容易找到了TCPTransport的exportObject()方法。这个方法做的事情就是将上面构造的Target对象暴露出去。调用TCPTransport的listen()方法,listen()方法创建了一个ServerSocket,并且启动了一条线程等待客户端的请求。接着调用父类Transport的exportObject()将Target对象存放进ObjectTable中。

	public void exportObject(Target var1) throws RemoteException {
        this.transport.exportObject(var1);
    }
    
    public void exportObject(Target var1) throws RemoteException {
        synchronized(this) {
            //进行监听,创建ServerSocket线程
            this.listen();
            ++this.exportCount;
        }

        boolean var2 = false;
        boolean var12 = false;

        try {
            var12 = true;
            //调用父类Transport的exportObject()将Target对象存放进ObjectTable中,用户端什么时候想取都有!
            super.exportObject(var1);
            var2 = true;
            var12 = false;
        } finally {
 	   //....................................................
	
	
	private void listen() throws RemoteException {
        assert Thread.holdsLock(this);
		
        TCPEndpoint var1 = this.getEndpoint();
        int var2 = var1.getPort();
        if (this.server == null) {
            if (tcpLog.isLoggable(Log.BRIEF)) {
                tcpLog.log(Log.BRIEF, "(port " + var2 + ") create server socket");
            }

            try {
                this.server = var1.newServerSocket();//开启TCP服务节点
                Thread var3 = (Thread)AccessController.doPrivileged(new NewThreadAction(new 						                                 TCPTransport.AcceptLoop(this.server), "TCP Accept-" + var2, true));
                //启动监听线程等着客户端调用
                var3.start();
            } catch (BindException var4) {
                throw new ExportException("Port already in use: " + var2, var4);
            } catch (IOException var5) {
                throw new ExportException("Listen failed on port: " + var2, var5);
            }
        } else {
            SecurityManager var6 = System.getSecurityManager();
            if (var6 != null) {
                var6.checkListen(var2);
            }
        }

    }

Naming.bind("rmi://127.0.0.1:8080/UserService", userServer);//绑定服务接口对象和访问url

	public static void bind(String name, Remote obj)
        throws AlreadyBoundException,
            java.net.MalformedURLException,
            RemoteException
    {
        ParsedNamingURL parsed = parseURL(name);
        Registry registry = getRegistry(parsed);//通过传入的host和port构造RemoteRef对象

        if (obj == null)
            throw new NullPointerException("cannot bind to null");

        registry.bind(parsed.name, obj);//进行访问绑定
    }

public void bind(String var1, Remote var2) throws RemoteException, AlreadyBoundException, AccessException {
        checkAccess("Registry.bind");
        synchronized(this.bindings) {
            Remote var4 = (Remote)this.bindings.get(var1);
            if (var4 != null) {
                throw new AlreadyBoundException(var1);
            } else {
                this.bindings.put(var1, var2);
            }
        }
    }

到这里,我们已经将RegistryImpl对象创建并且起了服务等待客户端的请求。

5.2.2 客户端发送服务请求

Naming.lookup("rmi://127.0.0.1:8080/UserService");

    
    public static Remote lookup(String name) throws NotBoundException,
            java.net.MalformedURLException,
            RemoteException
    {
        //跟服务端一样,根据访问路径得到Registry对象(你可以理解为服务注册中心)
        ParsedNamingURL parsed = parseURL(name);
        Registry registry = getRegistry(parsed);
​
        if (parsed.name == null)
            return registry;
        //根据服务名称获取远程服务对象
        return registry.lookup(parsed.name);
    }
​
    /**
        代码追溯到LocateRegistry的getRegistry()方法。这个方法做的事情是通过传入的host和port构造RemoteRef对象,并创建了一个本地代理。通过Debug功能发现,这个代理对象其实是RegistryImpl_Stub对象。这样客户端便有了服务端的RegistryImpl的代理(取决于ignoreStubClasses变量)。但注意此时这个代理其实还没有和服务端的RegistryImpl对象关联,毕竟是两个VM上面的对象,这里我们也可以猜测,代理和远程的Registry对象之间是通过socket消息来完成的。
​
    **/
    public static Registry getRegistry(String host, int port,
                                       RMIClientSocketFactory csf)
        throws RemoteException
    {
        Registry registry = null;
​
        if (port <= 0)
            port = Registry.REGISTRY_PORT;
​
        if (host == null || host.length() == 0) {
​
            try {
                host = java.net.InetAddress.getLocalHost().getHostAddress();
            } catch (Exception e) {
                host = "";
            }
        }
​
        LiveRef liveRef =
            new LiveRef(new ObjID(ObjID.REGISTRY_ID),
                        new TCPEndpoint(host, port, csf, null),
                        false);
        RemoteRef ref =
            (csf == null) ? new UnicastRef(liveRef) : new UnicastRef2(liveRef);
        //客户端有了服务端的RegistryImpl的代理
        return (Registry) Util.createProxy(RegistryImpl.class, ref, false);
    }
    
    //获取到远程访问对象
    public Remote lookup(String var1) throws RemoteException, NotBoundException {
        synchronized(this.bindings) {
            Remote var3 = (Remote)this.bindings.get(var1);
            if (var3 == null) {
                throw new NotBoundException(var1);
            } else {
                return var3;
            }
        }
    }

接下来就是重头戏了,从下面代码看起。

追溯下去,获取到远程Registry对象的代理对象之后,调用RegistryImpl_Stub的lookUp()方法。主要代码如下。做的事情是利用上面通过服务端host和port等信息创建的RegistryImpl_stub对象构造RemoteCall调用对象,operations参数中是各个Registry中声明的操作

	/**
    调用 RegistryImpl_Stub的ref(RemoteRef)对象的newCall()方法,将RegistryImpl_Stub对象传了进去,不要忘了构造它的时候我们将服务器的主机端口等信息传了进去,也就是我们把服务器相关的信息也传进了newCall()方法。
    */	
	public Remote lookup(String var1) throws AccessException, NotBoundException, RemoteException {
        try {
            RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L);
            try {
                ObjectOutput var3 = var2.getOutputStream();
                var3.writeObject(var1);
            } catch (IOException var18) {
                throw new MarshalException("error marshalling arguments", var18);
            }
		   //通过tcp连接发送消息到服务端
            super.ref.invoke(var2);

            Remote var23;
            try {
                ObjectInput var6 = var2.getInputStream();
                var23 = (Remote)var6.readObject();
            } catch (IOException var15) {
                throw new UnmarshalException("error unmarshalling return", var15);
            } catch (ClassNotFoundException var16) {
                throw new UnmarshalException("error unmarshalling return", var16);
            } finally {
                super.ref.done(var2);
            }

            return var23;
        .......
    }
    
     /**
     	newCall()方法做的事情简单来看就是建立了跟远程RegistryImpl的Skeleton对象的连接。(不要忘了上面我们说到过服务端通过TCPTransport的exportObject()方法等待着客户端的请求)
     */
    public RemoteCall newCall(RemoteObject var1, Operation[] var2, int var3, long var4) throws RemoteException {
        clientRefLog.log(Log.BRIEF, "get connection");
        Connection var6 = this.ref.getChannel().newConnection();

        try {
            clientRefLog.log(Log.VERBOSE, "create call context");
            if (clientCallLog.isLoggable(Log.VERBOSE)) {
                this.logClientCall(var1, var2[var3]);
            }

            StreamRemoteCall var7 = new StreamRemoteCall(var6, this.ref.getObjID(), var3, var4);

            try {
                this.marshalCustomCallData(var7.getOutputStream());
            } catch (IOException var9) {
                throw new MarshalException("error marshaling custom call data");
            }

            return var7;
        } catch (RemoteException var10) {
            this.ref.getChannel().free(var6, false);
            throw var10;
        }
    }

 

	/**
		连接建立之后自然就是发送请求了。我们知道客户端终究只是拥有Registry对象的代理,而不是真正地服务端的Registry对象本身,他们位于不同的虚拟机实例之中,无法直接调用。必然是通过消息进行交互的。看看super.ref.invoke()这里做了什么?追溯到StreamRemoteCall的executeCall()方法。看似本地调用,但其实很容易从代码中看出来是通过tcp连接发送消息到服务端(别的看不懂,ACK看得懂吧,TCP消息接收确认!)。由服务端解析并且处理调用。
	*/
	public void executeCall() throws Exception {
        DGCAckHandler var2 = null;

        byte var1;
        try {
            if (this.out != null) {
                var2 = this.out.getDGCAckHandler();
            }

            this.releaseOutputStream();
            //读取连接输入流数据
            DataInputStream var3 = new DataInputStream(this.conn.getInputStream());
            byte var4 = var3.readByte();
            if (var4 != 81) {
                if (Transport.transportLog.isLoggable(Log.BRIEF)) {
                    Transport.transportLog.log(Log.BRIEF, "transport return code invalid: " + var4);
                }

                throw new UnmarshalException("Transport return code invalid");
            }

            this.getInputStream();
            var1 = this.in.readByte();
            this.in.readID();
        } catch (UnmarshalException var11) {
            throw var11;
        } catch (IOException var12) {
            throw new UnmarshalException("Error unmarshaling return header", var12);
        } finally {
            if (var2 != null) {
                var2.release();
            }

        }

        switch(var1) {
        case 1:
            return;
        case 2:
            Object var14;
            try {
                var14 = this.in.readObject();
            } catch (Exception var10) {
                throw new UnmarshalException("Error unmarshaling return", var10);
            }

            if (!(var14 instanceof Exception)) {
                throw new UnmarshalException("Return type not Exception");
            } else {
                this.exceptionReceivedFromServer((Exception)var14);
            }
        default:
            if (Transport.transportLog.isLoggable(Log.BRIEF)) {
                Transport.transportLog.log(Log.BRIEF, "return code invalid: " + var1);
            }

            throw new UnmarshalException("Return code invalid");
        }
    }

至此,我们已经将客户端的服务查询请求发出了。

 

5.2.3 服务端接收请求并返回结果给客户端

这里我用的方法是直接断点在服务端的Thread的run()方法中,因为我们知道服务端已经用线程跑起了服务(当然我是先断点在Registry_Impl的lookUp()方法并查找调用栈找到源头的)。一步一步我们找到了Transport的serviceCall()方法,这个方法是关键。瞻仰一下主要的代码,到ObjectTable.getTarget()为止做的事情是从socket流中获取ObjId,并通过ObjId和Transport对象获取Target对象,这里的Target对象已经是服务端的对象。再借由Target的派发器Dispatcher,传入参数服务实现和请求对象RemoteCall,将请求派发给服务端那个真正提供服务的RegistryImpl的lookUp()方法,这就是Skeleton移交给具体实现的过程了,Skeleton负责底层的操作。

public boolean serviceCall(final RemoteCall var1) {
        try {
            ObjID var39;
            try {
                var39 = ObjID.read(var1.getInputStream());
            } catch (IOException var33) {
                throw new MarshalException("unable to read objID", var33);
            }
​
            Transport var40 = var39.equals(dgcID) ? null : this;
            //获取目标对象,5.2.1启动服务的时候put进去的
            Target var5 = ObjectTable.getTarget(new ObjectEndpoint(var39, var40));
            final Remote var37;
            if (var5 != null && (var37 = var5.getImpl()) != null) {
                final Dispatcher var6 = var5.getDispatcher();
                var5.incrementCallCount();
​
                boolean var8;
                try {
                    transportLog.log(Log.VERBOSE, "call dispatcher");
                    final AccessControlContext var7 = var5.getAccessControlContext();
                    ClassLoader var41 = var5.getContextClassLoader();
                    ClassLoader var9 = Thread.currentThread().getContextClassLoader();
​
                    try {
                        setContextClassLoader(var41);
                        currentTransport.set(this);
​
                        try {
                            AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
                                public Void run() throws IOException {
                                    Transport.this.checkAcceptPermission(var7);
                                    //派发服务
                                    var6.dispatch(var37, var1);
                                    return null;
                                }
                            }, var7);
                            return true;
                        } catch (PrivilegedActionException var31) {
                            throw (IOException)var31.getException();
                        }
                    } finally {
                        setContextClassLoader(var9);
                        currentTransport.set((Object)null);
                    }
                } catch (IOException var34) {
                    transportLog.log(Log.BRIEF, "exception thrown by dispatcher: ", var34);
                    var8 = false;
                } finally {
                    var5.decrementCallCount();
                }
​
                return var8;
            }
​
            throw new NoSuchObjectException("no such object in table");
        } catch (RemoteException var36) {
            RemoteException var2 = var36;
            if (UnicastServerRef.callLog.isLoggable(Log.BRIEF)) {
                String var3 = "";
​
                try {
                    var3 = "[" + RemoteServer.getClientHost() + "] ";
                } catch (ServerNotActiveException var30) {
                }
​
                String var4 = var3 + "exception: ";
                UnicastServerRef.callLog.log(Log.BRIEF, var4, var36);
            }
​
            try {
                ObjectOutput var38 = var1.getResultStream(false);
                UnicastServerRef.clearStackTraces(var2);
                var38.writeObject(var2);
                var1.releaseOutputStream();
            } catch (IOException var29) {
                transportLog.log(Log.BRIEF, "exception thrown marshalling exception: ", var29);
                return false;
            }
        }
​
        return true;
    }

再回过头来,看看RegistryImpl的lookUp()实现。做了同步控制,并通过服务名从Map中取出服务对象。返回给客户端。还记得我们在bindings中存放的其实是OperationImpl的真正实现,并非是Stub对象。

public Remote lookup(String var1) throws RemoteException, NotBoundException {
    Hashtable var2 = this.bindings;
    synchronized(this.bindings) {
        Remote var3 = (Remote)this.bindings.get(var1);
        if(var3 == null) {
            throw new NotBoundException(var1);
        } else {
            return var3;
        }
    }
}

 

5.2.4 服务端创建服务对象

从OperationImpl的构造函数看起。调用了父类UnicastRemoteObject的构造方法,追溯到UnicastRemoteObject的私有方法exportObject()。这里做了一个判断,判断服务的实现是不是UnicastRemoteObject的子类,如果是,则直接赋值其ref(RemoteRef)对象为传入的UnicastServerRef对象。反之则调用UnicastServerRef的exportObject()方法。这里我们是第一种情况。

private static Remote exportObject(Remote obj, UnicastServerRef sref)
    throws RemoteException
{
    // if obj extends UnicastRemoteObject, set its ref.
    if (obj instanceof UnicastRemoteObject) {
        ((UnicastRemoteObject) obj).ref = sref;
    }
    return sref.exportObject(obj, null, false);
}

 

5.2.5 服务实现绑定到服务端的Registry

将服务实现绑定到服务端的Registry上,使得客户端只需与Registry交互

IOperation iOperation=new OperationImpl();
Naming.rebind("rmi://127.0.0.1:1099/Operation",iOperation);

/**
	从上面这行代码开始看,容易发现Naming的方法全部都是调用的Registry的方法。这里通过host和port找到我们第一步启动的服务端Registry服务对象,追溯到其rebind()方法,可以看到,其实做的事情很是简单,就是把名字和服务实现存进一个Map里面,跟bind同一个缓存变量
*/

public void rebind(String var1, Remote var2) throws RemoteException, AccessException {
    checkAccess("Registry.rebind");
    this.bindings.put(var1, var2);
}

 

5.2.6 小结

前面我们做了很多工作,大量工作用于起动Registry服务和如何查找客户端需要调用的服务。但事实上,这个Registry可以服务于很多的其他服务。一旦客户端和服务端通过Stub和Skeleton建立了socket连接,后面的操作直接通过这个连接完成就结了!

创建SocketServer,进行监听:

	//TCPEndpoint
	ServerSocket newServerSocket() throws IOException {
        if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) {
            TCPTransport.tcpLog.log(Log.VERBOSE, "creating server socket on " + this);
        }

        Object var1 = this.ssf;
        if (var1 == null) {
            var1 = chooseFactory();
        }

        ServerSocket var2 = ((RMIServerSocketFactory)var1).createServerSocket(this.listenPort);
        if (this.listenPort == 0) {
            setDefaultPort(var2.getLocalPort(), this.csf, this.ssf);
        }

        return var2;
    }

服务器接收请求:

		//TCPTransport
		private void executeAcceptLoop() {
            if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
                TCPTransport.tcpLog.log(Log.BRIEF, "listening on port " + TCPTransport.this.getEndpoint().getPort());
            }

            while(true) {
                Socket var1 = null;

                try {
                    var1 = this.serverSocket.accept();//服务端等待接收请求
                    InetAddress var16 = var1.getInetAddress();
                    String var3 = var16 != null ? var16.getHostAddress() : "0.0.0.0";

                    try {
                        TCPTransport.connectionThreadPool.execute(TCPTransport.this.new ConnectionHandler(var1, var3));
                    } catch (RejectedExecutionException var11) {
                        TCPTransport.closeSocket(var1);
                        TCPTransport.tcpLog.log(Log.BRIEF, "rejected connection from " + var3);
                    }
                } catch (Throwable var15) {
                    Throwable var2 = var15;

                    try {
                        if (this.serverSocket.isClosed()) {
                            return;
                        }

                        try {
                            if (TCPTransport.tcpLog.isLoggable(Level.WARNING)) {
                                TCPTransport.tcpLog.log(Level.WARNING, "accept loop for " + this.serverSocket + " throws", var2);
                            }
                        } catch (Throwable var13) {
                        }
                    } finally {
                        if (var1 != null) {
                            TCPTransport.closeSocket(var1);
                        }

                    }

                    if (!(var15 instanceof SecurityException)) {
                        try {
                            TCPEndpoint.shedConnectionCaches();
                        } catch (Throwable var12) {
                        }
                    }

                    if (!(var15 instanceof Exception) && !(var15 instanceof OutOfMemoryError) && !(var15 instanceof NoClassDefFoundError)) {
                        if (var15 instanceof Error) {
                            throw (Error)var15;
                        }

                        throw new UndeclaredThrowableException(var15);
                    }

                    if (!this.continueAfterAcceptFailure(var15)) {
                        return;
                    }
                }
            }
        }

服务器处理服务请求:

        //TCPTransport
        public void run() {
            Thread var1 = Thread.currentThread();
            String var2 = var1.getName();
​
            try {
                var1.setName("RMI TCP Connection(" + TCPTransport.connectionCount.incrementAndGet() + ")-" + this.remoteHost);
                AccessController.doPrivileged(() -> {
                    this.run0();//处理请求
                    return null;
                }, TCPTransport.NOPERMS_ACC);
            } finally {
                var1.setName(var2);
            }
​
        }
    //UnicastServerRef
    public void dispatch(Remote var1, RemoteCall var2) throws IOException {
        try {
            long var4;
            ObjectInput var39;
            try {
                var39 = var2.getInputStream();
                int var3 = var39.readInt();
                if (var3 >= 0) {
                    if (this.skel != null) {
                        this.oldDispatch(var1, var2, var3);
                        return;
                    }
​
                    throw new UnmarshalException("skeleton class not found but required for client version");
                }
​
                var4 = var39.readLong();
            } catch (Exception var35) {
                throw new UnmarshalException("error unmarshalling call header", var35);
            }
​
            MarshalInputStream var38 = (MarshalInputStream)var39;
            var38.skipDefaultResolveClass();
            Method var8 = (Method)this.hashToMethod_Map.get(var4);
            if (var8 == null) {
                throw new UnmarshalException("unrecognized method hash: method not supported by remote object");
            }
​
            this.logCall(var1, var8);
            Object[] var9 = null;
​
            try {
                this.unmarshalCustomCallData(var39);
                var9 = this.unmarshalParameters(var1, var8, var38);
            } catch (IOException var32) {
                throw new UnmarshalException("error unmarshalling arguments", var32);
            } catch (ClassNotFoundException var33) {
                throw new UnmarshalException("error unmarshalling arguments", var33);
            } finally {
                var2.releaseInputStream();
            }
​
            Object var10;
            try {
                var10 = var8.invoke(var1, var9);//通过反射进行调用
            } catch (InvocationTargetException var31) {
                throw var31.getTargetException();
            }
​
            try {
                ObjectOutput var11 = var2.getResultStream(true);
                Class var12 = var8.getReturnType();
                if (var12 != Void.TYPE) {
                    marshalValue(var12, var10, var11);
                }
            } catch (IOException var30) {
                throw new MarshalException("error marshalling return", var30);
            }
        } catch (Throwable var36) {
            Object var6 = var36;
            this.logCallException(var36);
            ObjectOutput var7 = var2.getResultStream(false);
            if (var36 instanceof Error) {
                var6 = new ServerError("Error occurred in server thread", (Error)var36);
            } else if (var36 instanceof RemoteException) {
                var6 = new ServerException("RemoteException occurred in server thread", (Exception)var36);
            }
​
            if (suppressStackTraces) {
                clearStackTraces((Throwable)var6);
            }
​
            var7.writeObject(var6);
        } finally {
            var2.releaseInputStream();
            var2.releaseOutputStream();
        }
​
    }

 

PS:

监听两个端口,一个是1099,一个是随机的端口号。完整处理流程可以简单分三步:

1)1099端口接收请求,sun.rmi.transport.tcp.TCPTransport#handleMessages做请求的一般化处理,最终交由sun.rmi.registry.RegistryImpl_Skel#dispatch来执行请求,并返回注册对象,最主要的返回内容就是ip+port:

2)client端接收server返回对象,包装为代理对象,最主要的也是ip+port信息。然后代理对象主要调用的是java.rmi.server.RemoteObjectInvocationHandler#invokeRemoteMethod->sun.rmi.server.UnicastRef#invoke(java.rmi.Remote, java.lang.reflect.Method, java.lang.Object[], long);来调用远程服务。

3)随机端口号接收请求,然后根据接收的信息找到对应的实例类,和请求的方法(这里有用hash映射),然后调用对应实例的方法,返回结果

请求的类名解析:sun.rmi.transport.Transport#serviceCall

请求的方法名解析:sun.rmi.server.UnicastServerRef#dispatch

 

个人理解(非官方):在我理解,stub就是客户端的proxy。skeleton貌似并不存在了,服务端是统一的服务调用,非常类似与servlet服务端实现方式

WebService:客户端和服务器端通信是Xml,所以代理类跟Xml之间就有序列化和反序列化的过程

RMI:依靠Java序列化机制,性能上虽然比不上ProtoBuf那种变态,但是对于xml这种方式来说,已经好太多了

5.3 自定义RMI协议框架

package com.ydt.myrmi;
​
public interface UserService {
​
    int getAge();
}
​
package com.ydt.myrmi;
​
public class UserServiceImpl implements UserService {
    private int age = 18;
​
    public int getAge() {
        return age;
    }
​
}
​
package com.ydt.myrmi;
​
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
​
public class Stub {
​
    private Socket socket;
​
    public Stub() throws IOException {
        socket = new Socket("localhost",8888);
    }
​
    public Object getService(String serviceName) throws IOException, ClassNotFoundException {
        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
        oos.writeObject(serviceName);
        oos.flush();
​
        ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
        return ois.readObject();
​
    }
}
​
package com.ydt.myrmi;
​
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
​
public class Skeleton extends Thread{
​
    private UserService userService;
​
    public Skeleton(UserService userServiceProxy) {
        this.userService = userServiceProxy;
    }
​
    @Override
    public void run() {
        try {
            ServerSocket serverSocket = new ServerSocket(8888);
            while (true){
                Socket socket = serverSocket.accept();
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                Object object = ois.readObject();
                Method[] methods = userService.getClass().getMethods();
                for (Method method : methods) {
                    if(method.getName().equals(object)){
                        Object invoke = method.invoke(userService, null);
                        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                        oos.writeObject(invoke);
                        oos.flush();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
​
package com.ydt.myrmi;
​
public class Server {
​
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        Skeleton skeleton = new Skeleton(userService);
        skeleton.start();
        System.out.println("server start ...");
    }
}
​
package com.ydt.myrmi;
​
import java.io.IOException;
​
public class Client {
​
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Stub stub = new Stub();
        System.out.println(stub.getService("getAge"));
    }
}
​
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页