精华内容
下载资源
问答
  • Java RMI远程方法调用详解

    万次阅读 多人点赞 2016-07-22 17:45:43
    远程方法调用RMI(Remote Method Invocation),是允许运行一个Java虚拟机的对象调用运行另一个Java虚拟机上的对象的方法。 这两个虚拟机可以是运行相同计算机上的不同进程中,也可以是运行网络上的不同...

    Java RMI远程方法调用详解

        【尊重 原创,转载请注明出处 】http://blog.csdn.net/guyuealian/article/details/51992182
    一、Java RMI机制: 
            远程方法调用 RMI(Remote Method Invocation),是允许运行在一个Java虚拟机的对象调用运行在另一个Java虚拟机上的对象的方法。 这两个虚拟机可以是运行在相同计算机上的不同进程中,也可以是运行在网络上的不同计算机中。
           Java RMI:Java远程方法调用,即Java RMI(Java Remote Method Invocation)是Java编程语言里,一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能简化远程接口对象的使用。
           而RPC是远程过程调用(Remote Procedure Call)可以用于一个进程调用另一个进程(很可能在另一个远程主机上)中的过程,从而提供了过程的分布能力。Java 的 RMI 则在 RPC 的基础上向前又迈进了一步,即提供分布式对象间的通讯
     (1)RMI框架
           【参考资料】
             《Java网络编程精解》孙卫琴,这本书适合入门学习RMI框架基础   
             http://wenku.baidu.com/view/90171bd03186bceb19e8bbdc.html?from=search
           RMI框架封装了所有底层通信细节,并且解决了编组、分布式垃圾收集、安全检查和并发性等通用问题。有了现成的框架,开发人员就只需专注于开发与特定问题领域相关的各种本地对象和远程对象。

          要了解RMI原理,先了解一下Stub和Skeleton两个概念。
    (2)Stub和Skeleton
          RMI框架采用代理来负责客户与远程对象之间通过Socket进行通信的细节。RMI框架为远程对象分别生成了客户端代理和服务器端代理。 位于客户端的代理类称为存根(Stub),位于服务器端的代理类称为骨架(Skeleton)。


        【 相关资料
          《RMI(Remote Method Invocation)初窥门径》 http://blog.csdn.net/smcwwh/article/details/7080997 
         stub(存根)和skeleton( 骨架 ) 在RMI中充当代理角色,在现实开发中主要是用来隐藏系统和网络的的差异, 这一部分的功能在RMI开发中对程序员是透明的。Stub为客户端编码远程命令并把他们发送到服务器。而Skeleton则是把远程命令解码,调用服务端的远程对象的方法,把结果在编码发给stub,然后stub再解码返回调用结果给客户端。
         RMI远程过程调用的实现过程如下图所示:

         RMI 框架的基本原理大概如下图,应用了代理模式来封装了本地存根与真实的远程对象进行通信的细节

    参考资料
        《Java RMI 框架(远程方法调用)》http://haolloyin.blog.51cto.com/1177454/332426  
        《 java RMI原理详解》 http://blog.csdn.net/xinghun_4/article/details/45787549

    二、Java RMI 简单示例

        别急,慢慢分析~具体代码在下面,附例子代码下载:http://download.csdn.net/detail/guyuealian/9583633
    大致说来,创建一个RMI应用包括以下步骤:
          (1)创建远程接口:继承java.rmi.Remote接口。
          (2)创建远程类:实现远程接口。
          (3)创建服务器程序:创建远程对象,通过createRegistry()方法注册远程对象。并通过bind或者rebind方法,把远程对象绑定到指定名称空间(URL)中。
          (4)创建客户程序:通过 lookup()方法查找远程对象,进行远程方法调用 
         下面具体分析每个步骤:
    (1)创建远程接口:继承java.rmi.Remote接口。
           远程接口中声明了可以被客户程序访问的远程方法。RMI规范要求远程对象所属的类实现一个远程接口,并且远程接口符合以下条件:
           (a)直接或间接继承java.rmi.Remote接口。
           (b)接口中的所有方法声明抛出java.rmi.RemoteException。
    (2)创建远程类:实现远程接口。
            远程类就是远程对象所属的类。RMI规范要求远程类必须实现一个远程接口。此外,为了使远程类的实例变成能为远程客户提供服务的远程对象,可通过以下两种途径之一把它导出(export)为远程对象
           (a)导出为远程对象的第一种方式:使远程类实现远程接口时,同时继承java.rmi.server.UnicastRemoteObject类,并且远程类的构造方法必须声明抛出RemoteException。这是最常用的方式,下面的本例子就采取这种方式。
    public class RemoteImpl extends UnicastRemoteObject implements RemoteInterface
           (b)导出为远程对象的第二种方式:如果一个远程类已经继承了其他类,无法再继承UnicastRemoteObject类,那么可以在构造方法中调用UnicastRemoteObject类的静态exportObject()方法,同样,远程类的构造方法也必须声明抛出RemoteException。
    public class RemoteImpl extends OtherClass implements RemoteInterface{
      private String name;
      public RemoteImpl (String name)throws RemoteException{
        this.name=name;
        UnicastRemoteObject.exportObject(this,0);
      }
    
            在构造方法RemoteImpl 中调用了UnicastRemoteObject.exportObject(this,0)方法,将自身导出为远程对象。
            exportObject()是UnicastRemoteObject的静态方法,源码是:
        protected UnicastRemoteObject(int port) throws RemoteException
        {
            this.port = port;
            exportObject((Remote) this, port);
        }
        public static Remote exportObject(Remote obj, int port)
            throws RemoteException
        {
            return exportObject(obj, new UnicastServerRef(port));
        }
           exportObject(Remote obj, int port),该方法负责把参数obj指定的对象导出为远程对象,使它具有相应的存根(Stub),并监听远程客户的方法调用请求;参数port指导监听的端口,如果值为0,表示监听任意一个匿名端口。
    (3)创建服务器程序:创建远程对象,在rmiregistry注册表中注册远程对象,并绑定到指定的URL中。
           RMI采用一种命名服务机制来使得客户程序可以找到服务器上的一个远程对象。在JDK的安装目录的bin子目录下有一个rmiregistry.exe程序,它是提供命名服务的注册表程序。
          服务器程序的一大任务就是向rmiregistry注册表注册远程对象。从JDK1.3以上版本开始,RMI的命名服务API被整合到JNDI(Java Naming and Directory Interface,Java名字与目录接口)中。在JNDI中,javax.naming.Context接口声明了注册、查找,以及注销对象的方法:
        【1】 bind(String name,Object obj):注册对象,把对象与一个名字name绑定
    ,这里的name其实就是URL格式。如果该名字已经与其它对象绑定,就会抛出NameAlreadyBoundException。
        【2】rebind(String name,Object obj):注册对象,把对象与一个名字绑定。如果该名字已经与其它对象绑定,不会抛出NameAlreadyBoundException,而是把当前参数obj指定的对象覆盖原先的对象。
        【3】 lookup(String name):查找对象,返回与参数name指定的名字所绑定的对象。
        【4】unbind(String name):注销对象,取消对象与名字的绑定。
         注册一个远程对象remoteObj2 关键代码如下:
    RemoteInterface remoteObj2 = new RemoteImpl();// 创建远程对象
    Context namingContext = new InitialContext();// 初始化命名内容
    LocateRegistry.createRegistry(8892);// 在本地主机上创建和导出注册表实例,并在指定的端口上接受请求
    namingContext.rebind("rmi://localhost:8892/RemoteObj2", remoteObj2);// 注册对象,即把对象与一个名字绑定。
         但在JDK1.3版本或更低的版本,需要使用java.rmi.Naming来注册远程对象,注册代码代替如下:
    RemoteInterface remoteObj = new RemoteImpl();
    LocateRegistry.createRegistry(8891);
    Naming.rebind("rmi://localhost:8891/RemoteObj", remoteObj);
    (4)创建客户程序:通过 lookup()方法查找远程对象,进行远程方法调用
    关键代码如下:
    Context namingContext = new InitialContext();// 初始化命名内容
    RemoteInterface RmObj2 = (RemoteInterface) namingContext.lookup("rmi://localhost:8892/RemoteObj2");//获得远程对象的存根对象
    System.out.println(RmObj2.doSomething());//通过远程对象,调用doSomething方法
         在JDK1.3版本或更低的版本,如下方式调用;
    RemoteInterface RmObj = (RemoteInterface) Naming.lookup("rmi://localhost:8891/RemoteObj");
    System.out.println(RmObj.doSomething());
    例子 具体 代码:
       1. 创建远程接口:继承java.rmi.Remote接口。
    package rmi;
    import java.rmi.Remote;
    import java.rmi.RemoteException;
    //声明一个远程接口RemoteInterface,该接口必须继承Remote接口
    //接口中需要被远程调用的方法,必须抛出RemoteException异常
    public interface RemoteInterface extends Remote {
    	// 声明一个doSomething方法
    	public String doSomething() throws RemoteException;
    	// 声明一个计算方法Calculate
    	public int Calculate(int num1, int num2) throws RemoteException;
    }
           在Java中,只要一个类extends了java.rmi.Remote接口,即可成为存在于服务器端的远程对象, 供客户端访问并提供一定的服务。JavaDoc描述:Remote 接口用于标识其方法可以从非本地虚拟机上调用的接口。任何远程对象都必须直接或间接实现此接口。只有在“远程接口”  (扩展 java.rmi.Remote 的接口)中指定的这些方法才可被远程调用。注意:接口中需要被远程调用的方法,必须抛出RemoteException异常。
          2. 创建远程类:实现远程接口
    package rmi;
    import java.rmi.RemoteException;
    import java.rmi.server.UnicastRemoteObject;
    //实现远程接口RemoteInterface,并继承UnicastRemoteObject
    //注意RemoteObject这个类,实现了Serializable, Remote这两个接口
    public class RemoteImpl extends UnicastRemoteObject implements RemoteInterface {
    	// 这个实现必须有一个显式的构造函数,并且要抛出一个RemoteException异常
    	public RemoteImpl() throws RemoteException {
    	}
    	// 实现doSomething方法
    	public String doSomething() throws RemoteException {
    		return "OK ,You can do......";
    	}
    	// 实现Calculate方法,返回计算结果
    	public int Calculate(int num1, int num2) throws RemoteException {
    		return (num1 + num2);
    	}
    }
          远程对象必须实现java.rmi.server.UniCastRemoteObject类,该类的构造函数中将生成stub和skeleton, 这样才能保证客户端访问获得远程对象时,该远程对象将会把自身的一个拷贝以Socket的形式传输给客户端,此时客户端所获得的这个拷贝称为Stub( 存根), 而服务器端本身已存在的远程对象则称之为Skeleton(骨架) 其实此时的存根是客户端的一个代理,用于与服务器端的通信,  而骨架也可认为是服务器端的一个代理,用于接收客户端的请求之后调用远程方法来响应客户端的请求。
         3.创建服务器程序:在rmiregistry注册表中注册远程对象,向客户端提供远程对象服务  
    package rmi2;
    import javax.naming.Context;
    import javax.naming.InitialContext;
    
    import rmi.RemoteInterface;
    
    public class ClientTest {
    	public static void main(String args[]) {
    		try {
    			Context namingContext = new InitialContext();// 初始化命名内容
    			RemoteInterface RmObj2 = (RemoteInterface) namingContext
    					.lookup("rmi://localhost:8892/RemoteObj2");//获得远程对象的存根对象
    			System.out.println(RmObj2.doSomething());//通过远程对象,调用doSomething方法
    			System.out.println("远程服务器计算结果为:" + RmObj2.Calculate(90, 2));
    		} catch (Exception e) {
    		}
    	}
    }
       在JDK1.3版本或更低的版本,如下方式注册;
    package rmi;
    import java.net.MalformedURLException;
    import java.rmi.AlreadyBoundException;
    import java.rmi.Naming;
    import java.rmi.RemoteException;
    import java.rmi.registry.LocateRegistry;
    //在JDK1.3版本或更低的版本,需要使用java.rmi.Naming来注册远程对象
    //创建RMI注册表,启动RMI服务,并将远程对象注册到RMI注册表中。
    public class RMIServer {
    	public static void main(String args[])
    			throws java.rmi.AlreadyBoundException {
    
    		try {
    			// 创建一个远程对象RemoteObj,实质上隐含了是生成stub和skeleton,并返回stub代理引用
    			RemoteInterface remoteObj = new RemoteImpl();
    
    			// 本地创建并启动RMI Service,被创建的Registry服务将在指定的端口,侦听请求
    			// Java默认端口是1099,缺少注册表创建,则无法绑定对象到远程注册表上
    			LocateRegistry.createRegistry(8891);
    
    			// 把远程对象注册到RMI注册服务器上,并命名为RemoteObj(名字可自定义,客户端要对应)
    			// 绑定的URL标准格式为:rmi://host:port/name(其中协议名可以省略,下面两种写法都是正确的)
    			Naming.rebind("rmi://localhost:8891/RemoteObj", remoteObj);// 将stub代理绑定到Registry服务的URL上
    			// Naming.bind("//localhost:8880/RemoteObj",remoteObj);
    
    			System.out.println(">>>>>INFO:远程IHello对象绑定成功!");
    		} catch (RemoteException e) {
    			System.out.println("创建远程对象发生异常!");
    			e.printStackTrace();
    		} catch (MalformedURLException e) {
    			System.out.println("发生URL畸形异常!");
    			e.printStackTrace();
    		}
    	}
    }
         RMIServer类主要实现注册远程对象,并向客户端提供远程对象服务。远程对象是在远程服务上创建的,你无法确切地知道远程服务器上的对象的名称 。但是,将远程对象注册到RMI Service之后,客户端就可以通过RMI Service请求到该远程服务对象的stub了,利用stub代理就可以访问远程服务对象了 。
          4. 客户端代码
         Server端的代码已经全部写完,这时把服务器的接口RemoteInterface打包成jar,以便在Client端的项目 使用。
         项目-->右键-->导出-->jar->选择RemoteInterface.java-->finish;关于客户端如何导入jar包,请看这里:http://jingyan.baidu.com/article/ca41422fc76c4a1eae99ed9f.html

    package rmi2;
    import javax.naming.Context;
    import javax.naming.InitialContext;
    
    import rmi.RemoteInterface;
    public class ClientTest {
    	public static void main(String args[]) {
    		try {
    			Context namingContext = new InitialContext();// 初始化命名内容
    			RemoteInterface RmObj2 = (RemoteInterface) namingContext
    					.lookup("rmi://localhost:8892/RemoteObj2");//获得远程对象的存根对象
    			System.out.println(RmObj2.doSomething());//通过远程对象,调用doSomething方法
    			System.out.println("远程服务器计算结果为:" + RmObj2.Calculate(90, 2));
    		} catch (Exception e) {
    		}
    	}
    }
    在JDK1.3版本或更低的版本,如下方式调用
    import java.net.MalformedURLException;
    import java.rmi.Naming;
    import java.rmi.NotBoundException;
    import java.rmi.RemoteException;
    
    public class ClientTest {
    	public static void main(String args[]) {
    		try {
    			// 在RMI服务注册表中查找名称为RemoteObj的对象,并调用其上的方法
    			// 客户端通过命名服务Naming获得指向远程对象的远程引用
    			RemoteInterface RmObj = (RemoteInterface) Naming
    					.lookup("rmi://localhost:8881/RemoteObj");
    			System.out.println(RmObj.doSomething());
    			System.out.println("远程服务器计算结果为:" + RmObj.Calculate(1, 2));
    		} catch (NotBoundException e) {
    			e.printStackTrace();
    		} catch (MalformedURLException e) {
    			e.printStackTrace();
    		} catch (RemoteException e) {
    			e.printStackTrace();
    		}
    	}
    }
    输出结果:
    OK ,You can do......
    远程服务器计算结果为:92
    例子代码下载:http://download.csdn.net/detail/guyuealian/9583633




    如果你觉得该帖子帮到你,还望贵人多多支持,鄙人会再接再厉,继续努力的~

    展开全文
  • 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;  2. 信号(Signal):信号...

    一、IPC进程间通信

    IPC是进程间通信方法的统称,Linux IPC包括以下方法,Android的进程间通信主要采用是哪些方法呢?

       1. 管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;
       2. 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);
       3. 报文(Message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
       4. 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
       5. 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
       6. 套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

     

          "Android虽然构建在Linux上面,但是在IPC(进程间)机制方面,没有利用Linux提供IPC机制,而是自己实现了一套轻量级的IPC机制——binder机制。并且Android Binder机制之上,Android框架提供了一套封装,可以实现对象代理在本地进程中代理远程进程的对象 )。“

         “ Google为什么要采用这种方式呢,这取决于Binder通信方式 的高效率。 Binder通信是通过linux的binder driver来实现的,Binder通信操作类似线程迁移(thread migration),两个进程间IPC看起来就象是一个进程进入另一个进程执行代码然后带着执行的结果返回 。Binder的用户空间为每一个进程维护着 一个可用的线程池,线程池用于处理到来的IPC以及执行进程本地消息,Binder通信是同步而不是异步。"

     

    二、AIDL

    AIDL - Android Interface Definition Language - Android 接口定义语言

    因为在Android中,应用程序运行在各自独立的进程里。应用程序之间是不能访问对方的内存空间的。有时为了实现进程间的通信,要用到PCI机制。Android支持PCI机制,但是需要Android能读懂的序列化数据(marshaling/un marshaling of data).

        AIDL就是为了描述这样的数据产生的,它是一种接口定义语言。语法类似JAVA,.aidl文件里面写的是公布给客户端接口声明。

    三、客户端 服务器

    这种方式主要基于远程代理设计模式,适合长时间的面向连接的直接对象方法调用。基本思想是在Client端和 Server端都增加一个代理(客户端称之为Stud,Server端称之为skeleton),调用请求由Stub封装并基于底层传输机制(IPC或者 网络等)传送到skeleton,skeleton解封装后,调用Server的方法,然后再反方向返回。 这种方式,可以使得Client对于远程对象的范围,就如同本地对象一样。

     

    步骤

    1.编写.aidl文件,声明是公布给客户端的接口。

    2.在Service类里实现接口。

    3.远程调用方调用接口方法。

    四、实例

    (1)定义AIDL,声明是公布给客户端的接口。

    /src/com.marakana/IAdditionService.aidl

     

    (2)实现远程服务器的方法

    OnBind方法返回的IBind对象是要供远程调用方使用的。

    如果你实现了.aidl文件,那么eclipse+aidl工具会自动为你生成一个同名的.java文件(IAdditionService.java)。

    /src/com.marakana/AdditionService.java

     

     

    (3) 远程调用方调用接口方法

    我们用AIDLDemo Activity作为远程调用方来调用AdditionService的add方法。

    先要实现一个ServiceConnection的类,重写onServiceConnected() 方法和 onServiceDiconnected()方法来与服务器建立和断开服务器。

    /src/com.marakana/AIDLDemo.java

     

    (4)界面布局

    /res/layout/main.xml

     

    参考

    http://my.unix-center.net/~Simon_fu/?p=875

    http://topic.csdn.net/u/20100625/10/D7B0CB89-EBAE-4218-A9A5-4D8226B8B6B2.html

    http://marakana.com/forums/android/examples/48.html

    http://www.javaeye.com/topic/655866

     

     

     

     

     

    展开全文
  • RPC远程调用

    千次阅读 2013-08-06 14:38:42
    第十一章 RPC远程过程调用     顾客服务员模型进程...但是计算机网络系统,这种调用可能不同的机器上执行,因此称为远程过程调用(remote procedure call)。远程过程调用的基础是XDR协议。   1

     

    第十一章 RPC远程过程调用

          

     

    在顾客服务员模型中,进程之间的相互作用是由一个进程先向另一个进程发送一个报文请求服务,然后等待回答;服务进程接收一个请求,然后发送回答。这样一种交互作用很象通常意义的过程调用。但是在计算机网络系统中,这种调用可能在不同的机器上执行,因此称为远程过程调用(remote procedure call)。远程过程调用的基础是XDR协议。

     

    11.1 XDR标准

     

    11.1.1 数据结构传输的问题

     

           在异构的网络系统中,在顾客进程和服务器进程之间可能需要传递一些复杂的数据结构,这些数据结构可能用于控制进程的行为或者返回进程处理的结果。在数据结构传输过程中可能存在的问题有:

           1.网络字节序问题

           不同类型的计算机系统对于数据的存储格式可能不同,例如对于一个整数intPC机存储时低位字节在前,而高位字节在后;而Sun工作站存储时是低位字节在后,而高位字节在前。这将导致它们对相同整数的2进制序列理解不同。

           2.浮点数的传递

           浮点数的传递比整数更加困难,通常浮点数使用若干比特表示整数部分,其它比特表示小数部分。不同类型的浮点数floatdouble,它们使用的比特数不同,这使得在网络中传递它们有一定的困难。

           对于浮点数的处理,用户可以将浮点数前后的两个部分分别看成两个整数,分别进行传递,也可以将浮点数看成字符串的形式传递。

           3.指针的处理

           在数据结构传递中,指针的传递是最困难的,因为指针的含义是本机上存放某个数据的地址,这个地址在远端的主机上没有意义。所以用户必须传递的是指针的内容而不是指针本身。例如,对于一个字符串指针,用户需要将字符串的内容包含在数据内容中,同时还需要包含字符串的长度信息。

     

    11.1.2 XDR标准

     

           数据类型的传输可以多种多样,用户可以使用自己定义的规则,满足应用程序的数据结构传递。但是如果要使网络程序能够很好地同其它网络程序互通,则需要遵循一个公共的标准。在数据传递过程中实际使用的标准是Sun microsystem设计的XDR标准。

     

    11.1.2.1 XDR标准中包含的数据类型

     

           Sunmicrosystem设计的XDR标准规定了在网络中传输数据如何表示成公共的形式,它已经成为大多数顾客服务员应用中的事实上的标准。在XDR标准中定义了表11-1中的数据类型。

    11-1 XDR标准中的是数据类型

    数据类型

    长度( bits)

    含义

    int

    32

    32比特的2进制符号整数

    unsigned int

    32

    32比特的2进制无符号整数

    bool

    32

    布尔值,用1或0表示

    enum

    任意

    枚举类型,值被定义成常数

    hyper

    64

    64比特的2进制符号整数

    unsigned  hyper

    64

    64比特的2进制无符号整数

    float

    32

    单精度浮点数

    double

    32

    双精度浮点数

    opaque

    任意

    不对这样的字节序列进行转化

    string

    任意

    ASCII字符串

    fixed array

    任意

    任何其它数据类型的定长数组

    counted  array

    任意

    数组中的类型有一个固定上界,但各个数组的上限大小不同

    structure

    任意

    数据的聚合,类似C语言中的结构

    discriminated  union

    任意

    类似C语言中的union,可以在几种形式中选择一种数据类型

    void

    0

    如果数据项可选,它又没有给出具体数据,则使用这种类型

    symbolic  constant

    任意

    一个符号常量及相关值

    optional  data

    任意

    允许一个数据出现0次或1次

     

           XDR标准中的数据类型和C语言中的数据类型非常相似,XDR允许有结构数组,结构中可以有多个字段,每个字段成员可以是一个数组、结构或者联合,它完全能够适应复杂数据结构的传递。

     

    11.1.2.2 XDR实现的原理

     

           XDR对各种数据类型规定了编码的方式。用户可以使用函数xdrmem_create在内存中创建一个XDR流来存放用户将要发送的数据结构。在初始化后的XDR流包含一个流的头部,函数xdrmem_create的使用方法如下:

           #include<rpc/xdr.h>

           externvoid xdrmem_create ((XDR *xdrs, const caddr_t addr, u_int size, enum xdr_opxop));

           其中,变量xdrs是创建XDR流的指针,变量addr是内存中用于存放XDR流空间的起始地址,变量size是这个空间的长度,变量xop是说明对函数xdrmem_create调用的操作,其定义如下:

           enumxdr_op  {

                         XDR_ENCODE=0

                         XDR_DECODE=1

                         XDR_FREE=2

                         };

           如果变量xopXDR_ENCODE,则创建一个用于发送的XDR流;如果变量xopXDR_DECODE,则创建一个用于接收的XDR流;如果变量xopXDR_FREE,则释放这个XDR流。

           随后假如用户调用相应的函数来填写一个整数0x00000004XDR流的结果如图11-1所示。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     


           XDR在编码中并没有提供关于数据类型的信息。例如,当XDR对一个32比特的整数进行编码时,编码的结果仍然是32比特,所以,只有数据的接收者知道这32比特的数据类型时,数据的接收者才能正确地恢复这项数据。因此,用户必须配对地编写发送和接收函数,分别处理每个数据项。

     

    11.1.2.3 XDR的转换函数库

     

           1.转换函数库

           XDR库函数中提供了对各种数据类型进行编码和解码的函数。这些函数具体进行编码还是解码,不是由这些函数决定的,而是由函数中XDR流的性质决定。当XDR流被创建为编码流,则这些函数将对数据进行编码处理;当XDR流被创建为解码流,则这些函数对数据进行解码处理。

           11-2列出了XDR库函数中进行类型转化的函数。用户在调用这些函数之前,必须包含头文件rpc/xdr.h

    11-2 XDR中类型转换函数

    函数调用

    说明

    extern bool_t  xdr_void((void));

    xdr_void用于处理没有数据的空选项

    extern bool_t  xdr_short((XDR *xdrs, short *sp));

    xdr_short用于处理short类型数据。xdrsXDR流指针,sp是指向存放short类型数据空间的指针

    extern bool_t  xdr_u_short((XDR *xdrs, u_short *usp));

    xdr_u_short用于处理u_short类型数据。xdrsXDR流指针,usp是指向存放u_short类型数据空间的指针

    extern bool_t xdr_int((XDR  *xdrs, int *ip));

    xdr_int用于处理int类型数据。xdrsXDR流指针,ip是指向存放int类型数据空间的指针

    extern bool_t  xdr_u_int((XDR *xdrs, int *up));

    xdr_u_int用于处理u_int类型数据。xdrsXDR流指针,up是指向存放u_int类型数据空间的指针

    extern bool_t xdr_long((XDR  *xdrs, long *lp));

    xdr_long用于处理long类型数据。xdrsXDR流指针,lp是指向存放long类型数据空间的指针

    extern bool_t  xdr_u_long((XDR *xdrs,u_long *ulp));

    xdr_u_long用于处理u_long类型数据

    extern bool_t  xdr_hyper((XDR *xdrs,u_quad_t *llp));

    xdr_hyper用于处理hyper64比特类型数据。llp是指向hyper类型的指针

    extern bool_t  xdr_u_hyper((XDR *xdrs,u_quad_t *ullp));

    xdr_u_hyper用于处理u_hyper64比特类型数据。ullp是指向u_hyper类型的指针

    extern bool_t xdr_longlong_t((XDR  *xdrs, quad_t *llp));

    类似函数xdr_hyper

    extern bool_t xdr_u_longlong_t((XDR  *xdrs, u_quad_t *llp));

    类似函数xdr_u_hyper

    extern bool_t xdr_int8_t((XDR  *xdrs, int8_t *ip));

    用于处理8比特符号整数类型数据

    extern bool_t xdr_uint8_t((XDR  *xdrs, uint8_t *up));

    用于处理8比特无符号整数类型数据

    extern bool_t xdr_int16_t((XDR  *xdrs, int16_t *ip));

    用于处理16比特符号整数类型数据

    extern bool_t xdr_uint16_t((XDR  *xdrs, uint16_t *up));

    用于处理16比特无符号整数类型数据

    extern bool_t xdr_int32_t((XDR  *xdrs, int32_t *ip));

    用于处理32比特符号整数类型数据

    extern bool_t xdr_uint32_t((XDR  *xdrs, uint32_t *up));

    用于处理32比特无符号整数类型数据

    extern bool_t xdr_int64_t((XDR  *xdrs, int64_t *ip));

    用于处理64比特符号整数类型数据

    extern bool_t xdr_uint64_t((XDR  *xdrs, uint64_t *up));

    用于处理64比特无符号整数类型数据

    extern bool_t xdr_bool((XDR  *xdrs,bool_t *bp));

    用于处理bool_t类型数据

    extern bool_t xdr_enum((XDR  *xdrs,enum_t *ep));

    用于处理枚举类型数据

    extern bool_t xdr_array((XDR  *xdrs,caddr_t *addrp, u_int *sizep, u_int maxsize, u_int elsize, xdrproc_t  elproc));

    用于处理数组类型数据

    extern bool_t xdr_bytes((XDR  *xdrs, char **cpp, u_int *sizep, u_int maxsize));

    用于处理字节类型数据

    extern bool_t xdr_opaque((XDR  *xdrs, caddr_t cp, u_int cnt));

    用于处理opaque类型数据

    extern bool_t xdr_string((XDR  *xdrs, char **cpp, u_int maxsize));

    用于处理字符串类型数据

    extern bool_t xdr_union((XDR  *xdrs, enum *dscmp, char *unp, const struct xdr_discrim *choices, xdrproc  dfault));

    用于处理联合类型数据

    extern bool_t xdr_char((XDR  *xdrs,char *cp));

    用于处理字符类型数据

    extern bool_t xdr_u_char((XDR  *xdrs,u_char *cp));

    用于处理u_char类型数据

    extern bool_t xdr_vector((XDR  *xdrs,char *basep, u_int nelem, u_int elemsize, xdrproc_t xdr_elem));

    用于处理vector类型数据

    extern bool_t xdr_float((XDR  *xdrs,float *fp));

    用于处理float类型数据

    extern bool_t xdr_double((XDR  *xdrs,double *dp));

    用于处理double类型数据

    extern bool_t xdr_reference((XDR  *xdrs,caddr_t *xpp, u_int size, xdrproc_t roc));

    用于处理reference类型数据

    extern bool_t xdr_pointer((XDR  *xdrs,char **objpp, u_int obj_size, xdrproc_t xdr_obj));

    用于处理pointer类型数据

    extern bool_t xdr_wrapstring((XDR  *xdrs,char **cpp));

    用于处理wrapstring类型数据

    extern u_long xdr_sizeof((xdrproc_t,  void *));

    获得数据类型的长度

     

           上表中的函数都需要一个已经创建好的XDR流作为操作对象,并且都返回一个bool_t类型说明操作是否成功。关于这些函数的具体使用方法可以参见函数的帮助手册。

           2XDR工作方式

           实际上XDR库函数中提供了2XDR流的支持,即工作在内存的XDR流和工作在I/OXDR流。

           调用函数xdrmem_create,它可以用于在内存中创建一个XDR流,用户可以将需要传递的数据结构在这个XDR编码流中编码,而后使用系统调用将编码发送到套接口缓冲区中,当接收方收到这些数据后也在内存中创建XDR解码流,并从套接口中将数据复制到这个XDR流中,接着使用对应的函数进行解码,得到原来的数据结构。

           11-2说明了使用内存XDR的工作过程。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     


           函数xdrstdio_create用于创建工作在I/O上的XDR流,函数的使用形式如下:

                         #include<rpc/xdr.h>

                         externvoid xdrstdio_create((XDR *xdrs, FILE *file, enum xdr_op xop));

           该函数中,xdrs是指向创建的XDR流的指针,file是用于输入/输出的文件流,xop是操作选项,它的取值同xdrmem_create函数中的xop类似。

           I/OXDR流可以将编码/解码的结果使用系统提供的标准输出函数输出到文件流中,或者通过标准输入函数库,从文件流中读取编码/解码的结果。

           11-3说明了I/O XDR流的工作过程。

     

     

     

     

     

     

     

     

     

     

     

     


           实际上,对于套接口描述字,可以使用fdopen把它转化成对应的I/O流形式,函数fdopen的使用方法如下:

                         #include<stdio.h>

                         FILE*fdopen(int fd);

           调用函数fdopen之后,应用程序每次调用一个XDR的转化函数,则转化函数将使用文件流指针所包含的描述符,自动完成一个带缓冲的write/read操作,将数据发送到套接口缓冲区,或者从套接口缓冲区中读取数据,这样,可以不需要进行显示的write/read系统调用,如图11-4所示。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     


           3.面向记录的XDR

           上面的两种XDR都是以流方式工作的,由于XDRTCP都是流的抽象,所以XDR可以同TCP很好的结合,但是UDP提供的是面向记录的数据抽象,因此为了使XDRUDP能够很好的结合,XDR库中还提供了面向记录的XDR抽象。

           总之,XDR标准中定义了同C语言中类似的数据类型,并提供了相应的转化函数,XDR流包含内存XDR流、I/O XDR流和具有记录定义能力的XDRXDR的转化函数所做的操作决定于XDR流本身的性质,当XDR流是编码流时,转化函数进行编码的工作;如果XDR流是解码流时,转化函数进行解码操作。XDR标准是RPC远程过程调用的基础。

     

    11.2 远程过程调用(RPC)的原理

     

    11.2.1 分布式数据处理方法

     

           在计算机网络中,数据可以分布式地存放,数据的存放地点和数据的处理地点可能不在同一主机上。一种方法是将数据从数据的存放主机发送到对数据进行处理的主机上,而后在数据处理主机上进行数据处理。这种方法在需要传输的数据量较小时性能较好,但是如果数据量很大,这种方式将消耗大量的网络资源。并且可能由于网络的不可靠性,间接地影响应用程序的不可靠性。

           另一种方法是前台的进程仅仅做一些同顾客相关的界面的处理,还有一些诸如输入数据的有效性检测等事务,而将有效的数据输入转化成请求,并将请求发送到数据处理主机上,数据处理主机对数据进行处理后将数据的处理结果发送回来。两种方法的工作过程如图11-5所示。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     


           RPC基于后一种方法。它希望达到的目的是将网络通信的功能和应用的需求分离开。由于通信协议的复杂性,因此它需要设计报文格式,指明进程收到每个报文后应当如何处理。协议需要严格的协议认证,以确保通信协议的可靠性。

     

    11.2.2 RPC系统组成及特点

     

           在顾客服务员模型中,进程之间相互作用是由一个进程先向另一个进程发送一个报文请求服务,然后等待回答;服务进程接收一个请求,经过处理后发送回答。这样一种交互作用很象通常意义的过程调用。但是在计算机网络系统中,这种调用可能在不同的机器上执行,所以叫远程过程调用(remote procedure call)。

    在进行远程过程调用时,调用过程暂停执行,将参数经由网络送至被调用者执行此过程调用,执行结束后将结果返回给调用者,调用者恢复执行。整个过程就象发生在本地一样。远程过程调用原语是在报文传递原语基础上产生的。把可靠的阻塞原语SENDRECEICE结合起来成为SEND-GET,用于顾客进程向服务员进程发送一个请求,之后等待服务员进程的回答。GET-REQUEST用于服务员得到一个报文,报文告诉服务员要做的工作。当服务员完成该工作时,用原语SEND-REPLY发回一个回答报文。远程过程调用可以看作是由这些原语结合起来完成的过程,具有对用户更方便的句法。

    远程过程调用是基于进程相互作用的顾客服务员模型之上的同步通信的一种形式。顾客为了得到一个服务员(Server)的某种服务(Service)的结果(result),可以使用下面的原语:

                  CALL(Server:…;Service:…; var result:…; var status:…; time-out:…);

           其中status是调用执行情况,可能是成功(OK),或未执行(not-done),或者不能执行(absent)等等;time-out是一个参数,指定允许顾客可以等待的最长时间;statusresult和参量均用数值传递。

           远程过程调用与报文传递方式相比有许多优点:语义清楚、简单、容易使用;对通信来说也非常简单,有较高的效率;有通用性,象在单机上计算时,过程就象是算法两个部分之间进行通信的最重要的机构一样。

    远程过程调用的程序由五部分组成:用户程序、用户代理程序、RPC通信软件包、服务员代理程序和服务员程序,见图11-6

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     


           通信程序包分成两部分,分别位于顾客机和服务员机。用户进行远程过程调用时,只需进行通常的本地调用,引起用户代理过程运行,后者将目标过程的说明和参数装入一个或几个调用包内,交给通信程序发送给被调用服务员。被调用服务员上的通信程序收到调用包后交给服务员代理程序,后者把参数取出后进行通常的本地调用。服务员完成这一调用,把结果返回给服务员代理程序进行打包,再交给通信程序发送给顾客机的通信程序。这是顾客机上的通信程序正等待接收这一结果包,然后把它交给用户代理程序拆包,把结果交给用户。在此期间,顾客进程暂停运行,等待返回结果。通信程序负责重发、确认、包的路径选择和数据加密等等。如果不考虑多机和通信失效,调用就好象用户直接调用服务员中的过程一样。事实上,如果服务员和顾客程序都在一个机器上,去掉代理程序仍能正常工作。

    RPC实际上拓宽了“调用”。用户可以使用某些定义好的信息格式来保存被调用过程需要的参数,并在信息格式中说明应当如何去找到被调用者(通常是某种标志)。然后通过网络将信息报文发送到被调用者所在的机器,调用者等待被调用者发回调用结果。

    在被调用者的那台机器上,应当有一个分派器,这个分派器知道它所控制的所有的远程过程。当它收到报文后,通过标志可以知道调用者希望调用哪个远程过程。然后,它从报文中取出被调用者需要的参数,并将参数传递给被调用者。

    当被调用者被调用后,它在它的环境中运行,并将运行的结果写在信息格式中,最后通过网络将信息返回。

     

    11.2.3 实现RPC要解决的问题

     

           实现RPC要解决的一个问题是如何在报文中表示不同机型的参数和结果。不同型号的机器,其浮点数和负数的表示以及字节次序可能不同;各机器语言可能用不同的句法和不同的语义表示相似的数据结构;各机器语言可能支持不同的数据结构和过程接口。使用RPC进行通信时,必须在各不同机器之间进行转换工作。其转换工作量可能很大。对于有很多型号机器的系统,常使用一种标准格式,每种机型中的表示都转换成标准格式。但如果双方都使用同一种表示,这种转换就有些浪费。如果使用另一种方法,发送者使用自己的内部格式,让接收者转换成自己的内部表示,那么每种机器必须对其它种机器的每一种进行转换,当机种很多时工作量很大。而且当新机种加入时,很多现有的软件都必须加以补充。

           再一个问题是RPC透明性带来的服务员定位问题。例如在具有多个文件服务员的系统中,如果一个用户在某个服务员上创建一个文件,则通常希望以后的操作(例如写操作)都对该服务员上的那个文件进行。但使用RPC时,该用户只在其程序上设一个过程调用:

                  write(FileDestriptor,BufferAddress, ByteCount);

           RPC设备试图对用户隐去所有的关于服务员定位的细节。而在这种情况下,这些细节却是十分重要的。

           还有关于广播通信问题。很多应用场合,例如确定某个进程或某种服务的位置,需要广播通信和组通信,即向所有的或若干个目的地而不只是一个目的地发送查询报文,并等待回答。RPC本身并不提供这种机制,语义也是完全不同的。因为RPC是一种同步通信,发送者必须等待每个接收者全部接收完毕。这不仅要等待很长时间,而且发送者并不一定知道共有多少个接收者接收并处理完毕的时间。

           还有可靠性语义问题。在计算机网络系统中,RPC并不能总是得到正确的回答,因为通信双方(顾客和服务员)都可能失效,通信线路也可能失效。顾客在向服务员发出一个RPC后,如果由于服务员或信道的故障未得到响应,一定时间后重发RPC;当服务员工作正常而只是顾客未收到回答时,服务员可能重复执行RPC操作;如果顾客在发出RPC后出现故障,而服务员继续执行这一RPC,则成为“孤儿”,顾客恢复后又可能重发这一RPC。从而服务员再次执行这一RPC

    在实际应用场合中,RPC的重复执行有时无害,有时是不允许的。例如顾客要求服务员读取某个文件,即使服务员在故障前后执行两次也没什么关系,这叫作重复等效(idempotent)的操作。但是如果一个用户要求从某个银行帐户向另一个帐户转移一笔钱的操作,则要求准确地执行一次,不能执行多次或者不执行。工厂自动生产过程中某些开关的控制要求恰好执行一次RPC,否则会出现故障。所以,发生RPC的多次执行的系统并不总是符合实际应用的需求。因此必须区别不同情况的RPCRPC可能执行:

    恰好一次(Exactly-Once):每次调用时只精确地执行一次,具有和本地过程调用相同的语义;

    至少一次(At-Least-Once):用户代理重复发出RPC一直到服务员至少执行了一次;

    最多一次(At-Most-Once):通信双方及信道无故障时恰好执行一次并返回一次结果;如果发现服务员崩溃,用户代理将放弃并返回一个错误码,不进行重发。在此情况下,顾客知道操作可能执行0次或一次,不会再多。进一步的恢复由顾客负责。

    多次中最后一次语义(Last-of-Many-Call)。这种语义要求给一个调用的每次请求一个顺序号。顾客只接受最近一次请求的返回值。

    等效语义(Idempotent)。这种语义可以使用有状态服务员,也可以使用无状态服务员。在有多个顾客请求远程过程调用的情况下,这种语义保证不会对结果构成不利影响。对于有状态服务员,服务员所保持的状态不会被多个顾客的请求所破坏。

    为了使RPC具有“恰好执行一次”或“最多执行一次”的语义。可在顾客的请求报文中设置顺序号,它应与返回结果报文中的顺序号一致。所有重发的报文包含相同的顺序号。服务员拒绝执行具有相同顺序号的请求,或以前的顺序号的请求。为了解决“孤儿”问题,服务员应能检测“孤儿”的产生,并在接受新的RPC之前退回到初始状态(不执行那个RPC操作)。

     

    11.3 RPC的实现

     

    11.3.1 本地函数调用的过程

     

           本地的函数调用,通常是将函数的传入参数压进函数栈,然后将函数的返回地址压入函数栈中,最后在函数栈为函数的局部变量分配空间,并将程序计数器(PC)指向被调用的函数的入口地址(即调用函数在正文段中的位置),接着开始调用函数。函数调用的栈结构如图11-7所示。

     

     

     

     

     

     

     

     

     


           在函数调用结束后将函数的局部变量弹出栈,并用函数的返回地址设置PC,而后程序将控制权返回到调用者。

           如果从较高的抽象层来考虑函数调用,就可以发现调用的关键步骤:

    1. 将被调用函数需要的参数准备好,并通过某种方式使被调用函数可以访问到。在本地函数调用中,它通过函数栈来实现。

    2. 必须包含函数的返回信息,在被调用函数结束后,可以将控制权重新交给调用者。在本地函数调用中,这一点通过指明函数的返回地址来实现。

    3. 能够确定被调用者的位置,也就是说,调用者需要通过某种方式说明被调用者的位置。在本地函数调用中,这一点通过将PC指针指向被调用函数的入口地址来实现。

    4. 必须为被调用函数创建它可以运行的环境,例如函数的需要访问的数据和变量,以及函数的代码等是函数可以访问到的。在本地函数调用中,这一点是这样实现的:被调用函数的局部变量在函数栈中分配,而其它需要的数据可以共享进程中数据(如果具有权限)。

    经过上面的抽象,可以发现只要满足这4点要求,“调用”就可以实现。而这4点要求其实与具体实现无关。本地过程调用只是上面4点原则的一个具体实现。如果实现了一种新的方式,则一种新的调用就产生了。

    因此,“调用”的概念可以进一步拓宽,本地调用只是“调用”的一种具体实现形式。用户只要准备好被调用者需要的参数,然后找到它,为被调用者创建好运行的环境,然后运行它,最后通过已经说明了的返回方式将控制权移交,将返回参数正确传递回来,这就完成了一个“调用”。至于数据应当以什么方式存放,数据应当怎么传送给被调用者,被调用者应当如何识别等,这些都是具体的实现细节的问题。

     

    11.3.2 远程过程的标识

     

           RPC的实现也贯穿前面介绍的4个调用的原则。RPC的具体实现就是对4个原则的实现。远程过程的标识实际上就是引导如何能够找到远程过程。通常将一组相关的远程调用编写在一个程序中,然后通过一个分派器来管理它们。所以,标志一个远程调用过程需要(程序号、过程号),程序号是这组远程调用过程所在程序的名称,过程号是具体的过程在程序中的索引号。

           由于程序可能会不断地升级,所以还可以在标识中加入一个过程的版本号,这样做有两点好处:

    1. 当远程过程的版本升级时,应用程序仅仅需要改变调用的远程过程的版本号,就可以使用新的版本来工作。应用程序的改变非常小。

    2. 有的应用程序可能需要使用旧版本的远程过程调用来工作,如果在过程标识中加入版本号,则多个版本的远程过程可以同时并存。这样,旧版本的应用程序不会因为远程调用过程的版本升级而失效。

    这样,远程调用过程的完整标识如下:

    (程序号,远程调用过程的版本号,远程过程的序号)

    ONC RPC的标准中指出:在某个机器上执行的每个远程程序都必须分配一个唯一的32比特整数,调用者通过这个整数来标识这个远程程序。所谓程序名,就是这里所说的32比特整数。

    为了确保不同的组织所定义的程序号不会冲突,ONC RPC将程序号的集合分成8组,供不同的组织使用。Sun公司管理其中的第一组标识,它允许任何人申请一个标准的RPC程序号。Sun在它所管理的标志中分配了一些标准的程序号,如表11-3所示。

     

    11-3 标准的程序号

    程序名称

    程序号

    程序用途

    portmap

    100000

    端口映射器

    nfs

    100003

    网络文件系统

    rusersd

    100002

    远程用户

    Ypserv

    100004

    NIS网络信息系统

    mountd

    100005

    Mount, showmount

    etherstatd

    100010

    以太网统计信息

     

    远程过程的具体描述如下:

    programRMESGPROG {                        /*nameof remote program*/

                         version RMESGVERS  {                  /*versionof program*/

                                int INIT(void) = 1;                     /*first procedure in theprogram*/

                                int ADDMESG(string)=2;           /*second procedure*/

                                int DELMESG(string)= 3;

                                string LOOKMESG(int)= 4;

                                } =2; /*程序的版本号*/

                         } = 0x30091000;                               /*程序唯一的程序号*/

     

    11.3.3 端口的动态映射

     

           由于一台机器上可能同时运行多个远程程序,所以每个远程程序将使用不同的传输层端口。而调用远程程序的应用并不知道它希望调用的远程程序具体使用哪个端口。

           由于给远程程序的程序号是32比特整数,而传输层使用的端口是16比特整数,因此不能将一个远程调用唯一地映射到一个传输层端口上。即使能够做到这样的映射,那也将浪费大量的传输层端口资源。

           某个远程程序运行的端口不是直接提供的,它将由操作系统根据当时传输层端口的使用情况来动态分配。而真正使用固定端口的是端口映射器。而在远程程序提供服务之前,它将向端口映射器注册自己,使端口映射器知道它是谁,它将使用什么端口,具体工作情况如图11-8所示。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     


    所有应用程序希望调用远程程序时,都先向端口映射器发出请求,询问具体的远程程序使用的端口号。端口映射器维持一张表,表中保存本机运行的远程程序和该远程程序使用的端口号。当它收到请求后,将查表来获取端口信息,并返回给发出请求的应用程序。然后应用程序将向具体的远程程序发起调用请求,具体工作情况如图11-9所示。

     

     

     

     

     

     

     

     

     

     

     

     

     

     


    11.3.4 RPC报文

     

           RPC使用XDR语言来定义应用的报文,这样可以为应用提供更大的灵活性。

    1. RPC报文的类型

    使用XDR定义的RPC报文类型如下:

           enum msg_type {  /* RPC 报文的类型*/

                  CALL = 0;

                  REPLY = 1;

           };

    RPC报文的类型只有两种:RPCCALL RPC REPLY;分别用于远程调用和远程调用结果的返回。

           2RPC报文的格式

           RPC报文格式使用XDR语言定义如下:

           structrpc_msg {

                         unsignedint mesgid;  /*使用mesgid来匹配CALLREPLY报文*/

                         unionswitch(msg_type mesgt) {

                                caseCALL:

                                       call_body  cbody;

                                caseREPLY:

                                       rply_bodyrbody;

                         }body;

                  };

           上面的语句声明了一个RPC报文格式,报文中的mesgid用于将调用和调用的返回进行匹配,因为一个应用可能会调用多个远程过程,那么需要一个机制将调用报文和调用结果报文对应起来。报文根据mesgt的值来区分不同的报文体。如果mesgt的值为CALL,也就是调用RPC报文,则RPC报文使用CALL报文体;如果mesgt的值为REPLY,也就是返回RPC报文,则使用REPLY报文体。

           3CALL报文体

           根据前面的原则,CALL报文体中的内容包含远程程序的程序号、远程过程在程序中的过程号、版本信息、调用过程需要的参数。RPC CALL报文体定义如下:

                  structcall_body {

                         unsignedint rpcvers;    /*RPC的版本*/

                         unsignedint rprog;     /*远程程序的程序号*/

                         unsignedint rprogvers;  /*远程程序的版本号*/

                         unsignedint rproc;     /*远程过程的过程号*/

                         opaque_authcred;      /*鉴别信息*/

                         opaque_authverf;      /*鉴别的确证*/

                         /*ARGS*/                    /*过程的参数*/

                  }

           RPC报文为了网络安全而增加了鉴别信息,一个RPC CALL报文的具体形式如图11-10所示。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     


    11.3.5 RPC开发工具

     

           由于ONC RPC协议规程非常复杂,所以虽然可以直接使用报文实现远程过程调用,但是这样做需要耗费大量的时间和精力。因此,系统提供了专门用于开发的工具。这些工具主要包括:

    1. XDR库函数,用于将各种数据结构从本机的表示转化成XDR的标准表示方式,不需要程序员来实现比较复杂的转化工作。

    2. RPC运行时库函数(runtime library),这些库函数将具体实现RPC报文的形成和发送以及其它细节。

    顾客端实现:在顾客端向端口映射器发送请求,并从端口映射器中接收回答;形成CALL报文,并向真正的远程程序发送调用请求,接收来自服务器的调用结果。服务器端实现:在服务器提供服务前向端口映射器注册自己的实际端口;将一个调用分派到具体的调用程序中的一个调用过程。

           实际上它们将实现同RPC相关的大部分工作,程序员使用这些工具可以快速地进行RPC的应用开发。

    3. 一些程序的自动生成工具,它产生一个构件RPC分布式程序所需要的许多C文件,这些程序主要是屏蔽底层通信对应用的影响。

     

    11.3.6 RPC设计的原则

     

           由于RPC调用实际上是实现前面说明的4个原则,所以在RPC设计中,也应当将这4个原则进一步具体化成设计的原则:

    1.   由于RPC通过网络将被调用过程需要的各种参数进行传递,因此虽然RPC可以传递十分复杂的数据结构,例如链表等,但是这将消耗大量的系统和网络资源。所以应当将RPC调用的接口尽量设计成需要传递的参数较少,且传递的数据结构较简单。例如,在传递多个参数时,可以将它们定义在一个结构中,然后传递结构,这样既有利于程序的可读性,也有利于提高程序的效率。

    2.   RPC并没有指定使用的传输层端口,并且TCPUDP都可以使用。当用户使用UDP协议来构建程序时,需要注意UDP协议的不可靠性,对于远程过程的一次调用,将可能导致RPC过程被调用的次数不确定。

    在使用UDP协议通信时,一个RPC请求报文可能丢失,可能会丢失后重传,所以当一个应用进程收到RPC的回应时,RPC可能被调用了一次或者多次。而当一个应用进程没有收到回应报文时,RPC可能被调用了0次或者多次。由于回应的报文也可能丢失,所以在使用UDP协议实现RPC时,用户应当充分考虑这些问题。

    3.   ONC RPC规定:在给定的时刻,一个远程程序中最多可以有一个远程过程被调用,也就是说,在一个给定的程序中,RPC将保证过程调用是互斥的,这一点对于应用端是十分重要的。

    4.   由于远程过程是在远程的机器上运行,所以一定要保证过程中的语句是适合它所在的运行环境。例如printf语句,由于printf语句希望输入来自于同用户相关的终端,而不是RPC过程所运行的环境;还有对本地系统I/O和文件的访问语句,也不应当出现在远程过程中。

    总之,RPC的思想来源于本地过程调用,两者都遵守相同的调用原则,只是实现的手段不同。在构造RPC应用时,首先象编写普通的应用那样进行需求分析,这使得用户不会将注意力过早地放在复杂的通信协议上;而后将应用划分为两部分,分别放在顾客端和提供RPC服务的一端。进行划分时,需要尽量的遵循上面的原则,包括在两端传递的参数应当尽量简单,RPC端必须能满足调用所需要的环境等等。

    用户可以使用系统提供的RPC开发工具加快开发的速度,而直接使用RPC报文的方式来进行应用开发,效率将会很低。

    RPC同时支持TCPUDP协议。对于使用UDP协议的RPC应用,由于使用UDP协议的RPC是不可靠的,因此必须使顾客端的程序能够满足“至多被调用了一次”的语义。

     

    11.4 SUN RPC

     

    第一个RPC软件包是1985年发表的,它是SUN OPEN NETWORK COMPUTING (ONC) RPC,一般称它为SUNRPC。它最初是在SUN OS上实现的,现在也在Solaris操作系统上实现了。SUN RPC支持最多一次的调用语义和等效调用语义。除此之外,它还支持广播RPC和无响应(no-response or batching)RPC。无响应RPC不需要返回值,并常用于修改记录。SUN RPC对参数数目进行了限制,它只允许两个参数,一个是输入参数,一个是输出参数。但是C语言支持结构数据类型,可以将多个参数集合到一个数据结构中作为一个参数传递给远程过程。SUNRPC在三个层次上支持认证,最高层次的认证称为安全RPC并使用DES加密技术。

    我们以计算一个数的平方为例来说明怎样实现一个RPC应用程序。在这个例子中,顾客给出一个整数作为参数调用服务员上的过程,服务员上的过程计算该数的平方,并将结果返回给顾客。实现这个远程过程调用应用的整个过程包括如下步骤。

    (1)编写一个RPC说明文件square.x。RPC说明文件名的后缀为.x,它说明服务员能执行哪些过程,使用哪些参数。square.x文件如下:

    1      struct  square_in {     /* input parameter */

    2            long arg1;

    3      };

    4      struct  square_out {     /* output parameter */

    5            long res1;

    6      };

    7      program  SQUARE_PROG {

    8          version SQUARE_VERS {

    9              square_out SQUAREPROC(square_in)=1;  /* 过程号=1 */

    10         }=1;      /* 版本号=1 */

    11      }=0x31230000;

     

    此文件包含如下内容:

    定义参数和返回值。文件的1—6行定义了两个结构,一个是参数,另一个是返回值。

    定义程序、过程和版本。7—11行用于定义程序、过程和版本,RPC程序名为SQUARE_PROG,这个程序有一个版本SQUARE_VERS,该版本中只有一个过程。这个过程名为SQUAREPROC,它的参数的类型为struct square_in,它的返回值的类型为struct square_out。过程号被赋值为1,版本号也被赋值为1。程序号为32位,这里用8位16进制数表示。

    这个文件经过rpcgen编译程序编译后可以生成4个文件:square.h、square_clnt.c、square_xdr.c、square_svc.c。

    (2)编写一个顾客程序,该程序调用远程过程。该例子的顾客程序源文件为client.c,如下所示:

    1       #include  “unpipc.h”   /*our header*/

    2       #include  “square.h”   /*generated by rpcgen*/

    3       int

    4       main(int  argc, char **argv)

    5       {

    6           CLIENT *cl;

    7           square_in in;

    8           square_out *outp;

    9           if(argc!=3)

    10              err_quit(“usage: client  <hostname> <integer-value”);

    11          cl=clnt_creat(argv[1],SQUARE_PROG,SQUARE_VERS,  “tcp”);

    12          in.arg1=atoll(argv[2]);

    13          if((outp=squareproc_1(&in,cl))==NULL)

    14              err_quit(“%s”,clnt_sperror(cl,argv[1]));

    15          printf(“result:%ld\n”,outp->res1);

    16          exit(0);

    17      }

     

    此文件包含如下内容:

    包含有rpcgen产生的头部文件。在本例中这个头部文件为square.h,如程序中的第2行。

    说明一个顾客句柄变量。如程序中第6行的变量cl。

    获得一个顾客句柄。调用函数clnt_create,如果调用成功,该函数返回一个顾客句柄,如程序中的第11行。同标准的I/O文件句柄类似,用户不必关心顾客句柄指针所指定的具体内容是什么,它由RPC运行时系统所保持,它是某种结构类型的信息。函数clnt_create的第一个参数是运行服务员程序的机器的名字或IP地址;第二个参数是服务员程序的程序号码;第三个参数是该程序的版本号,这两个参数是RPC说明文件中所定义的(本例在square.x中定义);第四个参数是所选择的协议,通常是TCP或UDP协议。

    调用远程过程和打印结果。这一部分由程序的12行到15行完成。第一个是一个指向输入结构的指针,第二个参数是这个顾客的句柄。返回值是一个指向结果结构的指针。输入结构的存储空间由用户程序分配,而结果结构的存储空间是有RPC运行时系统分配的。在square.x说明文件中,把要调用的过程命名为SQUAREPROC,但是在顾客程序中则调用过程squareproc_1,对应的规则是将square.x中的过程名由大写改为小写,再加上一个下划线,后跟一个版本号数字。

    (3)编写一个服务员程序。服务员的主程序由rpcgen自动生成,只需要编写服务过程程序。如下所示是过程程序文件server.c:

    1       #include  “unpipc.h”

    2       #include  “square.h”

    3       square_out  *

    4       squareproc_1_svc(square_in  *inp,struct svc_req *rqstp)

    5       {

    6           static square_out out;

    7           oui.res1=inp->arg1*inp->arg1;

    8           return(&out);

    9       }

     

    此文件包含的头部文件同顾客程序文件包含的头部文件一样,此外还包含如下内容:

    过程名及参数。服务员过程名是在版本号后再加上_svc,过程第一个参数是指向输入结构的一个指针,第二个参数也是一个指向结构的指针,这个结构由RPC运行时系统传递,它包含调用请求的相关信息。如程序中的3行和4行。

    执行和返回。取出输入参数并计算它的平方。结果存放在一个结构中,结构的地址是过程的返回值。由于是从一个函数里返回一个变量的地址,所以该变量要说明为static。如程序中的6到8行。

    (4)第四步,编译。有如下编译任务:

    使用rpcgen对RPC说明文件进行编译。

    Solaris% rpcgen –C  square.x

    通过编译生成4个文件:square.h、square_clnt.c、square_xdr.c、square_svc.c。

    用cc编译生成顾客程序client。

    Solaris% cc –o client  client.c square_clnt.c square_xdr.c libunpipc.a -lnsl

    用cc编译生成服务员程序server。

    Solaris% cc –o  server server.c square_svc.c square_xdr.c libunpipc.a -lnsl

     

     

    (5)第五步,执行程序。在服务员机上执行server程序,在顾客机上执行client程序。

    当顾客程序和服务员程序在两个不同的系统上实现的话,一些文件如本例中的square.h和square_xdr.c需要共享或拷贝到不同的机器上,同顾客和服务员程序一起在不同的机器上编译。

    图11-11描述了上述例子的实现过程,图中阴影部分是由用户编写的。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     


    图11-11 建立SUN RPC顾客/服务员的过程

     

    想详细了解SUN RPC请参见[STEVENS,1999],该书对SUN RPC有非常详细的介绍。

     

    11.5 加密技术

     

         在计算机网络系统中的保护和安全有以下三个方面:

    1. 数据加密:这个问题已经成为世界上许多国家计算机安全工作的重点,主要研究对数据加密的算法、算法实现的效率等问题。

    2. 计算机网络的安全保密:计算机网络的目的是资源共享,同时也是分布计算系统、网格系统的基础平台,但网络容易产生不安全和失密问题。系统可以采用多级安全控制,包括使用可信赖的计算机系统,主机将保密的数据完全隔离。一个非常方便并且行之有效的方法是采用加密技术,实现用户到用户之间的加密,确保数据在网络传输过程中(特别是通信系统中)不丢失和被窜改。

    3. 访问控制:象多用户单机系统那样,规定什么人可以访问系统的什么数据,得到何种服务,并对用户进行身份鉴别,对他们的访问权限加以限制(授权)。保护的对象可以是数据、程序、计算机性能、存储空间、存储介质或外部设备,这种保护称为对这些对象的访问控制。由于广泛使用网络和数据库系统,机器不仅要识别用户或者其进程是否合法,而且需要双方互相鉴别身份,有时甚至需要多方的鉴别。口令文件本身也需要保密。现代网络系统常采用多种方法进行用户身份鉴别,除了口令外还可以使用磁卡、指纹、签名、确认用户机器位置等。更严格的鉴别系统不仅在用户登录时进行鉴别,而且在整个运行期间随时限制访问。一般来说,使用硬件和固件做的鉴别系统不易受到攻击,而且不易出错。

    可以把上述三个问题归并成两个:一个是加密技术,包括对数据及通信系统的加密;另一个是访问控制。加密技术不仅用于保护通信,也用于保护鉴别报文和访问控制。

    为了传输敏感信息,如军事或金融数据,系统必须能够保证保密性。但是,微波、卫星以及各式电缆上传输的信息都很容易被截取。实际上,任何系统都不可能完全防止未经授权的用户对传输介质进行非法访问。

    在实际系统中,比较实际的保护信息的方法是对信息加以改变,使得只有经过授权的用户才能够理解它,未经授权的用户即使得到它,也不能理解它。这种保护信息的方法称为对信息的加密和解密。加密意味着发送者将信息从最初的格式改变为另一种格式,将最终不可阅读的消息通过网络发送出去。解密是加密的相反过程,它将消息变换回原来的格式。图11-12显示了加密和解密的基本过程。


    图11-12加密和解密的过程

     

    加密和解密的方法分为两种类型:传统方法和公开密钥方法。

     

    11.5.1传统加密方法

     

    11.5.1.1单密钥系统加密模型

     

    传统的单密钥加密系统的加密模型如图11-13所示。A处待加密的明文x,它被一个以密钥k为参数的加密函数E变换成密文y=E(k,x),通过网络传送到B处,在B处,密文y被一个以密钥k为参数的解密函数D变换成明文x=D(k,y)。


    图11-13单密钥系统加密模型

     

    加密函数必须与解密函数配对使用,以便恢复原文。非法用户想要弄懂密文,必须知道密钥和解密函数,只知道其中一个是无法将密文转换为明文的。对于接收者B来说,由于它确信只有发送者A才知道如何将消息按它们之间约定的方法加密,非法用户冒充A发送来的消息经过解密变换后得到的是毫无意义的信息。因此,凡经解密后有明确意义的消息肯定是由A发来的。B在收到A发来的消息后,也用保密方式给A发送一个应答,表示收到。这样,A和B之间就实现了保密通信。

    通常密钥是由简单的字符串组成,它选择很多可能加密方式中的一种。只要有必要,可以经常改变密钥。加密和解密函数指出一种加密和解密的方法。为了使保密有效,似乎应该尽可能把所有的加密和解密算法的全部细节保密,但是,使用一段时间之后,难免会泄密。为了安全起见,加密和解密算法应该经常更换,可是,能够经得起攻击的过硬算法并不是很容易寻找和设计的,经常更换加密和解密算法是不现实的,因此才使用密钥。加密和解密算法可以长时间使用,但是密钥应该经常更换。

    传统的加密方法有两种:替换法和位置变换法。

     

    11.5.1.2替换法

     

    替换法在很久以前就开始使用了。在开始的时候,加密是通过使用被称为单字母替换的办法来实现的:在这种加密方法中,每个字符都被另一个字符所代替。这种加密方法称为恺撒密码,因为它最初被恺撒使用。

    更安全的字符替换方法是多字母替换。这里,我们仍然使用一个字符替换另一个字符,但是在这种方法中,同样的原文字符是用不同的密文字符替换的;替换不仅取决于原文的字符,也取决于字符在文中的位置。例如,消息GOOD MORNING中的三个O可以被三个不同的密码字符所代替。

     

    11.5.1.3位置交换法

     

    在这种方法中,字符将保持它们在原文中的格式,但是它们的位置将被改变来创建密文。这种类型的加密用下面的方法非常有效地加以实现,将文本组织成一个二维表格,然后根据一个密钥将列重新组织,这个密钥指出了用哪一列替换哪一列。

    例如下列明文:pleasegive me the books, when you come up next.

    使用一个不重复出现字母的短语MEGABUCK作为密钥,将明文各自母对准此密钥各字母排列,形成8列,同时给每列一个编号,编号按对应的密钥字母在字母表中出现的先后顺序给出。

    M

    E

    G

    A

    B

    U

    C

    K

    7

    4

    5

    1

    2

    8

    3

    6

    p

    l

    e

    a

    s

    e

     

    g

    i

    v

    e

     

    m

    e

     

    t

    h

    e

     

    b

    o

    o

    k

    s

    ,

     

    w

    h

    e

    n

     

    y

    o

    u

     

    c

    o

    m

    e

     

    u

    p

     

    n

    e

    x

    t

    .

    然后从小号列到大列,每列从上到下,把明文字母排列即得密文:

    a bhcnsmoeoe  k etlveupee w  gtsy .pih,oueeonmx

     

    11.5.1.4DES加密

     

    上面提到的方法都是很少使用的。现在的传统加密方法都是基于比特而不是字符的。在比特级别的技术中,数据首先是划分为比特块,然后通过替换、位置变换、交换、异或、循环移位等方法进行改变。

    比特级别加密的一个例子是数据加密标准(DES)。DES是由IBM公司制定的,被美国政府接受成为非军事和非保密的加密标准。算法使用64比特的原文和56比特的密钥,原文经过19个不同而复杂的过程来产生一个64比特的密文。


    图11-14显示了DES的流程图。第一个和最后两个步骤相对简单,而步骤2到步骤17非常复杂,每一个步骤都由位置替换、替换、交换、异或和循环移位组合的多个子步骤组成。它的复杂性表现在两个方面:第一,尽管步骤2到步骤17是相同的,但是所使用的密钥是不同的,虽然它们是从同一个密钥继承来的。第二,前一个步骤的输出是后一个步骤的输入。

     

    图11-14  DES加密过程

     

    11.5.1.5密钥的分配

     

    如何把解密密钥告诉接收者又不让他人知道,特别是在地理上分散的网络环境中,由人工传送密钥是很不方便的,例如,银行系统可能有成百上千个分支机构,并且可能经常更换密钥,由人工传送密钥很困难。这就提出了如何通过系统本身传送密钥的问题,问题在于这种传送必须是保密的。

    现在我们就讨论这个问题。设两个用户要进行对话,对话之前要商定一个对话密钥,由于它们之间没有一个对话密钥,密钥的传送是不能用明文的形式传送的,所以需要一个双方都信任的第三者帮助他们完成密钥的分配,将这个第三者叫做网络安全中心(NSC)。现在假定用户A和用户B均已经和NSC建立了保密的通信信道,使用的密钥分别为ka和kb,A用ka和NSC进行保密通信,B用kb和NSC进行保密通信。A和B要进行保密通信,必须向NSC申请一个对话密钥Ks,假设A是对话的发起者,那么A先向NSC申请一个对话密钥Ks,NSC将Ks以密文的形式E(ka,ks)和E(kb,ks)分别传送给A和B。密码可能含有其它数据,例如,消息的序号与时间。A和B解密得到ks,对话结束后消除它,NSC也不保留ks的副本。

    NSC向A和B传送密钥Ks的方法有两种,如图11-15(a)和图11-15(b)所示。

    图11-15(a)的方法存在一个问题:如果A是对话的发起者,A要向B发送一个消息M,那么接收者B应该先从NSC那里获得密钥E(kb,ks),然后从A那里获得消息E(ks,M),这样B才能解密获得M。但是B有可能先得到加密的消息,后得到密钥,从而不能对消息解密。图11-15(b)的方法则比较方便:NSC先把E(ka,ks)和E(kb,ks)传送给A,再由A把E(kb,ks)传送给B,A和B都得到密钥ks后,A再向B发送消息。

     


    图11-15 密钥的分配

     

     

    11.5.2公开密钥加密方法

     

    11.5.2.1公开密钥系统加密模型

     

    在传统的加密方法中,密钥的分配需要NSC,并且它已经跟通信双方A和B分别建立了保密通信的条件下才可实现。那么分属于不同的组织机构的两个用户,它们之间没有一个NSC,这样的用户怎样进行保密通信呢?

    在传统的加密系统中只使用一个密钥,它既是加密密钥,同时也是解密密钥,因此密钥必须保密。所以传统的加密系统又称为对称加密系统或单密钥加密系统。如果使用不保密的加密密钥,则不必用保密信道来传送加密密钥。这样的加密系统称为非对称加密系统或公开密钥加密系统。这种加密思想是Diffie和Hellman在1976年提出的,这一思想使人们对加密系统有了新的认识。主要思想是,加密算法E和解密算法D无法保持秘密,不如干脆公开,但是使用两个密钥:加密密钥Ke和解密密钥Kd。加密密钥是不保密的,谁都可以使用,所以叫做公开密钥;解密密钥是保密的,只有接收密文的一方才知道,所以叫做专用密钥或保密密钥。选择某种类型的算法E和算法D,使得局外人即使知道了加密密钥Ke,也推算不出来解密密钥Kd。图11-16描述了公开密钥系统的加密模型。

    在图11-16中,A有两个密钥:加密密钥KeA,这个密钥是公开的,B如果要向A发送保密报文,可用此密钥对报文加密;A还有一个解密密钥KdA,此密钥是保密的,除A之外,其它任何人都不知道,A用此密钥解密B发送来的保密报文。如图11-16(b)所示。

    同样,B也有两个密钥:加密密钥KeB,这个密钥是公开的,A如果要向B发送保密报文,可用此密钥对报文加密;B还有一个解密密钥KdB,此密钥是保密的,除B之外,其它任何人都不知道,B用此密钥解密A发送来的保密报文。如图11-16(a)所示。

    采用这种方法,所有网络用户登录时应该公布其加密密钥和加密函数,让所有人知道。这种方法使得密钥的分配工作大大简化。其缺点是加密和解密算法都很复杂,相当耗费时间和主存空间。算法的选择很困难,从1976年到现在,实用的算法很少。


    图11-16 公开密钥加密模型

     

     

    11.5.2.2RSA加密

     

    RSA加密技术是一种公开密钥加密技术,它的名字来自于最初的三个发明者(Rivest,Shamir, Adleman)。下面我们简要的介绍如何使用这种方法。

    首先计算出一些参数:

    1. 选择两个,p和q。

    2. 计算n=p×q和z=(p-1) ×(q-1)。

    3. 选择一个与z互质的数d。

    4. 找出e,使得e×d=1 mod z。

    确定公开密钥和保密密钥:公开密钥由(e,n)构成,保密密钥由(d,n)构成。

    加密时可以把明文看成一个比特串,把明文划分成大小为k比特的块,每块可以看作是一个正整数m,其中0≤m<n,所以块的大小k应该满足:2k<n。

    加密的过程非常简单:设X是要加密的明文信息,计算Y=Xe(mod n),则Y就是加密后得到的密文。

    解密的过程也非常简单:设Y是要解密的密文信息,计算X=Yd(mod n),则X就是解密后得到的明文。

    算法的安全性:此算法的安全性建立在难于对大数进行分解的基础上。如果破译者能够对公开的n作因子分解,那么就能够找出p和q,并从中得到z。如果知道了z和e,就能够用欧几里德算法很容易地求出d。可是,对于一个很大的数n,对它做因子分解是极其困难的问题,也就是说,很难从n求出p和q。

    根据Rivest及其同事们的研究,如果使用最好的算法,在指令时间为1us的计算机上对200位的数分解因子需要40亿年的时间,对500位的数分解因子需要1025年。

    我们用表10-3的例子来说明如何对明文“SUZANNE”进行加密。在此例子中我们只考虑英文大写字母的加密,我们可以对英文字母A~Z按顺序编码为1~26。在该例子中,我们选择p=3,q=11,得到n=33,z=(p-1)×(q-1)=2×10=20。由于7和20互为质数,故可以设d=7。对于所选的d=7,解方程7e=1(mod 20),可以得到e=3。

    在我们的例子中,由于所选的p和q太小,破译当然很容易,我们的例子只是用来说明此算法的原理。同时,也由于所选的p和q太小,所以每个明文块所对应的数值要小于33(p×q=33),所以每个明文块只能包含一个字符。

     

    加密

    解密

    明文(X)

    X3

    密文(Y)

    Y7

    Y7(mod  33)

    符号

    符号

    数值

    X3(mod  33)

    S

    19

    6859

    28

    13492928512

    19

    S

    U

    21

    9261

    21

    1801088541

    21

    U

    Z

    26

    17576

    20

    1280000000

    26

    Z

    A

    1

    1

    1

    1

    1

    A

    N

    14

    2744

    5

    78125

    14

    N

    N

    14

    2744

    5

    78125

    14

    N

    E

    5

    125

    26

    8031810176

    5

    E

     

     

    11.5.2.3认证

     

    认证意味着确认发送者的身份。换句话说,一种认证技术需要确认一条消息是来自一个确认的发送者而不是一个冒充者。认证常通过数字签名的方式来实现。

    在日常生活中很多场合需要签名原本文件。例如,为了从你的银行账户中提取大笔现金,你首先到银行填写一张取款单,银行需要你签署这张单子,同时保留签署的单子作为纪录。假如你日后说你从来没有提取过这样金额的现金,银行就可以拿出你的签名,证明你确实提取过。在网络交易中,虽然你不能签署单子,但是,你可以在发送数据的同时创建一个等价的数字签名。

    实现数字签名,要解决两个问题:第一,接收者能验证所要求的发送者的身份;第二,发送者在发送已经签名的报文后不能否认。

    加密技术不仅能够用于保密,而且还能够用于鉴别身份,即能够实现数字签名。

     

    11.5.2.4公开密钥加密技术实现数字签名

     

    使用公开密钥加密技术实现数字签名要求加密函数E和解密函数D满足下列条件:

    E(D(P))=P, 当然同时还有D(E(P))=P

    也就是E和D可以互换。

    现在,假定A向B发送一个签名报文P。A的公开密钥为KeA,保密密钥为KdA。B的公开密钥为KeB,保密密钥为KdB


    图11-17显示了使用公开密钥加密技术实现数字签名的过程。图中DA(P)=D(KdA,P), EB(P)=E(KeB,P)。A先对报文P进行签名,形成P的签名形式DA(P);然后对报文的签名形式进行加密,形成签名报文的加密形式EB(DA(P));B收到这个加过密的签名报文后,使用自己的保密密钥解密,得到报文的签名形式DA(P)=DB(EB(DA(P)));最后B对签名的报文进行验证并获得报文的明文形式P=EA(DA(P)),同时保留P的签名形式DA(P)。B知道这个签名报文确实是从A那儿发送来的,因为只能用A的公开密钥KeA才能解密成功。同时,A不能否认,因为B保留有P的签名形式DA(P)。

    图11-17使用公开密钥实现数字签名

     

    11.5.2.5单密钥加密技术实现数字签名

     

    使用单密钥系统也可以实现数字签名,参见图11-18。


    图11-18 使用单密钥加密技术实现数字签名

     

    为获得保密性,NSC有一个对任何人都保密的密钥X。由两个用户A和B,A和NSC之间用密钥KA进行保密通信,B和NSC之间用密钥KB进行保密通信。当A要发送一个报文P给B时,如果按照一下过程进行,B可以认定报文P是A向它发送的:

    1. A向NSC发送加密报文,简记成KA(P)=E(KA,P)。

    2. NSC解密KA(P)得到P,然后在报文P上加上发送者的名字A和日期D,产生一个新报文,用X加密这个新报文,得到的形式记为X(A+D+P),然后送回给A。这里注意,NSC可以证明P确实是从A发送来的,因为只有A和NSC知道KA。A把X(A+D+P)送给B。

    3. B保留一个X(A+D+P)的副本,然后把X(A+D+P)送给NSC,NSC将它换成KB(A+D+P)再送回给B。

    4. B把NSC送来的KB(A+D+P)解密,得到A、D、P。

    如果A否认曾向B发送P,则因B处有X(A+D+P),这可由NSC解密得到A、D、P,而B不知道X,因此不会伪造。

     

    11.5.2.6使用报文摘要的数字签名

     

    由于加密整个报文很慢,下面的数字签名方案不要求加密整个报文。这种方案是基于单向散列(hash)函数的思想,该函数从一段很长的明文中计算出固定长度的比特串。这个散列函数通常被称为报文摘要(message digest),它具有三个重要的属性:

    1. 给出报文P就很容易计算出其报文摘要MD(P)。

    2. 只给出MD(P),几乎无法推导出P。

    3. 无法生成这样的两条报文,它们具有同样的报文摘要。

    要满足第三个条件,散列至少长128比特,或者更长。

    从一段明文中计算出一段报文摘要要比用公开密钥算法加密整个明文要快得多。因此,报文摘要可以用来加速数字签名算法。

    例如,A向B发送签名的报文P,A首先计算P的报文摘要MD(P),A用自己的保密密钥对MD(P)进行加密以达到签名的目的,而不用对整个报文加密,所以签名的速度大大提高了。最后,A将报文摘要的签名形式DA(MD(P))连同明文P一起发送给B。B用A的公开密钥解密DA(MD(P)),从而得到MD(P),如果非法用户改变了明文P,B计算MD(P)时就会发现这一点。由于解密的数据长度大大地缩短,所以认证的速度也大大提高了。

     

     

    展开全文
  • RPC服务远程调用

    万次阅读 2018-09-03 22:43:42
    允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的,本质上编写的调用代码基本相同。 RPC 起源 RPC 这个...

     

    RPC 是什么?

    RPC 的全称是 Remote Procedure Call 是一种进程间通信方式。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的,本质上编写的调用代码基本相同。

    RPC 起源

    RPC 这个概念术语在上世纪 80 年代由 Bruce Jay Nelson 提出。这里我们追溯下当初开发 RPC 的原动机是什么?在 Nelson 的论文 "Implementing Remote Procedure Calls" 中他提到了几点:

    1. 简单:RPC 概念的语义十分清晰和简单,这样建立分布式计算就更容易。
    2. 高效:过程调用看起来十分简单而且高效。
    3. 通用:在单机计算中过程往往是不同算法部分间最重要的通信机制。 

    通俗一点说,就是一般程序员对于本地的过程调用很熟悉,那么我们把 RPC 作成和本地调用完全类似,那么就更容易被接受,使用起来毫无障碍。Nelson 的论文发表于 30 年前,其观点今天看来确实高瞻远瞩,今天我们使用的 RPC 框架基本就是按这个目标来实现的。

    RPC 结构

    Nelson 的论文中指出实现 RPC 的程序包括 5 个部分:

    1. User
    2. User-stub
    3. RPCRuntime
    4. Server-stub
    5. Server

    这 5 个部分的关系如下图所示

    这里 user 就是 client 端,当 user 想发起一个远程调用时,它实际是通过本地调用 user-stub。user-stub 负责将调用的接口、方法和参数通过约定的协议规范进行编码并通过本地的 RPCRuntime 实例传输到远端的实例。远端 RPCRuntime 实例收到请求后交给 server-stub 进行解码后发起本地端调用,调用结果再返回给 user 端。

    RPC 实现

    Nelson 论文中给出的这个实现结构也成为后来大家参考的标准范本。大约 10 年前,我最早接触分布式计算时使用的 CORBAR 实现结构基本与此类似。CORBAR 为了解决异构平台的 RPC,使用了 IDL(Interface Definition Language)来定义远程接口,并将其映射到特定的平台语言中。后来大部分的跨语言平台 RPC 基本都采用了此类方式,比如我们熟悉的 Web Service(SOAP),近年开源的 Thrift 等。他们大部分都通过 IDL 定义,并提供工具来映射生成不同语言平台的 user-stub 和 server-stub,并通过框架库来提供 RPCRuntime 的支持。不过貌似每个不同的 RPC 框架都定义了各自不同的 IDL 格式,导致程序员的学习成本进一步上升(苦逼啊),Web Service 尝试建立业界标准,无赖标准规范复杂而效率偏低,否则 Thrift 等更高效的 RPC 框架就没必要出现了。

    IDL 是为了跨平台语言实现 RPC 不得已的选择,要解决更广泛的问题自然导致了更复杂的方案。而对于同一平台内的 RPC 而言显然没必要搞个中间语言出来,例如 java 原生的 RMI,这样对于 java 程序员而言显得更直接简单,降低使用的学习成本。目前市面上提供的 RPC 框架已经可算是五花八门,百家争鸣了。需要根据实际使用场景谨慎选型,需要考虑的选型因素我觉得至少包括下面几点:

    1. 性能指标
    2. 是否需要跨语言平台
    3. 内网开放还是公网开放
    4. 开源 RPC 框架本身的质量、社区活跃度

    RPC 功能目标

    RPC 的主要功能目标是让构建分布式计算(应用)更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性。为实现该目标,RPC 框架需提供一种透明调用机制让使用者不必显式的区分本地调用和远程调用,在前文《浅出篇》中给出了一种实现结构,基于 stub 的结构来实现。下面我们将具体细化 stub 结构的实现。

     

    RPC 调用分类

    RPC 调用分以下两种:

    1. 同步调用
       客户方等待调用执行完成并返回结果。
    2. 异步调用
       客户方调用后不用等待执行结果返回,但依然可以通过回调通知等方式获取返回结果。
       若客户方不关心调用返回结果,则变成单向异步调用,单向调用不用返回结果。
    

    异步和同步的区分在于是否等待服务端执行完成并返回结果。

     

    RPC 结构拆解

    《浅出篇》给出了一个比较粗粒度的 RPC 实现概念结构,这里我们进一步细化它应该由哪些组件构成,如下图所示。

    RPC 服务方通过 RpcServer 去导出(export)远程接口方法,而客户方通过 RpcClient 去引入(import)远程接口方法。客户方像调用本地方法一样去调用远程接口方法,RPC 框架提供接口的代理实现,实际的调用将委托给代理RpcProxy 。代理封装调用信息并将调用转交给RpcInvoker 去实际执行。在客户端的RpcInvoker 通过连接器RpcConnector 去维持与服务端的通道RpcChannel,并使用RpcProtocol 执行协议编码(encode)并将编码后的请求消息通过通道发送给服务方。

    RPC 服务端接收器 RpcAcceptor 接收客户端的调用请求,同样使用RpcProtocol 执行协议解码(decode)。解码后的调用信息传递给RpcProcessor 去控制处理调用过程,最后再委托调用给RpcInvoker 去实际执行并返回调用结果。

     

    RPC 组件职责

    上面我们进一步拆解了 RPC 实现结构的各个组件组成部分,下面我们详细说明下每个组件的职责划分。

    1. RpcServer
    
    负责导出(export)远程接口
    
    2. RpcClient
    
    负责导入(import)远程接口的代理实现
    
    3. RpcProxy
    
    远程接口的代理实现
    
    4. RpcInvoker
    
    客户方实现:负责编码调用信息和发送调用请求到服务方并等待调用结果返回
    
    服务方实现:负责调用服务端接口的具体实现并返回调用结果
    
    5. RpcProtocol
    
    负责协议编/解码
    
    6. RpcConnector
    
    负责维持客户方和服务方的连接通道和发送数据到服务方
    
    7. RpcAcceptor
    
    负责接收客户方请求并返回请求结果
    
    8. RpcProcessor
    
    负责在服务方控制调用过程,包括管理调用线程池、超时时间等
    
    9. RpcChannel
    
    数据传输通道

    RPC 实现分析

    在进一步拆解了组件并划分了职责之后,这里以在 java 平台实现该 RPC 框架概念模型为例,详细分析下实现中需要考虑的因素。

    导出远程接口

    导出远程接口的意思是指只有导出的接口可以供远程调用,而未导出的接口则不能。在 java 中导出接口的代码片段可能如下:

    DemoService demo = new ...;
    
    RpcServer server = new ...;
    
    server.export(DemoService.class, demo, options);

    我们可以导出整个接口,也可以更细粒度一点只导出接口中的某些方法,如:

    // 只导出 DemoService 中签名为 hi(String s) 的方法
    
    server.export(DemoService.class, demo, "hi", new Class<?>[] { String.class }, options);

    java 中还有一种比较特殊的调用就是多态,也就是一个接口可能有多个实现,那么远程调用时到底调用哪个?这个本地调用的语义是通过 jvm 提供的引用多态性隐式实现的,那么对于 RPC 来说跨进程的调用就没法隐式实现了。如果前面DemoService 接口有 2 个实现,那么在导出接口时就需要特殊标记不同的实现,如:

    DemoService demo = new ...;
    
    DemoService demo2 = new ...;
    
    RpcServer server = new ...;
    
    server.export(DemoService.class, demo, options);
    
    server.export("demo2", DemoService.class, demo2, options);

    上面 demo2 是另一个实现,我们标记为 "demo2" 来导出,那么远程调用时也需要传递该标记才能调用到正确的实现类,这样就解决了多态调用的语义。

    导入远程接口与客户端代理

    导入相对于导出远程接口,客户端代码为了能够发起调用必须要获得远程接口的方法或过程定义。目前,大部分跨语言平台 RPC 框架采用根据 IDL 定义通过 code generator 去生成 stub 代码,这种方式下实际导入的过程就是通过代码生成器在编译期完成的。我所使用过的一些跨语言平台 RPC 框架如 CORBAR、WebService、ICE、Thrift 均是此类方式。

    代码生成的方式对跨语言平台 RPC 框架而言是必然的选择,而对于同一语言平台的 RPC 则可以通过共享接口定义来实现。在 java 中导入接口的代码片段可能如下:

    RpcClient client = new ...;
    
    DemoService demo = client.refer(DemoService.class);
    
    demo.hi("how are you?");

    在 java 中 'import' 是关键字,所以代码片段中我们用 refer 来表达导入接口的意思。这里的导入方式本质也是一种代码生成技术,只不过是在运行时生成,比静态编译期的代码生成看起来更简洁些。java 里至少提供了两种技术来提供动态代码生成,一种是 jdk 动态代理,另外一种是字节码生成。动态代理相比字节码生成使用起来更方便,但动态代理方式在性能上是要逊色于直接的字节码生成的,而字节码生成在代码可读性上要差很多。两者权衡起来,个人认为牺牲一些性能来获得代码可读性和可维护性显得更重要。

    协议编解码

    客户端代理在发起调用前需要对调用信息进行编码,这就要考虑需要编码些什么信息并以什么格式传输到服务端才能让服务端完成调用。出于效率考虑,编码的信息越少越好(传输数据少),编码的规则越简单越好(执行效率高)。我们先看下需要编码些什么信息:

    -- 调用编码 --
    
    1. 接口方法
    
    包括接口名、方法名
    
    2. 方法参数
    
    包括参数类型、参数值
    
    3. 调用属性
    
    包括调用属性信息,例如调用附件隐式参数、调用超时时间等
    
    
    -- 返回编码 --
    
    1. 返回结果
    
    接口方法中定义的返回值
    
    2. 返回码
    
    异常返回码
    
    3. 返回异常信息
    
    调用异常信息

    除了以上这些必须的调用信息,我们可能还需要一些元信息以方便程序编解码以及未来可能的扩展。这样我们的编码消息里面就分成了两部分,一部分是元信息、另一部分是调用的必要信息。如果设计一种 RPC 协议消息的话,元信息我们把它放在协议消息头中,而必要信息放在协议消息体中。下面给出一种概念上的 RPC 协议消息设计格式:

    -- 消息头 --
    
    magic : 协议魔数,为解码设计
    
    header size: 协议头长度,为扩展设计
    
    version : 协议版本,为兼容设计
    
    st : 消息体序列化类型
    
    hb : 心跳消息标记,为长连接传输层心跳设计
    
    ow : 单向消息标记,
    
    rp : 响应消息标记,不置位默认是请求消息
    
    status code: 响应消息状态码
    
    reserved : 为字节对齐保留
    
    message id : 消息 id
    
    body size : 消息体长度
    
    
    -- 消息体 --
    
    采用序列化编码,常见有以下格式
    
    xml : 如 webservie soap
    
    json : 如 JSON-RPC
    
    binary: 如 thrift; hession; kryo 等

    格式确定后编解码就简单了,由于头长度一定所以我们比较关心的就是消息体的序列化方式。序列化我们关心三个方面:

    1. 序列化和反序列化的效率,越快越好。 
    2. 序列化后的字节长度,越小越好。 
    3. 序列化和反序列化的兼容性,接口参数对象若增加了字段,是否兼容。

    上面这三点有时是鱼与熊掌不可兼得,这里面涉及到具体的序列化库实现细节,就不在本文进一步展开分析了。

    传输服务

    协议编码之后,自然就是需要将编码后的 RPC 请求消息传输到服务方,服务方执行后返回结果消息或确认消息给客户方。RPC 的应用场景实质是一种可靠的请求应答消息流,和 HTTP 类似。因此选择长连接方式的 TCP 协议会更高效,与 HTTP 不同的是在协议层面我们定义了每个消息的唯一 id,因此可以更容易的复用连接。

    既然使用长连接,那么第一个问题是到底 client 和 server 之间需要多少根连接?实际上单连接和多连接在使用上没有区别,对于数据传输量较小的应用类型,单连接基本足够。单连接和多连接最大的区别在于,每根连接都有自己私有的发送和接收缓冲区,因此大数据量传输时分散在不同的连接缓冲区会得到更好的吞吐效率。所以,如果你的数据传输量不足以让单连接的缓冲区一直处于饱和状态的话,那么使用多连接并不会产生任何明显的提升,反而会增加连接管理的开销。

    连接是由 client 端发起建立并维持。如果 client 和 server 之间是直连的,那么连接一般不会中断(当然物理链路故障除外)。如果 client 和 server 连接经过一些负载中转设备,有可能连接一段时间不活跃时会被这些中间设备中断。为了保持连接有必要定时为每个连接发送心跳数据以维持连接不中断。心跳消息是 RPC 框架库使用的内部消息,在前文协议头结构中也有一个专门的心跳位,就是用来标记心跳消息的,它对业务应用透明。

    执行调用

    client stub 所做的事情仅仅是编码消息并传输给服务方,而真正调用过程发生在服务方。server stub 从前文的结构拆解中我们细分了 RpcProcessor 和 RpcInvoker 两个组件,一个负责控制调用过程,一个负责真正调用。这里我们还是以 java 中实现这两个组件为例来分析下它们到底需要做什么?

    java 中实现代码的动态接口调用目前一般通过反射调用。除了原生的 jdk 自带的反射,一些第三方库也提供了性能更优的反射调用,因此 RpcInvoker 就是封装了反射调用的实现细节。

    调用过程的控制需要考虑哪些因素,RpcProcessor 需要提供什么样地调用控制服务呢?下面提出几点以启发思考:

    1. 效率提升
    
    每个请求应该尽快被执行,因此我们不能每请求来再创建线程去执行,需要提供线程池服务。
    
    2. 资源隔离
    
    当我们导出多个远程接口时,如何避免单一接口调用占据所有线程资源,而引发其他接口执行阻塞。
    
    3. 超时控制
    
    当某个接口执行缓慢,而 client 端已经超时放弃等待后,server 端的线程继续执行此时显得毫无意义。

    RPC 异常处理

    无论 RPC 怎样努力把远程调用伪装的像本地调用,但它们依然有很大的不同点,而且有一些异常情况是在本地调用时绝对不会碰到的。在说异常处理之前,我们先比较下本地调用和 RPC 调用的一些差异:

    1. 本地调用一定会执行,而远程调用则不一定,调用消息可能因为网络原因并未发送到服务方。
    2. 本地调用只会抛出接口声明的异常,而远程调用还会跑出 RPC 框架运行时的其他异常。
    3. 本地调用和远程调用的性能可能差距很大,这取决于 RPC 固有消耗所占的比重。

    正是这些区别决定了使用 RPC 时需要更多考量。当调用远程接口抛出异常时,异常可能是一个业务异常,也可能是 RPC 框架抛出的运行时异常(如:网络中断等)。业务异常表明服务方已经执行了调用,可能因为某些原因导致未能正常执行,而 RPC 运行时异常则有可能服务方根本没有执行,对调用方而言的异常处理策略自然需要区分。

    由于 RPC 固有的消耗相对本地调用高出几个数量级,本地调用的固有消耗是纳秒级,而 RPC 的固有消耗是在毫秒级。那么对于过于轻量的计算任务就并不合适导出远程接口由独立的进程提供服务,只有花在计算任务上时间远远高于 RPC 的固有消耗才值得导出为远程接口提供服务。

    转载自:https://blog.csdn.net/mindfloating/article/details/39473807

    原作者:mindwind-_-

    展开全文
  • 我们往往发一个intent请求就可以启动另一个应用的activity,或者一个你知道哪个进程的service,或者可以注册一个广播,只要有这个事件发生你都可以收到,又或者你可以查询一个contentProvider获得你想要的数据,...
  • 能够让客户端Java虚拟机上的对象像调用本地对象一样调用服务端java 虚拟机的对象上的方法。使用代表:EJB RMI远方法程调用步骤: 1、客户调用客户端辅助对象stub上的方法 2、客户端辅助对象stub打包调用信息...
  • go RPC 远程调用

    千次阅读 2019-08-25 18:24:20
    Go语言 RPC(Remote Procedure Call,远程过程调用)是一种通过网络从远程计算机程序上请求服务,而需要了解底层网络细节的应用程序通信协议。RPC 协议构建于 TCP 或 UDP,或者是 HTTP 之上,允许开发者直接调用...
  • 伙伴事务管理器已经禁止了它对远程/网络...服务的进程名为Msdtc.exe,该进程调用系统Microsoft Personal Web Server和Microsoft SQL Server。服务用于管理多个服务器 . 位置:控制面板--管理工具--服务-
  • <br />由于本人才初识BMP和Android,所以仅仅是自己的一些... 虽然某些情况下我们可以将Service直接加载到自己的进程中调用服务,此时可以进行简单的本地调用(Local Call),但是,从安全,性能和共享的角度出发
  • RPC远程过程调用

    千次阅读 2013-04-01 16:02:26
    RPC远程过程调用      顾客服务员模型进程之间的...但是计算机网络系统,这种调用可能不同的机器上执行,因此称为远程过程调用(remote procedure call)。远程过程调用的基础是XDR协议。   11.1
  • 远程过程调用(RPC)详解

    万次阅读 2016-07-11 00:12:33
    本文介绍了什么是远程过程调用(RPC),RPC 有哪些常用的方法,RPC 经历了哪些发展阶段,以及比较了各种 RPC 技术的优劣。
  • 远程调用shell的方法

    千次阅读 2010-11-30 20:42:00
    远程调用shell的方法
  • Windows RPC 远程过程调用

    千次阅读 2016-08-17 17:05:31
    本文章转载自 ... ...一、什么是远程过程调用 ... 什么是远程过程调用 RPC(Remote Procedure Call)? 你可能对这个概念有点陌生, 而你可能非常熟悉 NFS, 是的,  NFS 就是基于 RPC 的. 为了
  • NovaRPC远程过程调用 nova-compute RPC API的实现 novacomputemanager 模块 最后Nova Project Services nova-api:捕获novaclient发送过来的HTTP请求,并且将它转换为AMQP消息,通过Queue来与别的services
  • linux 运行*.sh文件的方法: Linux系统下运行.sh文件有两种方法,...1、任何路径下,输入文件的绝对路径/root/zpy/zpy.sh就可执行文件(当然要权限允许情况下) 2、cd到zpy.sh文件的目录下,然后执...
  • 远程过程调用(RPC)简介

    千次阅读 2018-09-24 12:19:39
    Remote Procedure Calls(远程过程调用) 本文译自:https://www.cs.rutgers.edu/~pxk/417/notes/03-rpc.html 简介 sockets是客户端/服务器网络通信模型的基础,它为程序与程序之间建立连接、收发信息提供了...
  • 浅析远程过程调用 RPC

    千次阅读 2013-09-13 17:02:39
    一、什么是远程过程调用  什么是远程过程调用 RPC(Remote Procedure Call)? 你可能对这个概念有点陌生, 而你可能非常熟悉 NFS, 是的,  NFS 就是基于 RPC 的. 为了理解远程过程调用,我们先来看一下过程调用。 ...
  • 远程调用框架认识Zookeeper

    千次阅读 2014-03-03 22:57:39
    编者按:作为Hadoop的子项目,Zookeeper多数情况下是配合Hadoop的工作,但Zookeeper还有更广泛的应用场景。Zookeeper是Google Chubby的开源实现,主要...本文转载自夏俊的博客,作者从远程调用服务的架构来认识Zooke
  • 显然, Java中不允许进程内存共享. 因此传递对象, 只能把对象拆分成操作系统能理解的简单形式, 以达到跨界对象访问的目的. J2EE,采用RMI的方式, 可以通过序列化传递对象. Android, 则采用AIDL的方式. 理论...
  • Java之——RMI远程过程调用(插曲)

    千次阅读 2015-05-25 16:58:37
    一、定义  Java RMI:Java远程方法调用,即Java RMI(Java Remote Method Invocation)是...远程方法调用特性使Java编程人员能够网络环境分布操作。RMI全部的宗旨就是尽可能简化远程接口对象的使用。  我们知道
  • 本文,我们来介绍RabbitMQ的RPC远程调用正式开始之前,我们假设RabbitMQ服务已经启动,运行端口为5672,如果各位看官有更改过默认配置,那么就需要修改为对应端口,保持一致即可。 准备工作: 操作系统:...
  • 1 绑定服务(本地的方式),要做如下的案例:操作步骤:“先点击绑定服务”,再点击“调用服务里面的方法”,然后查看服务的执行情况。2编写Android清单文件 xml version="1.0" encoding="utf-8"?> manifest xmlns:...
  • Dnode介绍 Dnode是一个提供异步双向远程方法调用的类库。网络socket和websocket风格的socket.io通信...RMI,连接的每一端都持有另一端能够调用其方法的一个远程对象。 Dnode所有的远程方法调用都是异步的。不同于
  • Android Service是分为两种: 本地服务(Local Service): 同一个apk内被调用远程服务(Remote...AIDL (Android Interface Definition Language) 是一种IDL 语言,用于生成可以Android设备上两个进程之间进行进程
  • 1、RPC(Remote Procedure Call)远程过程调用,它允许一台计算机程序远程调用另外一台计算机的子程序,而不用去关心底层的网络通信细节,对我们来说是透明的。经常用于分布式网络通信。 2、Hadoop的进程间交互都...
  • .Net远程方法调用研究

    千次阅读 2007-06-27 13:30:00
    简介远程方法调用发展到现在,已经有以下几种框架实现:DCE/RPC,CORBA,DCOM,MTS/COM+,Java RMI,Java EJB,Web Services/SOAP/XML-RPC,NET Remoting,本文主要介绍了.NET远程方法调用的原理,实现以及与微软...
  • Hadoop源码剖析07-远程过程调用(一)

    千次阅读 2019-12-30 22:49:13
    作为典型的分布式系统,Hadoop各个实体间存在着大量的交互,远程过程调用让用 户可以像调用本地方法一样调用另外一个应用程序提供的服务,而不必设计和开发相关的信 息发送、处理和接收等具体代码,是一种重要的...
  • RPC(远程过程调用)原理及应用

    千次阅读 2016-03-14 08:24:21
    所谓远程过程调用,也即是远程调用过程,它是一种通过网络从远程计算机程序上请求服务(某一过程),而需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 108,498
精华内容 43,399
关键字:

在该进程中不允许远程调用