将本地过程转换为远程过程示例

本节说明将本地过程转换为远程过程的示例。

此示例说明了将在单台机器上运行的应用程序转换为通过网络运行的应用程序的一种方法。 例如,程序员首先创建一个将消息打印到控制台的程序,如下所示:

/*
 * printmsg.c: print a message on the console
 */
#include <stdio.h>
main(argc, argv)
    int argc;
    char *argv[];
{
    char *message;
   if (argc < 2) {
       fprintf(stderr, "usage: %s <message>\n",
            argv[0]);
       exit(1);
    }
    message = argv[1];
    if (!printmessage(message)) {
         fprintf(stderr, "%s: couldn't print your
       message\n", argv[0]); 
         exit(1);
    }
    printf("Message Delivered!\n");
    exit(0);
}
/*
 * Print a message to the console.
 * Return a boolean indicating whether the
 * message was actually printed.
 */
printmessage(msg)
    char *msg;
{
    FILE *f;
    f = fopen("/dev/console", "w");
    if (f == NULL) { 
        return (0); 
    }
    fprintf(f, "%s\n", msg);
    fclose(f); 
    return(1);
}
回复消息如下:

example%  cc printmsg.c -o printmsg
example%  printmsg "Hello, there."
Message delivered!
example%

如果将 printmessage 程序转换为远程过程,那么可以从网络中的任何位置调用该程序。 理想情况下,可以在过程前插入诸如 remote 之类的关键字,以将其转换为远程过程。 不幸的是, C 语言的约束不允许这样做。 但是,可以在没有语言支持的情况下使过程成为远程过程。

为此,程序员必须知道所有过程输入和输出的数据类型。 在这种情况下, printmessage 过程采用字符串作为输入,并返回整数作为输出。 知道了这一点,程序员可以用远程过程调用语言 (RPCL) 编写协议规范,描述远程版本PRINTMESSAGE,如下所示:

/*
 * msg.x: Remote message printing protocol
 */
program MESSAGEPROG {
   version MESSAGEVERS {
       int PRINTMESSAGE(string) = 1;
   } = 1;
} = 99;

远程过程是远程程序的一部分,因此先前协议声明包含单个过程的远程程序PRINTMESSAGE。此过程已声明为远程程序的版本 1。 不需要空过程 (过程 0) ,因为 rpcgen 命令会自动生成该过程。

通常,所有声明都使用大写字母编写。

参数类型为string而不是char *因为char *在 C 中是模糊的。 程序员通常打算用它来表示以 null 结束的字符串,但它也可以表示指向单个字符的指针或指向字符数组的指针。 在 RPCL 中,以 null 结束的字符串被明确地称为string.

接下来,程序员自己编写远程过程。 要实现的远程过程的定义PRINTMESSAGE先前声明的过程可写如下:

/*
 * msg_proc.c: implementation of the remote 
 *procedure "printmessage"
 */
#include <stdio.h> 
#include <rpc/rpc.h>   /* always needed  */
#include "msg.h"       /* msg.h will be generated by rpcgen */

/*
 * Remote version of "printmessage"
 */ int *
printmessage_1(msg)
    char **msg;
{ 

    static int result;  /* must be static! */
    FILE *f;
    f = fopen("/dev/console", "w");
    if (f == NULL) { 
        result = 0;
        return (&result);
    }
    fprintf(f, "%s\en", *msg);
    fclose(f);
    result = 1;
    return (&result);
}
远程过程的声明printmessage_1在此步骤中与本地过程的步骤不同printmessage在第一步中,通过三种方式:
  • 它采用一个指向字符串的指针,而不是字符串本身。 所有远程过程都是如此,这些过程总是将指针指向它们的自变量,而不是自变量本身。
  • 它返回一个指向整数的指针,而不是整数本身。 对于远程过程也是如此,这些过程通常会返回指向其结果的指针。
  • 它有一个_1附加到其名称。 由 rpcgen 命令调用的远程过程由以下规则命名: 程序定义中的名称 (此处PRINTMESSAGE) 转换为所有小写字母,并附加 _ (下划线) 和版本号。
最后,程序员声明将调用远程过程的主客户机程序,如下所示:

/*
 * rprintmsg.c: remote version of "printmsg.c"
 */
#include <stdio.h> 
#include <rpc/rpc.h>     /* always needed */ 
#include "msg.h"  /* msg.h will be generated by rpcgen */
main(argc, argv) 
    int argc;
    char *argv[];
{
    CLIENT *cl;
    int *result;
    char *server;
    char *message;
    if (argc < 3) {
        fprintf(stderr,
        "usage: %s host message\en", argv[0]);
        exit(1);
    }

    /*
     * Save values of command line arguments
     */
    server = argv[1];
    message = argv[2];
    /*
     * Create client "handle" used for calling MESSAGEPROG on
     * the server designated on the command line.  We tell
     * the RPC package to use the "tcp" protocol when
     * contacting the server.
     */
    cl = clnt_create(server, MESSAGEPROG, MESSAGEVERS, "tcp");
    if (cl == NULL) {
         /*
          * Couldn't establish connection with server.
          * Print error message and die.
          */
          clnt_pcreateerror(server);
          exit(1);
    }
   /*
    * Call the remote procedure "printmessage" on the server
    */
    result = printmessage_1(&message, cl);
    if (result == NULL) {
        /*
         * An error occurred while calling the server.
         * Print error message and die.
         */
         clnt_perror(cl, server);
         exit(1);
    }
    /*
     * Okay, we successfully called the remote procedure.
     */
    if (*result == 0) {
        /*
         * Server was unable to print our message. 
         * Print error message and die.
         */
    fprintf(stderr, "%s: %s couldn't print your message\n",
        argv[0], server);
        exit(1);
    }
    /*
     * The message got printed on the server's console
     */
     printf("Message delivered to %s!\n", server);
     exit(0);
}
注意:
  1. 首先使用远程过程调用 (RPC) 库 clnt_create 例程创建客户机句柄。 此客户机句柄将传递到调用远程过程的存根例程。
  2. 远程过程printmessage_1调用方式与在 msg_proc.c 程序中声明的方式完全相同,但插入的客户机句柄作为第一个自变量除外。
客户机程序 rprintmsg 和服务器程序 msg_server 编译如下:

example%  rpcgen msg.x
example%  cc rprintmsg.c msg_clnt.c -o rprintmsg
example%  cc msg_proc.c msg_svc.c -o msg_server
但是,在编译之前, rpcgen 协议编译器用于对 msg.x 输入文件执行以下操作:
  • 它将创建一个名为 msg.h 的头文件,其中包含 #define 语句MESSAGEPROG,MESSAGEVERSPRINTMESSAGE以在其他模块中使用。
  • 它在 msg_clnt.c 文件中创建客户机存根例程。 在这种情况下,只有一个存根例程,即printmessage_1,从 printmsg 客户机程序引用。 客户机存根例程的输出文件的名称始终以此方式构成。 例如,如果输入文件的名称为 FOO.x,那么客户机存根的输出文件将称为 FOO_clnt.c
  • 它创建调用的服务器程序printmessage_1msg_proc.c 文件中。 此服务器程序名为 msg_svc.c。 用于命名服务器输出文件的规则类似于先前的规则。 例如,如果输入文件名为 FOO.x,那么输出服务器文件名为 FOO_svc.c