遠隔手続き呼び出し/Remtote Procedure Call

並行システム

                               システム情報系/情報工学域,
			       システム情報工学研究群/情報理工学位プログラム
			       システム情報工学研究科/コンピュータサイエンス専攻
                               新城 靖
                               <yas@cs.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19
あるいは、次のページから手繰っていくこともできます。
http://www.cs.tsukuba.ac.jp/~yas/cs/
http://www.cs.tsukuba.ac.jp/~yas/

■今日の重要な話

■遠隔手続き呼び出し/Remote Procedure Call

クライアント・サーバ・モデルに基づくプロセス間通信を 手続き呼出しの形で行う方法。 Remote Procedure Call (RPC) is interprocess communication based on a client server model. RPC looks like procedure call.

遠隔手続き呼び出しは、手続き呼び出しの一種ではない。プロセス間通信の 仕組みの1つ。 An RPC is not a procedure call. An RPC is an interprocess communication mechanism.

Referee: Andrew D. Birrell and Bruce Jay Nelson. "Implementing remote procedure calls", ACM Trans. Comput. Syst. Vol.2, No.1, pp.39-59 (February 1984). DOI=10.1145/2080.357392 https://dl.acm.org/doi/10.1145/2080.357392

◆スタブによるRPCの実現/Implementing RPC with stubs

例(example):手続き put()。ハッシュ表(hash table)にデータを格納する手続 きで、引数(arguments)にキー(key)となる文字列(string)と値(value)となる整 数(integer)を取る。

クライアントのmain()、クライアント側スタブ、サーバ側スタブ、サーバput()手続き。

図? スタブによるRPCの実現

特徴 スタブ(stub)の働きで、クライアント側のプロセスとサーバ側のプロセスは、 自動的にプロセス間通信を行うことができる。
クライアント側スタブ client stub
サーバ側スタブ server stub

◆スタブの作り方/how to make stubs

◆スタブの自動生成/automatic stub generation

インタフェース記述の例(example of interface description): ハッシュ表(hash table) key_t は文字列(string)。 SunPRC での記述方法。The interface description of a hash table in SunRPC.
typedef string key_t<256>; // A string. Its maximum length is 256.
struct keyvalue_t { 
   key_t key; 
   int   value ;
};
typedef key_t  keyarray_t<>; // A variable length array.

program HASHTABLE_PROG { 
   version HASHTABLE_VERSION {
       int        PUT(keyvalue_t)  = 11 ; 
       int        GETVALUE(key_t)  = 12 ; 
       keyarray_t GETKEYS(void)    = 13 ; 
   } = 1 ;
} = 0x20051001 ;
インタフェース定義をスタブ生成器(stub generator)に与えると、クライアン ト側スタブ(client stub)、サーバ側スタブ(server stub)が自動的に生成され る。

インタフェース記述の内容 Interface description

自動生成されるもの generated programs

◆バインディング/binding

バインディングとはクライアントとサーバの対応関係を、個々の手続き呼出し の前に決定すること。 Binding means fixing a server before a client performs an individual RPC.

クライアント3個、サーバ2個、対応関係を決める。

図? RPCのバインディング

クライアント側のプログラムとサーバ側のプログラムの対応はもはや1対1で はない。

◆サーバの複製(replication)

RPCのサーバを複数用意することが考えられる。 うまく作れば、あるサーバが落ちていても、別のサーバで対応できる。 バインディング時に、動いているサーバを選択する。

◆遠隔手続き呼び出しと通常の手続き呼出しの違い/differences between regular procedure calls and remote procedure calls

遠隔手続き呼び出しの意味「意味(semantics)」を、通常の手続き呼出しと同じ (透明)にしたい。しかし、完全に同じ「意味」を提供にすることは難しい。 この違いから、インタフェースを変えなければならないことがある。

◆インタフェース変更の例:read()/Examle of interface change

例: ファイルの内容を読むシステム・コール read()
ssize_t read(int fd, void *buf, size_t nbytes)
buf は、結果を受け取る場所を示したもので、RPCで遠隔に送る意味は ない。

方法1。インタフェースを変える。

struct read_result_t {
       ssize_t read_bytes;
       void *buf;
};
read_result_t read(int fd, size_t nbytes)
方法2: スタブで違いを吸収する。
ssize_t read(int fd, void *buf, size_t nbytes)
{
	fd, buf(nbytes分), nbytes を marshaling する。
	サーバへ要求メッセージとして送る。
	サーバから応答メッセージを受け取る。
	応答メッセージを unmarshaling して、結果の read_bytes と読んだ内容を取り出す。
	読んだ内容を buf read_bytes 分へコピーする。
	return read_bytes ;
}
単純に行えば、上のように余計なコピーが入ることがある。 スタブで最適化すれば、クライアントからサーバへのコピーを減らせる。

◆SunRPCでのポインタの扱い/Pointers in SunRPC

SunRPC では、ポインタの先の1要素だけコピーして送る機能がある。 SunRPC can send an item that is pointed by a pointer by copying the item.

この機能を使って送れるもの。 SunRPC can send following data structures.

送れないもの SunRPC cannot send following data structures.

◆RPCの実装例/Implementations of RPC

オブジェクト指向の考え方が含まれると、ORB (Object Request Broker) や分散オブジェクトと呼ばれることもある。

◆RPCの利点/Advantages of RPC

単なるメッセージ・パッシングと比較して Advantages of RPC over simple message passing RPC の問題点(単なるメッセージ・パッシングの利点) Disadvantages of RPC against simple message passing. ただし、自由度は、プログラムが難解になり、開発のコストを上げることがあ る。クライアント・サーバ・モデルに従うなら、RPCの方がよい。

XML-RPC, SOAP, JSON-RPC など、テキストを使う RPC もある。

■SunRPC

SunRPC は、Sun Microsystems社により開発され、仕様やソースコードが 公開された RPC の実装。ONC RPC (Open Network Computing) とも呼ばれる。 RFC にもなっている。

SunRPC は、 Transport として TCP と UDP が使える。 SunRPC を拡張して、Transport Independent にしたものが TI-RPC。

◆rpcgenコマンド/rpcgen command

SunRPC のスタブ生成器(stub generator)は、 rpcgen というコマンド。

◆rpcgenコマンドと生成されるファイル/genetated files in the rpcgen command

rpcgen コマンドを使うには、次のようなファイルを作成する。

rpcgenによるRPCプログラム開発で利用するファイル

図? rpcgenによるRPCプログラム開発で利用するファイル

開発者は次のようなプログラムを記述する。 A developer writes following programs.
name.x
インタフェースを記述。 Interface description
name_client.c
クライアント側の main プログラム。 The main program of the client.
name_server.c
サーバ側で、RPC で呼び出されるプログラム。 (main()関数は、rpcgen により自動生成される。) The server-side program that is called in RPC. (The main function is automatically generated by rpcgen.)

◆rpcgenコマンドの使い方/Usage of rpcgen command

% rpcgen name.x [←]
次の4つのファイルが生成される。 The rpcgen command generates four files.
name.h
そのRPCのプログラムで使う定数、データ構造、スタブ手続きのインタフェー ス。 Constants,data structures,interfaces of stub functions.
name_clnt.c
クライアント側のスタブ。 Stubs for a client.
name_xdr.c
name.x で定義したデータ構造について、 eXternal Data Representation (XDR) のための手続き(マーシャリングとアンマーシャリングを行なう手続き) 。 クライアント側とサーバ側の両方で使われる。 Functions that performs marshaling and unmarshaling based on eXternal Data Representation (XDR). These functions are used by both clients and servers.
name_svc.c
サーバ側の main 関数とディスパッチ手続き。受け付けた RPC の要求を解析 して、開発者が定義した手続きを呼び出す。 The main function and the dispatch function of a server.
これらのファイルの内容は、人間が十分読める。

◆XDR (eXternal Data Representation)

SunRPC で使われている marshaling と unmarshaling の方法。

◆手続きの識別/Identifying procedures

SunRPCでは、手続きの識別を、次のような情報で行う

◆portmap (portmapper,rpcbind)

SunRPC では、TCP/IP や UDP/IP のポート番号(port numbers)は動的 (dynamic)に決められる。

クライアント、サーバ、portmapper

図? portmapper の働き

SunRPCでは, 最終的にはTCP/IPまたはUDP/IPでデータが送られる。プログラム 番号などTCP/IPまたはUDP/IPのポート番号を対応させる必要がある。

各ホストには portmap (portmapper) というサーバがいて、3つ組

Program number,version number,protocol
を、TCP/IPまたはUDP/IPのポート番号へ変換する。

◆rpcinfo

portmap の情報は、 rpcinfo コマンドで表示できる。

% rpcinfo -p [←]
   program vers proto   port
    100000    2   tcp    111  portmapper
    100000    2   udp    111  portmapper
    100007    2   tcp   1024  ypbind
    100007    2   udp   1027  ypbind
    100007    1   tcp   1024  ypbind
    100007    1   udp   1027  ypbind
    100005    1   udp    831  mountd
    100005    2   udp    831  mountd
    100005    1   tcp    834  mountd
    100005    2   tcp    834  mountd
    100003    2   udp   2049  nfs
    100001    2   udp   4193  rstatd
    100001    3   udp   4193  rstatd
    100001    4   udp   4193  rstatd
% []
他のホストの情報も調べられる。
% rpcinfo -p hostname [←]
サーバは、起動時に、portmap に登録する。
bool_t
pmap_set(program, version, protocol, port)
		  u_long program;
		  u_long version;
		  int protocol;
		  u_short port;
クライアントは、呼び出す前に、ポート番号を調べる。
u_short
pmap_getport(address, program, version, protocol)
		      struct sockaddr_in *address;
		      u_long program;
		      u_long version;
		      u_int protocol;

スタブが自動的に呼び出すので、普段は気にすることはない。

portmap 自身も RPC で動いている。portmap の自身のポート番号は、111 と 決まっている。

rpcbind (portmap) が動作しているのに、rpcinfo -p でつながらない時には、 パケットフィルタやファイアウォールでポート番号 111 を止めていないか調べ る。

◆portmap (rpcbind) の実行

macOS 等で、portmap が動作していないことがある。その時は、rpcinfo -p localhost で エラーが出る。
$ rpcinfo -p [←]
Can't contact rpcbind on localhost
rpcinfo: RPC: Remote system error - Connection refused
$ []
その時は、root で 次のようにして rpcbind を実行する。
# launchctl start com.apple.rpcbind [←]

実験が終わったら、root で 次のようにして rpcbind を終了する。

# launchctl stop com.apple.rpcbind [←]

Linux 等、その他のシステムで、rpcinfo -p localhost がうまく表示できな い時には、次のパッケージを入れてみる。

それでも、うまくうまくいかない時には、 NFS クライアントをインストールすると良い。 NFS クライアントでも、普通、NFS でロックを動かす時に必要になるプログラ ム(rpc.statd)が RPC で動作する。

NFS クライアントでうまくいかない時には、NFS サーバを試す。

■SunRPCを使った分散アプリケーションの例/An distributed application with SunRPC

カウンタ (a counter)
void up(void)
カウンタを増やす。 Increment the counter.
int getvalue(void)
カウンタの値を得る。 Get the current value of the counter.
void reset(int)
カウンタの値を設定する。 Reset the value of the counter with the given parameter.

◆ローカルのプログラム/Local counter program

   1: 
   2: /*
   3:         counter-local.c -- Local counter program in C
   4:         Created on: 2012/01/18 22:19:35
   5: */
   6: 
   7: #include <stdio.h>      /* stderr, fprintf() */
   8: 
   9: static int val;
  10: 
  11: void
  12: counter_up(void)
  13: {
  14:         val ++;
  15: }
  16: 
  17: int
  18: counter_getvalue(void)
  19: {
  20:         return( val );
  21: }
  22: 
  23: void
  24: counter_reset(int new_value)
  25: {
  26:         val = new_value;
  27: }
  28: 
  29: int main( int argc, char *argv[], char *envp[] ) {
  30:     int i;
  31:         for( i=0; i<3; i++ )
  32:         {
  33:             counter_up();
  34:             printf("counter value == %d\n",counter_getvalue());
  35:         }
  36: }
実行例
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/counter-local.c [←]
% make counter-local [←]
gcc     counter-local.c   -o counter-local
% ./counter-local  [←]
counter value == 1
counter value == 2
counter value == 3
% ./counter-local [←]
counter value == 1
counter value == 2
counter value == 3
% []

◆インタフェース記述/Interface description

RPC では、手続き呼び出しの形でインタフェースを記述する。 マーシャリングとアンマーシャリングを意識する必要はあまりない。

   1: program COUNTER_PROG { 
   2:    version COUNTER_VERSION {
   3:        void     COUNTER_UP(void)       = 11 ; 
   4:        int      COUNTER_GETVALUE(void) = 12 ; 
   5:        void     COUNTER_RESET(int)     = 13 ; 
   6:    } = 1 ;
   7: } = 0x20052001 ;
COUNTER_UP COUNTER_GETVALUE COUNTER_RESET が手続きの名前(手続き番号を示す定数の定義)。

手続きの定義を program 番号と version 番号の括弧が取り囲んでいる。

◆生成されるヘッダファイル/generated header file

インタフェース定義から、rpcgen コマンドによりヘッダファイル、クライア ント側スタブ、サーバ側スタブ、XDR によるマーシャリングを行うプログラム が生成される。

システムによって生成されるものが異なる。 ヘッダファイルの主要部分は、以下の通りである。

...
  14: #define COUNTER_PROG ((rpc_uint)0x20052001)
  15: #define COUNTER_VERSION ((rpc_uint)1)
...
  28: #elif __STDC__
  29: #define COUNTER_UP ((rpc_uint)11)
  30: extern  void * counter_up_1(void *, CLIENT *);
  31: extern  void * counter_up_1_svc(void *, struct svc_req *);
  32: #define COUNTER_GETVALUE ((rpc_uint)12)
  33: extern  int * counter_getvalue_1(void *, CLIENT *);
  34: extern  int * counter_getvalue_1_svc(void *, struct svc_req *);
  35: #define COUNTER_RESET ((rpc_uint)13)
  36: extern  void * counter_reset_1(int *, CLIENT *);
  37: extern  void * counter_reset_1_svc(int *, struct svc_req *);
...

◆counter-client.c

クライアント・プログラムの例。 ローカル・プログラム参照 (See also thelocal program)

   1: 
   2: /*
   3:         counter-client.c -- counter client based on SunRPC
   4:         Created on: 2011/12/21 20:56:17
   5: */
   6: 
   7: #include <stdio.h>      /* stderr, fprintf() */
   8: #include <string.h>
   9: #include <stdlib.h>     /* exit() */
  10: #include "counter.h"
  11: 
  12: void usage( char *comname ) {
  13:         fprintf(stderr,"Usage: %% %s server-host\n", comname);
  14:         exit( 1 );
  15: }
  16: 
  17: int main( int argc, char *argv[], char *envp[] ) {
  18:     char *server ;
  19:     CLIENT *clnt;
  20:     char *counter_up_arg; /* dummy */
  21:     void *counter_up_result;
  22:     char *counter_getvalue_arg; /* dummy */
  23:     int  *counter_getvalue_result;
  24:     int i;
  25: 
  26:         if( argc != 2 )
  27:             usage( argv[0] );
  28:         server = argv[1];
  29:         clnt = clnt_create(server,COUNTER_PROG,
  30:                            COUNTER_VERSION, "tcp");
  31:         if( clnt == NULL ) {
  32:             clnt_pcreateerror( server );
  33:             exit( 1 );
  34:         }
  35: 
  36:         for( i=0; i<3; i++ )
  37:         {
  38:             counter_up_result = counter_up_1((void *)&counter_up_arg, clnt);
  39:             if( counter_up_result == NULL ) {
  40:                 clnt_perror(clnt, "call failed -- up");
  41:                 exit( 2 );
  42:             }
  43:             xdr_free( (xdrproc_t)xdr_void, (char *)counter_up_result );
  44: 
  45:             counter_getvalue_result = counter_getvalue_1((void *)&counter_getvalue_arg, clnt);
  46:             if( counter_getvalue_result == (void *) NULL )
  47:             {
  48:                 clnt_perror(clnt, "call failed -- getvalue");
  49:                 exit( 2 );
  50:             }
  51:             printf("value==%d\n", *counter_getvalue_result);
  52:             xdr_free( (xdrproc_t)xdr_int, (char *)counter_up_result );
  53:         }
  54:         clnt_destroy( clnt );
  55: }
この関数の中心部分は、自動生成されたスタブ counter_up_1(),counter_getvalue_1() を呼び出す部分である。 引数は、インタフェースで定義された型(この例ではダミーの voidへの ポインタ)と、CLIENT 構造体へのポインタ、結果は、インタ フェースで定義された型(void, int)へのポインタである。

スタブcounter_up_1(),counter_getvalue_1(),counter_reset_1() は、 rpcgen が生成する*_clnt.c というファイルに含まれてる。こ れは、引数をマーシャリングして要求メッセージを組み立て、サーバに送信す る。サーバから応答メッセージを受け取り、アンマーシャリングして、結果と して返す。アンマーシャリングの時、結果を保持するメモリを、malloc() で確 保している。

clnt_create() は、CLIENT 構造体を確保する。 引数は、サーバのホスト名、 プログラム番号バージョン番号、 通信に使うプロトコルである。 使い終わった構造体は、clnt_destroy() で解放する。

結果は、 void* か int*である。 xdr_free() で、アンマーシャリングの時に確保したメモリを解放している。 内部では、free() が呼ばれている。 (int や void を返す手続きでは、実際には static 変数の番地を return しているので何もしない。)

◆サーバ側のプログラム/The server-side program

   1: 
   2: /*
   3:         counter-server.c -- The counter server using SunRPC.
   4: */
   5: 
   6: #include <stdio.h>      /* stderr, fprintf() */
   7: #include <stdlib.h>     /* strtol() */
   8: #include <string.h>     /* memcpy() */
   9: #include "counter.h"
  10: 
  11: static int val;
  12: 
  13: void *
  14: counter_up_1_svc(void *argp, struct svc_req *rqstp)
  15: {
  16:         static char* result;
  17:         val ++;
  18:         return((void*) &result);
  19: }
  20: 
  21: int *
  22: counter_getvalue_1_svc(void *argp, struct svc_req *rqstp)
  23: {
  24:         static int  result;
  25:         result = val;
  26:         return(&result);
  27: }
  28: 
  29: void *
  30: counter_reset_1_svc(int *argp, struct svc_req *rqstp)
  31: {
  32:         static char* result;
  33:         val = *argp;
  34:         return((void*) &result);
  35: }
サーバ側では、手続き counter_up_1_svc(),counter_getvalue_1_svc(),counter_reset_1_svc() を記述する。 引数と結果は、rpcgen のソースで定義した構造体へのポインタ。

結果を返す時の構造体へのポインタを返す方法が問題。自動変数(auto 変数) にすると、呼出し側に戻った瞬間に無効になる。よく使われるのは、静的変数 (static 変数)に結果を代入して、返すことだが、マルチスレッドプログラミ ングでは問題になる。

サーバ側の main() 関数は、rpcgen により 自動生成される。このmain()では、 ポートマッパ(port mapper) へのプログラム番号とバージョン番号の登録される。

◆実行例/Execution results

% mkdir counter-rpc [←]
% cd counter-rpc [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/counter-client.c [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/counter-server.c [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/counter.x [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/Makefile [←]
% ls [←]
Makefile  counter-client.c  counter-server.c  counter.x
% make counter-client [←]
rpcgen counter.x
gcc -g -DRPC_SVC_FG   -c -o counter-client.o counter-client.c
gcc -g -DRPC_SVC_FG   -c -o counter_clnt.o counter_clnt.c
gcc -g -DRPC_SVC_FG  counter-client.o counter_clnt.o  -o counter-client
% make counter-server [←]
gcc -g -DRPC_SVC_FG   -c -o counter-server.o counter-server.c
gcc -g -DRPC_SVC_FG   -c -o counter_svc.o counter_svc.c
gcc -g -DRPC_SVC_FG counter-server.o counter_svc.o   -o counter-server
% ls [←]
Makefile          counter-server    counter.x       counter_svc.o
counter-client    counter-server.c  counter_clnt.c
counter-client.c  counter-server.o  counter_clnt.o
counter-client.o  counter.h         counter_svc.c
% []
この例では、rpcgen では 3 つのファイル counter.h counter_clnt.c counter_svc.c が作られる。

Linux Ubuntu で rpcgen コマンドがない、rpc.h が見付からない時には、次 のようなパッケージを入れる。

Linux RedHat で rpcgen コマンドがない、rpc.h が見付からない時には、次 のようなパッケージを入れる。 rpc/rpc.h が見付からないと言われたら Makefile にある次の行をコメントアウトする。
#CFLAGS = -g -DRPC_SVC_FG -I/usr/include/tirpc
#rpc_libs = -ltirpc
clnt_create が見付からないと言われたら Makefile にある次の行をコメントアウトする。
#CFLAGS = -g -DRPC_SVC_FG -I/usr/include/tirpc
#rpc_libs = -ltirpc

macOS 等で rpcgen が生成したファイル counter_clnt.c がコンパイルできない時には、 Makefile にある次の行をコメントアウトする。

#CFLAGS = -g -DRPC_SVC_FG -Wno-error=implicit-function-declaration -Wno-incompatible-pointer-types -Wno-implicit-function-declaration

例題を実行するには、クライアント用とサーバ用に端末を2つ開く。 その方法は、make help で表示される。

クライアントとサーバは、別のコンピュータで動作させても良い。 以下の例では、同じコンピュータで動かし、 クライアント側では localhost を指定して実行している。

サーバの実行。Run the server.

% ./counter-server  [←]
サーバは終了しないので、実験が終わったら ^C で止める。

クライアント側は、もう1つの端末で実行する。 rpcinfo コマンドで、サーバのプログラム番号、プロトコルポート番号 を確認する。

% rpcinfo -p localhost [←]
   program vers proto   port
    100000    2   tcp    111  portmapper
    100000    2   udp    111  portmapper
    100024    1   udp    977  status
    100024    1   tcp    980  status
    100021    1   udp  36430  nlockmgr
    100021    3   udp  36430  nlockmgr
    100021    4   udp  36430  nlockmgr
    100021    1   tcp  35786  nlockmgr
    100021    3   tcp  35786  nlockmgr
    100021    4   tcp  35786  nlockmgr
 537206785    1   udp  45125
 537206785    1   tcp  52142
% []
537206785 は、16進数で0x20052001。

クライアントを実行する。Run the clietn。

% ./counter-client  [←]
Usage: % ./counter-client server-host
% ./counter-client localhost [←]
value==1
value==2
value==3
% ./counter-client localhost [←]
value==4
value==5
value==6
% ./counter-client localhost [←]
value==7
value==8
value==9
% []
実験が終了したら、サーバを ^C コマンドで削除する。
% ./counter-server  [←]
^C
% []

◆Mercury

Mercury は、RPC の実装の1つ。HPC で使われる。

マーシャリングには、SunRPC と同様に、ポインタの先を送る事ができる。リ スト構造や木構造が扱える。ただし、SunRPC とは違い、自分でマーシャリン グのための手続きを定義しなければならない。

例: マーシャリングのための手続き hg_proc_int_list_t() の手書き。 Serializing complex data structures

SunRPC なら、次のように書くと、マーシャリングのための関数 xdr_int_list() が自動生成される。

   1: struct int_list {
   2:   int              value;
   3:   struct int_list *next;
   4: };
rpcgen で自動生成されたマーシャリングのための関数。
   1: /*
   2:  * Please do not edit this file.
   3:  * It was generated using rpcgen.
   4:  */
   5: 
   6: #include "int_list.h"
   7: 
   8: bool_t
   9: xdr_int_list(xdrs, objp)
  10:         XDR *xdrs;
  11:         int_list *objp;
  12: {
  13: 
  14:         if (!xdr_int(xdrs, &objp->value))
  15:                 return (FALSE);
  16:         if (!xdr_pointer(xdrs, (char **)&objp->next, sizeof(int_list), (xdrproc_t)xdr_int_list))
  17:                 return (FALSE);
  18:         return (TRUE);
  19: }

■PRCにおける障害対策/Dealing with failures in RPC

◆RPCにおける障害/ failures in RPC

portmapper が binding

図? RPCで起こりうる障害

◆クライアントがサーバを見つけられない/A client does not find a server.

クライアントで、スタブを呼出した時にエラーが起きるかもしれない。 これらを使うと、もはや透明ではない。

◆要求メッセージが紛失/Losing a request message

クライアントは、時間切れで要求メッセージを再送すれば、簡単に回復できる。 しかし、以下の他のエラーとの区別を付けるのが難しい。

◆応答メッセージが紛失/Losing a reply message.

応答メッセージが失われた場合、回復は難しい。 クライアントは、単純に時間切れでメッセージの再送をすることはできない。

例: 銀行預金転送。

対策:

◆要求受信後、サーバがクラッシュ/A server crashes after receiving a request message.

番号では対応できない。要求実行前と実行後を区別できない。

RPCのセマンティクス

集中だと、クライアントもサーバもいっしょに死ぬので、問題はない。

◆要求送信後、クライアントがクラッシュ/A client crashes after sending a request message.

孤児問題(orphan problem)。

サーバにあり、親(クライアント)がいない計算を孤児という。

対応方法

根絶(extermination)。
手続き実行前にログに書く。クラッシュしたらログを見て根絶する。重たい。 孫の孤児(grandorphan)問題がある。
再生(reincarnation)
クライアントは、リブートすると、タイムスタンプをサーバに投げる。サーバは、 全孤児を消去する。
穏和な再生(gentle reincarnation)
タイムスタンプを受信すると、サーバは親を探し、見つからない時だけ孤児を消去する。
期限切れ(expiration)
RPC に標準時間Tを与え、その時間内に終了しないものを消去する。 それ以上かかる時には、明示的に要求する。クラッシュ後、サーバがTだけ待てば 孤児は消える。

孤児が、ロックを持っていたら、孤児を消しただけでは話を終わらない。

◆SunRPC のクライアント側で見えるエラー/Visible errors in a client of SunRPC

Linux, /usr/include/rpc/clnt.h
/*
 * Rpc calls return an enum clnt_stat.  This should be looked at more,
 * since each implementation is required to live with this (implementation
 * independent) list of errors.
 */
enum clnt_stat {
        RPC_SUCCESS=0,                  /* call succeeded */
        /*
         * local errors
         */
        RPC_CANTENCODEARGS=1,           /* can't encode arguments */
        RPC_CANTDECODERES=2,            /* can't decode results */
        RPC_CANTSEND=3,                 /* failure in sending call */
        RPC_CANTRECV=4,                 /* failure in receiving result */
        RPC_TIMEDOUT=5,                 /* call timed out */
        /*
         * remote errors
         */
        RPC_VERSMISMATCH=6,             /* rpc versions not compatible */
        RPC_AUTHERROR=7,                /* authentication error */
        RPC_PROGUNAVAIL=8,              /* program not available */
        RPC_PROGVERSMISMATCH=9,         /* program version mismatched */
        RPC_PROCUNAVAIL=10,             /* procedure unavailable */
        RPC_CANTDECODEARGS=11,          /* decode arguments error */
        RPC_SYSTEMERROR=12,             /* generic "other problem" */
        RPC_NOBROADCAST = 21,           /* Broadcasting not supported */
        /*
         * callrpc & clnt_create errors
         */
        RPC_UNKNOWNHOST=13,             /* unknown host name */
        RPC_UNKNOWNPROTO=17,            /* unknown protocol */
        RPC_UNKNOWNADDR = 19,           /* Remote address unknown */

        /*
         * rpcbind errors
         */
        RPC_RPCBFAILURE=14,             /* portmapper failed in its call */
#define RPC_PMAPFAILURE RPC_RPCBFAILURE
        RPC_PROGNOTREGISTERED=15,       /* remote program is not registered */
        RPC_N2AXLATEFAILURE = 22,       /* Name to addr translation failed */
        /*
         * unspecified error
         */
        RPC_FAILED=16,
        RPC_INTR=18,
        RPC_TLIERROR=20,
        RPC_UDERROR=23,
        /*
         * asynchronous errors
         */
        RPC_INPROGRESS = 24,
        RPC_STALERACHANDLE = 25
};

◆冪等な操作/idempotent operation

冪等(idempotent)な操作とは、 その操作を何回繰り返しても、1回だけ実行した時と同じ結果になるもの。

例:

idempotentではない操作

◆無状態サーバ/stateless server

無状態サーバ(stateless server) とは、サーバ内部に状態を保持しないようなサーバ。

状態の例

RPCで冪等な操作や無状態サーバを実現すると、クラッシュに強いシステムを 作れる。クライアントは、サーバから応答がない場合、何度要求を再送信して もよい。

例:NFS Version 2

サーバは、ファイルに対する書き込み要求を受け付けると、ディスクへ の書き込みを完了してから応答を返す。 応答が返ってきた要求は、 ディスクへの書き込みが完了したことが保証されている。 この段階でサーバがクラッシュしたとしても、なにも失われない。

しかし、、、重たい。NFS Version 3 では、状態付きのサーバになった。

■NFS

NFS ( Network File System ) は, Sun Microsystems 社が 開発したネットワーク・ファイル・システムの名前(固有名詞, 商標)。

ネットワーク・ファイル・システム(一般名詞)
ネットワークを通じて他のコンピュータ上にあるファイルを あたかも自分自身のローカルディスクにあるファイ ルと同じように扱えるようにしたファイルシステム
分散ファイルシステム
ネットワーク・ファイル・システムが発展して 「分散透明性(分散透過性、network transparency)」 が実現されたもの。
NFS は、Unix 系の OS (MacOSX 含む) では、事実上の標準。

その他のネットワーク・ファイル・システム(用のプロトコル)

◆NFSの機能

NFSを使うと, ネットワークを通じて別のコンピュータ上のファイルシステム の一部分を, ローカルディスク上にあるファイルシステムと同じように, 自分 のファイルシステムの木に マウント(mount) できる。

図? NFSによるファイルの共有

図? NFSによるファイルの共有

相互に参照し合える。

表? NFSで使われているRPCの手続き

手続き名 意味 関連するコマンド、 システムコール
null() 何もしない rpcinfo -u hostname nfs コマンド
getattr() 属性の読み出し ls -l コマンド, stat システムコール , open システムコール
setattr() 属性の設定 chmod , chown コマンド
lookup() ファイルの検索 open システムコール
readlink() シンボリックリンクの読み出し ls -l コマンド, readlink システムコール
read() ファイルの読み出し read システムコール
write() ファイルの書き込み write システムコール
create() ファイルの作成 creat システムコール, open システムコール
remove() ハードリンクの削除 rm コマンド, unlink システムコール
rename() ファイル名前の変更 mv コマンド, rename システムコール
link() ハードリンクの作成 ln コマンド, link システムコール
symlink() シンボリックリンクの作成 ln -s コマンド, symlink システムコール
mkdir() ディレクトリの作成 mkdir コマンド
rmdir() ディレクトリの削除 rmdir コマンド
readdir() ディレクトリの読み出し ls コマンド
statfs() ファイルシステムの利用状況 df コマンド, statfs システムコール
commit()* ディスクへの書き込み fsync システムコール
access()* アクセス権のチェック access システムコール
open()** ファイルを開く。
close()** ファイルを閉じる。
lock()** ファイルのロック。
renew()** ファイルのロックの更新。
compound()** 複合手続き。複数の手続きをまとめて実行する。

* は、NFS Version 3 で追加された手続き。

** は、NFS v4 で追加された手続き。

◆NFSファイルハンドル

NFS でファイルやディレクトリを区別するための識別子。32バイト。

const NFS_FHSIZE	= 32;
...
/*
 * File access handle
 */
struct nfs_fh {
	opaque data[NFS_FHSIZE];
};
opaque (不定形)は、バイト配列の意味。 固定長のデータがそのままネットワークを流れる。 SunRPC では、char data[NFS_FHSIZE] と書くと、本当に文字の配列の意味に なり、文字単位でマーシャリングが行われる。

nfs_fh の内容は、サーバが独自に(勝手に)決める。

一番最初のNFSファイル・ハンドルをどうやって入手するか。

◆NFSマウントのためのRPCプログラム

NFS v2, NFS v3 では、NFS 本体とは別にディレクトリ木のルートを得るため のRPC のプログラム(MOUNTPROG)がある。 /usr/include/rpcsvc/mount.x
手続き名 意味 関連するコマンド、 システムコール
null() 何もしない rpcinfo -u hostname mount コマンド
mnt() NFSファイルハンドルを返す mount コマンド
dump() マウント一覧表 showmount hostname コマンド
umnt() アンマウント umount コマンド
umntall() 全アンマウント umount -h hostname コマンド
export() アクセス可能なディレクトリのリストを返す  

◆lookup-read-write

NFS (v2,v3) には、次の手続きがない。 open() -- while(...) {read() or write()} -- close() のようなプログラムは、 NFS のレベルでは、lookup(); while( ... ) {read() or write()} になる。 read(), write() の引数は、必ずファイル中の読み書きする位置が含まれる。

◆lookup()

引数
検索対象のディレクトリの NFSファイルハンドルとファイル名
結果
ファイル、または、ディレクトリの NFS ファイル・ハンドルと属性
ファイル名には、区切り「/」は含まれない。要素ごとに lookup する。

2.2.5.  Look Up File Name

	diropres
	NFSPROC_LOOKUP(diropargs) = 4;

If the reply "status" is NFS_OK, then the reply "file" and reply
"attributes" are the file handle and attributes for the file "name"
in the directory given by "dir" in the argument.

2.3.10.  diropargs

    struct diropargs {
	fhandle  dir;
	filename name;
    };

The "diropargs" structure is used in directory operations.  The
"fhandle" "dir" is the directory in which to find the file "name".
A directory operation is one in which the directory is affected.

2.3.11.  diropres

    union diropres switch (stat status) {
    case NFS_OK:
	struct {
	    fhandle file;
	    fattr   attributes;
	} diropok;
    default:
	void;
    };

The results of a directory operation are returned in a "diropres"
structure.  If the call succeeded, a new file handle "file" and
the "attributes" associated with that file are returned along with
the "status".

◆read()

引数
対象ファイルの NFSファイルハンドル、読み込む位置(先頭からのオフセット)、バイト数
結果
属性、データ(opaque型)
2.2.7.  Read From File

	struct readargs {
		fhandle file;
		unsigned offset;
		unsigned count;
		unsigned totalcount;
	};

	union readres switch (stat status) {
	case NFS_OK:
		fattr attributes;
		nfsdata data;
	default:
		void;
	};

	readres
	NFSPROC_READ(readargs) = 6;

Returns up to "count" bytes of "data" from the file given by "file",
starting at "offset" bytes from the beginning of the file.  The first
byte of the file is at offset zero.  The file attributes after the
read takes place are returned in "attributes".

Notes:  The argument "totalcount" is unused, and is removed in the
next protocol revision.

◆write()

引数
対象ファイルの NFSファイルハンドル、書き込む位置(先頭からのオフセット)、データ(バイト数数含む)
結果
属性
2.2.9.  Write to File

	struct writeargs {
		fhandle file;
		unsigned beginoffset;
		unsigned offset;
		unsigned totalcount;
		nfsdata data;
	};

	attrstat
	NFSPROC_WRITE(writeargs) = 8;

Writes "data" beginning "offset" bytes from the beginning of "file".
The first byte of the file is at offset zero.  If the reply "status"
is NFS_OK, then the reply "attributes" contains the attributes of the
file after the write has completed.  The write operation is atomic.
Data from this "WRITE" will not be mixed with data from another
client's "WRITE".

Notes:  The arguments "beginoffset" and "totalcount" are ignored and
are removed in the next protocol revision.

◆cookie

RPC のように結合(connection)が作られない通信サービスを使う時に冪等や無状態 といった性質を実現する時に必要になる技術。

例:NFSでのディレクトリの読み込み手続き nfsproc_readdir() で、1回の RPC で全部のデータを返せないことが起きる。 ディレクトリのどの位置まで読み込んだかを 示す中間状態を クッキー(cookie) という形でクライアントに返す。

クライアントは、次の RPC の呼び出しで、 前回受けとった応答の中のクッキーを、サーバへの要求に含めて送す。

◆readdir()

ls コマンドは、opendir() ライブラリ関数、getdirentries() システムコール (MacOSX, FreeBSD) を経て、NFS のレベルでは、readdir() になる。
引数
対象ディレクトリの NFSファイルハンドル、クッキー、バイト数
結果
エントリのリスト
各エントリは、名前、クッキー、inode番号からなる。
const NFS_COOKIESIZE	= 4;
typedef opaque nfscookie[NFS_COOKIESIZE];
2.2.17.  Read From Directory

	 struct readdirargs {
		 fhandle dir;
		 nfscookie cookie;
		 unsigned count;
	 };

	 struct entry {
		 unsigned fileid;
		 filename name;
		 nfscookie cookie;
		 entry *nextentry;
	 };

	 union readdirres switch (stat status) {
	 case NFS_OK:
		 struct {
			 entry *entries;
			 bool eof;
		 } readdirok;
	 default:
		 void;
	 };

	 readdirres
	 NFSPROC_READDIR (readdirargs) = 16;

 Returns a variable number of directory entries, with a total size of
 up to "count" bytes, from the directory given by "dir".  If the
 returned value of "status" is NFS_OK, then it is followed by a
 variable number of "entry"s.  Each "entry" contains a "fileid" which
 consists of a unique number to identify the file within a filesystem,
 the "name" of the file, and a "cookie" which is an opaque pointer to
 the next entry in the directory.  The cookie is used in the next
 READDIR call to get more entries starting at a given point in the
 directory.  The special cookie zero (all bits zero) can be used to
 get the entries starting at the beginning of the directory.  The
 "fileid" field should be the same number as the "fileid" in the the
 attributes of the file.  (See section "2.3.5. fattr" under "Basic
 Data Types".)  The "eof" flag has a value of TRUE if there are no
 more entries in the directory.

nfsproc_readdir() で、1回目と2回目の RPC の間にディレクトリの内容が 更新された場合、どのような結果になるのか不明。

◆NFS非同期入出力デーモン

NFS非同期入出力デーモン ( nfsiod (local NFS asynchronous I/O Daemon) または biod (asynchronous Block I/O Daemon) ) は、NFSのクライアントホスト上で動き、NFSの非同期的な入出力を行う。

◆NFS lockdとrstatd

NFS v2, NFS v3 には、ロックの機能が元々存在しない。 実装するのが非常に難しいので、それを認めて、仕様から削除した。

次のような障害に対応することが簡単ではない。

後に lockd と statd という2つの外付けのプログラムでロックの機能を付加し た(他の実装もある)。

図? NFS-client-lockd-statd-kernel NFS-server-lockd-kernel

図? NFSにおけるlockd と statd の役割

クライアント側の lockd

サーバ側の lockd サーバ側の statd クライアント側の statd

◆NFS Version 3

◆NFS Version 4

2003年

■XML Web Services

◆XML

XML(eXtensible Markup Language)

XML は、World Wide Web (WWW) で使われている HTML (Hyper Text Markup Language) と同様に、マークアップ言語(markup language)の一種。

マークアップ言語とは、文書(テキスト)に、「ここは表題」、「ここは箇条 書」といった、文書の構造を示す目印(マーク)を付ける機能を持つ言語。

World Wide Web コンソーシアムのXMLのページ
https://www.w3.org/XML/

◆What is a Web service?

Web service とは、非常に紛らわしい用語であるが、本来の意味は、 Web of services であり、XML と World Wide Web の技術を用いてソフトウェア・コンポー ネント(部品)をネットワークを通じて利用可能にしたものである。

The term "Web service" is derived from "Web of services". In Web services, software components use other software components over the network by using XML and the World Wide Web technology.

Web service を、一般の World Wide Web と紛れないように呼ぶ時には、 「XML Web Service」と言う。

◆プログラミング手法の発展/evolution of programming methods

XML Web Services でよく使われている遠隔手続き呼出しの仕組みは、 XML-RPC と SOAP である。

XML Web Servicesでは、異なるプログラミング言語で書かれたコンポーネントも相 互に接続できる。

◆XML Web service and World Wide Web

一般的な Web。Normal World Wide Web。
XML Web Service

◆WSDL (Web Services Description Language)

WSDL は、XML Web Servicesの内容を説明するための、XML に基づく言語。

WSDL で記述するもの

・データ型。Data types.
XML Web Servicesのクライアントとサーバの間で交換され るデータ形式の定義。
・メッセージ。 Messages.
要求メッセージ(クライアントからサーバへ送られ る)と応答メッセージ(逆方向)の形式。(厳密には、Web サービ スでは単方向の通信も許されている。)
・手続きの集合(portType)。A set of procedures.
オブジェクト指向言語のクラスに相当する。A classe in an object-oriented language.
・通信プロトコルとの対応(binding)。binding with communication protocols.
多くの XML Web services では、通信のために SOAP が使われる。 Many XML Web Services use SOAP.
・サーバの位置(service)。Location of services.
URL などで記述される。described in a URL.
XML Web Servicesでは、WSDL により、クライアントとサーバの間のインタフェー スを規定する。WSDL によるのインタフェース記述より、クライアント側とサー バ側のスタブを自動的に生成することができる。

We can generate client stubs and server stubs from WSDL.

◆WSDLの例(Glueにより自動生成されたもの)/Example of WSDL (automatically generated by Glue)

[c1.wsdl]
   1: <?xml version='1.0' encoding='UTF-8'?>
   2: 
   3: <!--generated by GLUE-->
   4: 
   5: <definitions name='Counter'
   6:     targetNamespace='http://www.themindelectric.com/wsdl/Counter/'
   7:     xmlns:tns='http://www.themindelectric.com/wsdl/Counter/'
   8:     xmlns:electric='http://www.themindelectric.com/'
   9:     xmlns:soap='http://schemas.xmlsoap.org/wsdl/soap/'
  10:     xmlns:http='http://schemas.xmlsoap.org/wsdl/http/'
  11:     xmlns:mime='http://schemas.xmlsoap.org/wsdl/mime/'
  12:     xmlns:xsd='http://www.w3.org/2001/XMLSchema'
  13:     xmlns:soapenc='http://schemas.xmlsoap.org/soap/encoding/'
  14:     xmlns:wsdl='http://schemas.xmlsoap.org/wsdl/'
  15:     xmlns='http://schemas.xmlsoap.org/wsdl/'>
  16: 
  17:     <message name='getValue0SoapIn'/>
  18:     <message name='getValue0SoapOut'>
  19:         <part name='Result' type='xsd:int'/>
  20:     </message>
  21:     <message name='reset1SoapIn'>
  22:         <part name='newVal' type='xsd:int'/>
  23:     </message>
  24:     <message name='reset1SoapOut'/>
  25:     <message name='up2SoapIn'/>
  26:     <message name='up2SoapOut'/>
  27: 
  28:     <portType name='CounterSoap'>
  29:         <operation name='getValue'>
  30:             <input name='getValue0SoapIn' message='tns:getValue0SoapIn'/>
  31:             <output name='getValue0SoapOut' message='tns:getValue0SoapOut'/>
  32:         </operation>
  33:         <operation name='reset' parameterOrder='newVal'>
  34:             <input name='reset1SoapIn' message='tns:reset1SoapIn'/>
  35:             <output name='reset1SoapOut' message='tns:reset1SoapOut'/>
  36:         </operation>
  37:         <operation name='up'>
  38:             <input name='up2SoapIn' message='tns:up2SoapIn'/>
  39:             <output name='up2SoapOut' message='tns:up2SoapOut'/>
  40:         </operation>
  41:     </portType>
  42: 
  43:     <binding name='CounterSoap' type='tns:CounterSoap'>
  44:         <soap:binding style='rpc' transport='http://schemas.xmlsoap.org/soap/http'/>
  45:         <operation name='getValue'>
  46:             <soap:operation soapAction='getValue' style='rpc'/>
  47:             <input name='getValue0SoapIn'>
  48:                 <soap:body use='encoded' namespace='http://tempuri.org/Counter'
  49:                     encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
  50:             </input>
  51:             <output name='getValue0SoapOut'>
  52:                 <soap:body use='encoded' namespace='http://tempuri.org/Counter'
  53:                     encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
  54:             </output>
  55:         </operation>
  56:         <operation name='reset'>
  57:             <soap:operation soapAction='reset' style='rpc'/>
  58:             <input name='reset1SoapIn'>
  59:                 <soap:body use='encoded' namespace='http://tempuri.org/Counter'
  60:                     encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
  61:             </input>
  62:             <output name='reset1SoapOut'>
  63:                 <soap:body use='encoded' namespace='http://tempuri.org/Counter'
  64:                     encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
  65:             </output>
  66:         </operation>
  67:         <operation name='up'>
  68:             <soap:operation soapAction='up' style='rpc'/>
  69:             <input name='up2SoapIn'>
  70:                 <soap:body use='encoded' namespace='http://tempuri.org/Counter'
  71:                     encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
  72:             </input>
  73:             <output name='up2SoapOut'>
  74:                 <soap:body use='encoded' namespace='http://tempuri.org/Counter'
  75:                     encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
  76:             </output>
  77:         </operation>
  78:     </binding>
  79: 
  80:     <service name='Counter'>
  81:         <documentation>
  82:         // ICounter.java//</documentation>
  83:         <port name='CounterSoap' binding='tns:CounterSoap'>
  84:             <soap:address location='http://127.0.0.1:4031/soap/Counter/c1'/>
  85:         </port>
  86:     </service>
  87: </definitions>

◆Glue

Glue は、The Mind Electric社(リンク切れ) により開発された XML Web Services開発のための開発キット。

HTTP サーバを内蔵しているので、手軽に試すのに適している。 WSDL を自動生成する機能がある。

The Mind Electric社は、2003年にwebMethods社により買収された。 webMethods社は、2007年に Software AG社によって買収された。

◆その他のフレームワーク/Other frameworks

◆Representational State Transfer(REST)

■Java によるローカルのカウンタ。Local counter in Java

Java でローカルで動作するカウンタのプログラムを作る。 後に、XML Web Service として動作するように書き直す。 First, we write a local program of counters in Java. Next, we rewrite the program to the XML Web service.

◆ローカル/インタフェース。 The interface of the local program

[ICounter.java]
   1: //
   2: // ICounter.java
   3: //
   4: 
   5: public interface ICounter
   6: {
   7:     void up();
   8:     int getValue();
   9:     void reset(int newVal);
  10: };
単に利用するだけならば、インタフェースを定義しなくてもよいが、リモート との比較のためにあえて定義する。カウンタは、3つの手続きを持つものであ る。

◆ローカル/オブジェクト。 Local object class

[Counter.java]
   1: //
   2: // Counter.java
   3: //
   4: public class Counter implements ICounter
   5: {
   6:     int val;
   7:     public Counter(int initVal)
   8:     {
   9:         val = initVal ;
  10:     }
  11:     public void up()
  12:     {
  13:         val ++ ;
  14:     }
  15:     public int getValue()
  16:     {
  17:         return( val );
  18:     }
  19:     public void reset(int newVal)
  20:     {
  21:         val = newVal ;
  22:     }
  23: };
3つの手続きとコンストラクタを実現している。

◆ローカル/main プログラム。 Local main program

[CounterLocal.java]
//
// CounterLocal.java
//

class CounterLocal
{
    public static void main(String argv[])
    {
	ICounter c1 = new Counter( 10 );
	for( int i=0 ; i<3 ; i++ )
	{
	    c1.up();
	    System.out.println("c1.value=="+c1.getValue());
	}
    }
};
実行例:
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/ICounter.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/Counter.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/CounterLocal.java [←]
% make CounterLocal [←]
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar ICounter.java
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar Counter.java
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterLocal.java
% make run-CounterLocal [←]
java -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterLocal
c1.value==11
c1.value==12
c1.value==13
% make run-CounterLocal [←]
java -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterLocal
c1.value==11
c1.value==12
c1.value==13
% []

オブジェクト c1 を、main で作って実行している。実行する度に新しいオブ ジェクトが new される。

■XML Web serviceによるカウンタ。Counter in XML Web Service

ローカルで動作するプログラムを、XML Web Servicesとして 動作するように書き 直す。

◆サーバ Server

[CounterServer.java]
   1: 
   2: import electric.registry.Registry;
   3: import electric.server.http.HTTP;
   4: 
   5: public class CounterServer
   6: {
   7:     public static void main( String[] args ) throws Exception
   8:     {
   9:         if( args.length != 2 )
  10:         {
  11:             System.err.println("Usge: %% java CounterServer portno name");
  12:             System.exit( 1 );
  13:         }
  14:         String portno = args[0];
  15:         String name = args[1];
  16:         String url = "http://localhost:" + portno + "/soap" ;
  17:         HTTP.startup( url );
  18: 
  19:         Registry.publish( name, new Counter(10) );
  20:         System.out.println("Ok.");
  21:     }
  22: }
このプログラムは、2つの引数を取る。 ポート番号としては、以下の Makefile では、8080 が指定されている。他の 人とぶつからないように、クライアント側、サーバ側とも書き換えることが望 ましい。

HTTP.Startup() で、Glue に含まれている XML Web Servicesのサーバを起動して いる。その結、果新しいスレッドが作られるので、このプログラムは、main() が終了しても、動き続けることになる。

Registry.publish() は、Glue に含まれている機能である。 これは、与えられた名前で、オブジェクトをアクセス可能にする。

◆クライアント Client

[CounterClient.java]
   1: 
   2: import electric.registry.Registry;
   3: 
   4: public class CounterClient
   5: {
   6:     public static void main( String[] args ) throws Exception
   7:     {
   8:         if( args.length != 3 )
   9:         {
  10:             System.err.println("Usge: %% java CounterClient hostname portno name");
  11:             System.exit( 1 );
  12:         }
  13:         String hostname = args[0];
  14:         String portno = args[1];
  15:         String name = args[2];
  16:         String url = "http://" + hostname + ":" + portno + "/soap/" + name + ".wsdl";
  17:         System.out.println("url is " + url );
  18:         ICounter c1 = (ICounter) Registry.bind( url, ICounter.class );
  19:         for( int i=0 ; i<3 ; i++ )
  20:         {
  21:             c1.up();
  22:             System.out.println("c1.value=="+c1.getValue());
  23:         }
  24:     }
  25: }
このプログラムは、3つの引数を取る。
hostname
サーバが動いているホストの名前。同じホストで動いている 時には、localhost を与える。
portno
ポート番号。サーバを実行した時と同じもの。
name
Counter オブジェクトの名前。サーバで指定したものと同じもの。
Registry.bind() により、クライアント側のスタブ(プロキシ)が自動的に生 成され、インタフェース ICounter のオブジェクトが返される。 このオブジェクトは、class Counter のものではない。このオブジェクトにア クセスすると、RPC が行われる。このプログラムは、3回ループを回って、 up() という手続きと getValue() という手続きを呼んでいる。合計、6 回の RPC が行われる。

◆コンパイルと実行(サーバ側) Compilation and execution of the server

実行する時には、サーバ側とクライアント側でそれぞれ1つウインドウを開く。

サーバは、は自動的に終了しないので、実験が終わったら ^C (Control-C)で 殺す。

% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/ICounter.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/Counter.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/CounterServer.java [←]
% emacs Makefile (環境に合わせて Makefile の修正) [←]
% make CounterServer [←]
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar ICounter.java
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar Counter.java
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterServer.java
% make run-CounterServer [←]
java -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterServer 8080 Counter/c1
GLUE 1.2 (c) 2001 The Mind Electric
startup server on http://127.0.0.1:8080/soap
Ok.
(最後に ^C で止める)

◆コンパイルと実行(クライアント側) Compilation and execution of the client

% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/ICounter.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/CounterClient.java [←]
% emacs Makefile (環境に合わせて Makefile の修正) [←]
% make CounterClient [←]
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar ICounter.java
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterClient.java
% make run-CounterClient [←]
java -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterClient localhost 8080 Counter/c1
url is http://localhost:8080/soap/Counter/c1.wsdl
c1.value==11
c1.value==12
c1.value==13
% make run-CounterClient [←]
java -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterClient localhost 8080 Counter/c1
url is http://localhost:8080/soap/Counter/c1.wsdl
c1.value==14
c1.value==15
c1.value==16
% []
クライアントのプログラムを2回実行しても、同じカウンタが使われている所 に注意する。

実際に XML Web Servicesを利用する場合には、いくつかの方法が選べる。

◆サーバ側のエラー a server error

% make run-CounterServer [←]
java -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterServer 8080 Counter/c1
Exception in thread "main" java.net.BindException: Address already in use
        at java.net.PlainSocketImpl.socketBind(Native Method)
        at java.net.PlainSocketImpl.bind(PlainSocketImpl.java:331)
        at java.net.ServerSocket.bind(ServerSocket.java:309)
        at java.net.ServerSocket.(ServerSocket.java:183)
        at java.net.ServerSocket.(ServerSocket.java:139)
        at javax.net.DefaultServerSocketFactory.createServerSocket(DashoA6275)
        at electric.net.socket.tcp.TCPSocketFactory.createServerSocket(Unknown Source)
        at electric.net.socket.SocketServer.createServerSocket(Unknown Source)
        at electric.net.socket.SocketServer.startup(Unknown Source)
        at electric.net.http.WebServer.startup(Unknown Source)
        at electric.net.http.WebServer.startWebServer(Unknown Source)
        at electric.server.http.HTTP.startup(Unknown Source)
        at electric.server.http.HTTP.startup(Unknown Source)
        at electric.server.http.HTTP.startup(Unknown Source)
        at CounterServer.main(CounterServer.java:17)
make: *** [run-CounterServer] Error 1
% []

同じポート番号が使われていた時、Address already in use というエラーが 出る。その時には、まず、自分のプログラム(の残がい)がどこかで動いてい ないかを調べる。他の同じ演習をしている人のプログラムの残がいを見つけた 時には、残がいだったら殺してもらう。本当に別のプログラムに使われていた 時には、別のコンピュータに移動するか、以下のように別のポート番号を指定 して走らせる。

% make run-CounterServer port=1231 [←]
java -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterServer 1231 Counter/c1
GLUE 1.2 (c) 2001 The Mind Electric
startup server on http://127.0.0.1:1231/soap
Ok.
(最後に ^C で止める)

◆WSDLファイルの取得 Getting the WSDL file

Glue では、オブジェクトをRegistry.publish() すると、WSDL が自動的に作 られ、HTTP でアクセス可能になる。Web ブラウザで読むこともできる。下の 例では、wget コマンドで取得している。
% telnet 127.0.0.1 8080 [←]
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
GET /soap/Counter/c1.wsdl HTTP/1.0[←]
[←]
HTTP/1.1 200 OK
Content-Type: text/xml
Server: GLUE/1.2
Content-Length: 2959

<?xml version='1.0' encoding='UTF-8'?>
<!--generated by GLUE-->
<definitions name='Counter' ... > ...
</definitions>
Connection closed by foreign host.
% []
得られた WSDLの例

◆アクセス制御 Access control

RPC では、クライアント、サーバ、相互にアクセス制御を行う必要がある。

XML Web Servicesでは、World Wide Web の技術を使ってアクセス制御を行うことができる。

Java で記述された Glue では、これに加えて、Java のセキュリティ機能も使 える。

■Java RMI (Remote Method Invocation)

Java RMI (Remote Method Invocation) は、Java 言語で書かれたプログラム間 のORB (Object Request Broker). ORB は、オブジェクト指向が入っている RPC (Remote Procedure Call).

別の Java 仮想計算機間オブジェクトのメソッドを呼び出す仕組み。 RMI は、いくつかの層を見えなくする。

◆HORB

RMI が入ったのは、JDK 1.1 から。それ以前に、電総研(現在の産電総研)の 平野氏によるHORB が開発された。

比較的軽い。組込みシステムで使われているような遅めのコンピュータに向い ている。

◆RMI におけるサーバとクライアント Servers and clients in Java

サーバもクライアントも Java で書かれたオブジェクト。 Both RMI clients and servers are objects in Java. サーバは、中身が空のインタフェース Remote を implements する。 An RMI server object must inherit a special empty interface, Remote.
src/java/rmi/Remote.java
public interface Remote {}
これを見つけると、コンパイラが特殊なコードを生成する。
Stub
クライアント側のスタブ(RPC用語)。サーバのオブジェクトと同じインタフェース を持つ。受け付けたメソッド呼出しを通信に変換する。 Client-side stub in terms of RPC.
Skelton
サーバ側のスタブ(RPC用語)。 Server-side stub in terms of RPC.

◆java.rmi.server.UnicastRemoteObject

サーバ側で継承すべきクラス。
java.lang.Object (class)
  |
  +--java.rmi.server.RemoteObject (class)
        |
        +--java.rmi.server.RemoteServer (class)
              |
              +--java.rmi.server.UnicastRemoteObject (class)
これに加えて、interface Remote を implements する。

クライアント側は、これに比べて簡単。違いは、サーバに接続する部分部分と、 分散固有の例外を受ける部分。

◆rmiregistry

Java RMI では、メッセージの転送には、TCP/IP が使われる。 Java RMI では、 SunRPCの portmap 相当のプログラム rmiregistry が必要になる。 動いていなければ、自分で実行する。

rmiregistry は、デフォルトでポート番号 1099 を使う。 複数人が1つのホストを使うとぶつかる。

■ローカルのカウンタ Local counter in Java

ローカルで動作するプログラムを、RMI で動作するように書き直す。 Java でローカルで動作するカウンタのプログラムを作る。 後に、XML Web Service として動作するように書き直す。 First, we write a local program of counters in Java. Next, we rewrite the program to the Java RMI program.

■RMIによるカウンタ Counter in RMI

◆リモート・インタフェース Remote interface of Counter

RMI でアクセス可能にするオブジェクトには、必ずインタフェースを定義する。

[IRCounter.java]

   1: //
   2: // IRCounter.java
   3: //
   4: 
   5: public interface IRCounter extends java.rmi.Remote
   6: {
   7:     public void up() throws java.rmi.RemoteException;
   8:     public int  getValue() throws java.rmi.RemoteException;
   9:     public void reset(int newVal) throws java.rmi.RemoteException;
  10: }
リモート・インタフェースの特徴 characteristic of remote interfaces:

◆リモート・オブジェクト The remote object of counters

[RCounter.java]
   1: //
   2: // RCounter.java
   3: //
   4: 
   5: import java.rmi.*;
   6: import java.rmi.server.*;
   7: 
   8: public class RCounter extends java.rmi.server.UnicastRemoteObject
   9:     implements IRCounter
  10: {
  11:     int val;
  12:     public RCounter(int initVal) throws RemoteException
  13:     {
  14:         super();
  15:         val = initVal ;
  16:     }
  17:     public void up() throws java.rmi.RemoteException
  18:     {
  19:         val ++ ;
  20:     }
  21:     public int getValue() throws java.rmi.RemoteException
  22:     {
  23:         return( val );
  24:     }
  25:     public void reset(int newVal) throws java.rmi.RemoteException
  26:     {
  27:         val = newVal ;
  28:     }
  29: };
  30: 
java.rmi.server.UnicastRemoteObject の代りに java.rmi.activation.Activatable を使うと呼び出された時に起動される。

オブジェクトは、serialize (直列化) されてコピーで渡される。

◆サーバのmainメソッド the main method of the servers

[RCounterServer.java]
   1: //
   2: // RCounterServer.java
   3: //
   4: 
   5: import java.rmi.registry.Registry;
   6: import java.rmi.registry.LocateRegistry;
   7: import java.rmi.RemoteException;
   8: import java.rmi.server.UnicastRemoteObject;
   9: 
  10: public class RCounterServer
  11: {
  12:     public static void main(String argv[]) throws Exception
  13:     {
  14:         if( argv.length != 1 )
  15:         {
  16:             System.err.println("Usage% java RCounterServer rmiregistry-portno");
  17:             System.exit( 1 );
  18:         }
  19:         String rmiregport_s = argv[0];
  20:         int rmiregport = Integer.parseInt( rmiregport_s );
  21:         Registry registry = LocateRegistry.getRegistry( rmiregport );
  22: 
  23:         IRCounter c1 = new RCounter( 10 );
  24:         String name = "/Counter/c1" ;
  25:         registry.rebind( name, c1 );
  26:     }
  27: };
Exception を catch したら、最後に System.exit( 1 ) した方がよい。

◆クライアント a client program

[RCounterClient.java]
   1: //
   2: // RCounterClient.java
   3: //
   4: 
   5: import java.rmi.registry.LocateRegistry;
   6: import java.rmi.registry.Registry;
   7: 
   8: class RCounterClient
   9: {
  10:     public static void main(String argv[]) throws Exception
  11:     {
  12:         if( argv.length != 2 )
  13:         {
  14:             System.err.println("Usage% java RCounterClient hostname rmiregistry-portno");
  15:             System.exit( 1 );
  16:         }
  17:         String hostname = argv[0];
  18:         String rmiregport_s = argv[1];
  19:         int rmiregport = Integer.parseInt( rmiregport_s );
  20:         Registry registry = LocateRegistry.getRegistry( hostname, rmiregport );
  21: 
  22:         String name = "/Counter/c1";
  23:         IRCounter c1 = (IRCounter) registry.lookup( name );
  24:         for( int i=0 ; i<3 ; i++ )
  25:         {
  26:             c1.up();
  27:             System.out.println("c1.value=="+c1.getValue());
  28:         }
  29:     }
  30: };

◆コンパイルと実行(サーバ側) Compilation and execution of the server

サーバ側では、ウインドウを2枚開く。

一方のウインドウで rmiregistry を起動する。ポート番号は、ぶつからない ように uid を使う。rmiregistry は自動的に終了しないので、実験が終わっ たら ^C (Control-C)で殺す。

% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/IRCounter.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/RCounter.java [←]
% make run-rmiregistry [←]
rmiregistry 8080
(最後に ^C で止める)
一方のウインドウでサーバを動作させる。サーバもは自動的に終了しないので、 実験が終わったら ^C (Control-C)で殺す。
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/IRCounter.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/RCounter.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/RCounterServer.java [←]
% make RCounterServer [←]
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar IRCounter.java
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar RCounter.java
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar RCounterServer.java
rmic RCounter
% make run-RCounterServer [←]
java -Djava.rmi.server.hostname=127.0.0.1 RCounterServer 8080
(最後に ^C で止める)

Java RMI ConnectException: Connection refused to host: 127.0.0.1 エラーの正しい理解と対策 参照。

◆コンパイルと実行(クライアント側) Compilation and execution of the client

クライアント側では、ウインドウを1枚開く。
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/IRCounter.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/RCounterClient.java [←]
% make RCounterClient [←]
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar IRCounter.java
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar RCounterClient.java
rmic RCounter
% make run-RCounterClient [←]
java RCounterClient localhost 8080
c1.value==11
c1.value==12
c1.value==13
% make run-RCounterClient [←]
java RCounterClient localhost 8080
c1.value==14
c1.value==15
c1.value==16
% []

◆Java RMI と直列化(serialize)

marshaling/unmarshaling のことを、Java では serialization/deserialization という。日本語では、直列化、または、整列 化。

RMI でオブジェクトを渡す時には、interface Serializable を implements する。

serialize 不要のフィールドには、transient をつける。

serialization は、RMI だけでなく、オブジェクトをファイルに落とす時にも 使える。

■Ruby言語によるローカルのカウンタ Local counter in Ruby

ローカルで動作するJava版のプログラム Ruby言語で書き直す。

◆ローカル/インタフェース Local interface in Ruby

Ruby (dRuby) には、インタフェースは存在しない。 Ruby の変数には静的な型付けがなされない。 メソッドの名前が一致すれば呼べる。

◆ローカル/オブジェクト Local counter object

[counter.rb]
   1: #
   2: #       counter.rb
   3: #
   4: 
   5: class Counter
   6:       def initialize(initVal)
   7:           @val = initVal
   8:       end
   9: 
  10:       def up()
  11:           @val += 1
  12:       end
  13: 
  14:       def getValue()
  15:           return @val
  16:       end
  17: 
  18:       def reset(newVal)
  19:           @val = newVal
  20:       end
  21: end
3つの手続きとコンストラクタからなる。

◆ローカル/利用 local main method

[counter-local.rb]
   1: #
   2: #       counter-local.rb
   3: #
   4: 
   5: require 'counter.rb'
   6: 
   7: def main(argv)
   8:         c1 = Counter.new( 10 )
   9:         0.upto(3-1) {|i|
  10:             c1.up()
  11:             printf("c1.value==%d\n",c1.getValue())
  12:         }
  13: end
  14: 
  15: main(ARGV)
Java に合わせるために、main() を作成している。

実行例:

% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/counter.rb [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/counter-local.rb [←]
% make run-counter-local.rb [←]
ruby counter-local.rb
c1.value==11
c1.value==12
c1.value==13
% make run-counter-local.rb [←]
ruby counter-local.rb
c1.value==11
c1.value==12
c1.value==13
% []

オブジェクト c1 を、main で作って実行している。実行する度に新しいオブ ジェクトが new される。

■dRubyによるカウンタ Counter in dRuby

dRuby は、Ruby 言語用の RPC 。 ローカルで動作するプログラムを、dRuby でアクセス可能なオブジェクトとし て動作するように書き直す。

◆サーバ Counter Server in dRuby

[counter-server.rb]
   1: #
   2: #       counter-server.rb
   3: #
   4: 
   5: require 'counter.rb'
   6: require 'drb/drb'
   7: 
   8: def main(argv)
   9:         if( argv.length != 1 )
  10:             $stderr.printf("Usage: %% ruby $0 port\n")
  11:             exit( 1 )
  12:         end
  13:         portno = argv[0]
  14:         url = "druby://localhost:" + portno
  15:         c1 = Counter.new( 10 )
  16:         DRb.start_service( url, c1 )
  17:         printf("startup server on %s\n", url)
  18:         printf("Ok.\n")
  19:         sleep()
  20: end
  21: 
  22: main(ARGV)
このプログラムは、1つの引数を取る。 This program takes one command line argument. Java 版とは違って、「カウンタ・オブジェクトの名前」がとれない。 ポート番号としては、以下の Makefile では、8080 が指定されている。他の 人とぶつからないように、クライアント側、サーバ側とも書き換えることが望 ましい。

DRb.start_service() で、dRuby のサーバを起動している。その結、果新しい スレッドが作られる。このプログラムは、main() が終了してしまうと全体が終 了してしまうので、sleep() を呼んでmain() が終了しないようにしている。

◆クライアント Counter client in dRuby

[counter-client.rb]
   1: #
   2: #       counter-client.rb
   3: #
   4: 
   5: require 'drb/drb'
   6: 
   7: def main(argv)
   8:         if( argv.length != 2 )
   9:             $stderr.printf("Usage: %% ruby $0 hostname port\n")
  10:             exit( 1 )
  11:         end
  12:         hostname = argv[0]
  13:         portno   = argv[1]
  14:         url = "druby://" + hostname + ":" + portno
  15:         printf("url is %s\n",url)
  16:         c1 = DRbObject.new_with_uri(url)
  17:         0.upto(3-1) {|i|
  18:             c1.up()
  19:             printf("c1.value==%d\n",c1.getValue())
  20:         }
  21: end
  22: 
  23: main(ARGV)
このプログラムは、2つの引数を取る。 This program takes two command line arguments.
hostname
サーバが動いているホストの名前。同じホストで動いている 時には、localhost を与える。
port number.
ポート番号。サーバを実行した時と同じもの。
DRbObject.new_with_uri() により、クライアント側のスタブ(プロキシ)が自 動的に生成される。 このオブジェクトは、class Counter のものではない。このオブジェクトにア クセスすると、RPC が行われる。このプログラムは、3回ループを回って、 up() という手続きと getValue() という手続きを呼んでいる。合計、6 回の RPC が行われる。

◆実行(サーバ側) Execution of the server

実行する時には、サーバ側とクライアント側でそれぞれ1つウインドウを開く。

サーバは、は自動的に終了しないので、実験が終わったら ^C (Control-C)で 殺す。

% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/counter.rb [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/counter-server.rb [←]
% make run-counter-server.rb [←]
ruby  -I. counter-server.rb 8080
startup server on druby://localhost:8080
Ok.
(最後に ^C で止める)

◆実行(クライアント側) Execution of the client

% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/counter-client.rb [←]
% make run-counter-client.rb [←]
ruby  -I. counter-client.rb localhost 8080
url is druby://localhost:8080
c1.value==11
c1.value==12
c1.value==13
% make run-counter-client.rb [←]
ruby  -I. counter-client.rb localhost 8080
url is druby://localhost:8080
c1.value==14
c1.value==15
c1.value==16
% []
クライアントのプログラムを2回実行しても、同じカウンタが使われている所 に注意する。

■MessagePack-RPC

MessagePack は、古橋貞之氏(Sadayuki Furuhashi)による marshaling/unmarshaling library。 MessagePack-RPC は、MessagePack を使ってデータをやりとりするRPC のライブラリ。クライア ント側でも非同期的なインタフェースがある。

◆サーバ Counter Server/MessagePack-RPC Ruby

[counter-server-msgpack.rb]
   1: #
   2: #       counter-server-msgpack.rb
   3: #
   4: 
   5: require 'counter.rb'
   6: require 'msgpack/rpc'  #  gem install msgpack-rpc
   7: 
   8: def main(argv)
   9:         if( argv.length != 1 )
  10:             $stderr.printf("Usage: %% ruby $0 port\n")
  11:             exit( 1 )
  12:         end
  13:         portno = argv[0]
  14:         svr = MessagePack::RPC::Server.new()
  15:         c1 = Counter.new( 10 )
  16: #       svr.listen("0.0.0.0", portno, c1 )
  17:         svr.listen("::", portno, c1 )
  18:         printf("startup server at port number %s\n", portno )
  19:         svr.run()
  20: end
  21: 
  22: main(ARGV)
このプログラムは、1つの引数を取る。 This program takes one command line argument. Java 版とは違って、「カウンタ・オブジェクトの名前」がとれない。 ポート番号としては、以下の Makefile では、8080 が指定されている。他の 人とぶつからないように、クライアント側、サーバ側とも書き換えることが望 ましい。

MessagePack::RPC::Server.new(), .run() で、 msgpack のサーバを起動している。その結、果新しい スレッドが作られる。

◆クライアント Counter client/MessagePack-RPC Ruby

[counter-client-msgpack.rb]
   1: #
   2: #       counter-client-msgpack.rb
   3: #
   4: 
   5: require 'msgpack/rpc'
   6: 
   7: def main(argv)
   8:         if( argv.length != 2 )
   9:             $stderr.printf("Usage: %% ruby $0 hostname port\n")
  10:             exit( 1 )
  11:         end
  12:         hostname = argv[0]
  13:         portno   = argv[1]
  14:         cli = MessagePack::RPC::Client.new(hostname, portno)
  15:         0.upto(3-1) {|i|
  16:             cli.call(:up)
  17:             printf("c1.value==%d\n",cli.call(:getValue))
  18:         }
  19: end
  20: 
  21: main(ARGV)
このプログラムは、2つの引数を取る。 This program takes two command line arguments.
hostname
サーバが動いているホストの名前。同じホストで動いている 時には、localhost を与える。
port number.
ポート番号。サーバを実行した時と同じもの。
MessagePack::RPC::Client.new()により、クライアント側の オブジェクトが生成される。 ただし、スタブは生成されないので、 c1.up() のように、ローカルの手続き呼出しと同じ形式では RPC を実行できないる その代わりに、cli.call(:up) のような形式で、RPCを実行する。

このプログラムは、3回ループを回って、up() という手続きと getValue() と いう手続きを呼んでいる。合計、6 回のRPC が行われる。

◆実行(サーバ側) Execution of the server/MessagePack-RPC Ruby

実行する前に、次の gem をインストールする。

# gem install msgpack-rpc [←]

実行する時には、サーバ側とクライアント側でそれぞれ1つウインドウを開く。

サーバは、は自動的に終了しないので、実験が終わったら ^C (Control-C)で 殺す。

% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/counter.rb [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/counter-server-msgpack.rb [←]
% make run-counter-server-msgpack.rb [←]
ruby  -I. counter-server-msgpack.rb 8080
startup server at port number 8080
Ok.
(最後に ^C で止める)

◆実行(クライアント側) Execution of the client/MessagePack-RPC Ruby

% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/counter-client-msgpack.rb [←]
% make run-counter-client-msgpack.rb [←]
ruby  -I. counter-client-msgpack.rb 127.0.0.1 8080
c1.value==11
c1.value==12
c1.value==13
% make run-counter-client-msgpack.rb [←]
ruby  -I. counter-client-msgpack.rb 127.0.0.1 8080
c1.value==14
c1.value==15
c1.value==16
$ []
クライアントのプログラムを2回実行しても、同じカウンタが使われている所 に注意する。 (localhost ではうまく動かないことがある。この例では、127.0.0.1 を使っている。)

■PythonでのRPC

Pythonは、標準で XML-RPC をサポートしている。

◆ローカル/オブジェクト Local counter object

ローカルで動作するRuby版のプログラム Python言語で書き直す。 [counter.py]
   1: #
   2: #       counter.py
   3: #
   4: 
   5: class Counter(object):
   6:     def __init__(self, initVal):
   7:         self.val = initVal
   8: 
   9:     def up(self):
  10:         self.val += 1
  11: 
  12:     def getValue(self):
  13:         return self.val
  14: 
  15:     def reset(self, newVal):
  16:         self.val = newVal
  17: 
3つの手続きとコンストラクタからなる。

◆ローカル/利用 local main method

[counter-local.py]
   1: #
   2: #       counter.py
   3: #
   4: 
   5: import sys
   6: import counter
   7: 
   8: def main(argv):
   9:     c1 = counter.Counter( 10 )
  10:     for i in range(3):
  11:         c1.up()
  12:         print(f'c1.value == {c1.getValue()}')
  13: 
  14: main(sys.argv)
Java, Ruby に合わせるために、main() を作成している。

python3 コマンドが見付からないと言われたら Makefile にある次の行をコメントアウトする。

#PYTHON = python

実行例:

% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/counter.py [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/counter-local.py [←]
% make run-counter-local.py [←]
python3 counter-local.py
c1.value==11
c1.value==12
c1.value==13
% make run-counter-local.py [←]
python3 counter-local.py
c1.value==11
c1.value==12
c1.value==13
% []

オブジェクト c1 を、main で作って実行している。実行する度に新しいオブ ジェクトが new される。

■Python XMLRPC によるカウンタ Counter in Pyton with XMLRPC

◆サーバ Counter Server in Python

ローカルで動作するプログラムを、XML RPC でアクセス可能なオブジェクトと して動作するように書き直す。 [counter-server.py]
   1: #
   2: #       counter-server.py
   3: #
   4: 
   5: import xmlrpc.server
   6: import sys
   7: import counter  # ./counter.py
   8: 
   9: def main(argv):
  10:     if len(argv) != 2:
  11:         print(f'Usage: % python {argv[0]} port', file=sys.stderr)
  12:         sys.exit(1)
  13:     portno = argv[1]
  14:     hostname = 'localhost'
  15:     c1 = counter.Counter(10)
  16:     server = xmlrpc.server.SimpleXMLRPCServer((hostname, int(portno)), 
  17:                                               allow_none=True)
  18:     server.register_instance(c1)
  19:     url = f'http://{hostname}:{portno}'
  20:     print(f'startup server on {url}')
  21:     server.serve_forever()
  22: 
  23: main(sys.argv)
このプログラムは、1つの引数を取る。 This program takes one command line argument. Java 版とは違って、「カウンタ・オブジェクトの名前」がとれない。 ポート番号としては、以下の Makefile では、8080 が指定されている。他の 人とぶつからないように、クライアント側、サーバ側とも書き換えることが望 ましい。

xmlrpc.server.SimpleXMLRPCServer() で、XML RPC のサーバを起動している。その結、果新しい スレッドが作られる。 register_instance() で、カウンタ・オブジェクトを登録している。 このプログラムは、main() が終了してしまうと全体が終 了してしまうので、server.serve_forever() を呼んでmain() が終了しないようにしている。

◆クライアント Counter client in Python

[counter-client.py]
   1: #
   2: #       counter-client.py
   3: #
   4: 
   5: import xmlrpc.client
   6: import sys
   7: 
   8: def main(argv):
   9:     if len(argv) != 3:
  10:         print(f'Usage: % python {argv[0]} hostname port', file=sys.stderr)
  11:         sys.exit(1)
  12:     hostname = argv[1]
  13:     portno   = argv[2]
  14:     url = f'http://{hostname}:{portno}'
  15:     c1 = xmlrpc.client.ServerProxy(url)
  16:     for i in range(3):
  17:         c1.up()
  18:         print(f'c1.value == {c1.getValue()}')
  19: 
  20: main(sys.argv)
このプログラムは、2つの引数を取る。 This program takes two command line arguments.
hostname
サーバが動いているホストの名前。同じホストで動いている 時には、localhost を与える。
port number.
ポート番号。サーバを実行した時と同じもの。
xmlrpc.client.ServerProxy() により、クライアント側のスタブ(プロキシ)が自 動的に生成される。 このオブジェクトは、class Counter のものではない。このオブジェクトにア クセスすると、RPC が行われる。このプログラムは、3回ループを回って、 up() という手続きと getValue() という手続きを呼んでいる。合計、6 回の RPC が行われる。

◆実行(サーバ側) Execution of the server

実行する時には、サーバ側とクライアント側でそれぞれ1つウインドウを開く。

サーバは、は自動的に終了しないので、実験が終わったら ^C (Control-C)で 殺す。

% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/counter.py [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/counter-server.py [←]
$ make run-counter-server.py [←]
python3 counter-server.py 8080
startup server on http://localhost:8080
(最後に ^C で止める)

◆実行(クライアント側) Execution of the client

% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2023/2023-05-19/ex/counter-client.py [←]
$ make run-counter-client.py [←]
python3 counter-client.py localhost 8080
c1.value == 11
c1.value == 12
c1.value == 13
$ make run-counter-client.py [←]
python3 counter-client.py localhost 8080
c1.value == 14
c1.value == 15
c1.value == 16
$ []
クライアントのプログラムを2回実行しても、同じカウンタが使われている所 に注意する。

■その他のRPC

◆JSON-RPC

JSON (JavaScript Object Notation) は、 JavaScript 言語の一部を利用した マーシャリング形式。 JSON は、様々なプログラミング言語に対応している。

JSON-RPC は、JSON を使ってデータをやりとりするRPC のライブラリ。

◆gRPC

gRPC は、Google が開発した RPC。 マーシャリング形式としては、 Protocol Buffers を利用している。 様々なプログラミング言語に対応している。

■練習問題(exercise)5 遠隔手続き呼び出し/Remtote Procedure Call

★問題(501) カウンタへの手続き追加/Adding a procedure to the counter

この Web ページでは、C言語、Java 言語、Ruby 言語、および、Python 言語によりRPC でアクセス可能なカウンタの実装を示した。 これらの中から1つ選び、次のような手続きを追加しなさい。 This Web page shows the implementations of counters that can be accessed with RPC in C, Java, Ruby, and Python. Choose one implementation of the counter and add the following procedure. 注意: 必ず 2 つのプログラム(RPC のクライアントと RPC サーバ)を書くように。 RPC を行わないローカルのプログラムは認めない。 Notice: You must write two programs, an RPC client and and RPC server. We do not accept a local program that does not perform RPC.

注意: 自動生成されたプログラム、たとえば、SunRPC のmain 関数を提出しな いように。

Notice: You should not submit automatically generated programs, such as the main function of a SunRPC server.

★問題(502) コンテナ/A container

次の何れかのオブジェクトを、RPC でアクセスできるようにしなさい。 Choose one container object in the following list, and make it accessible with RPC. 要素は、整数に限定してもよい。サーバのプログラムは、1つだけつくりなさ い。クライアントとしては、要素を追加するものと要素を取出すものの2つを 作りなさい。

You can deal with only integer elements. You must write a single server program and two client programs. One client program adds an element to the container, and the other client program removes the element from the container.

★問題(503) 複数カウンタを保持するサーバ/A server hosting multiple counters

この Web ページでは、C言語、Java 言語、Ruby 言語、および、Python 言語によりRPC でアクセス可能なカウンタの実装を示した。 これらのプログラムでは、サーバはカウンタを1つしか保持しない。 これらのいずれかを修正して、1つのサーバで複数のカウンタを保持できるようにしなさい。 This Web page shows the implementations of counters that can be accessed with RPC in C, Java, Ruby, and Python. In these programs, the server hosts a single counter. Choose one implementation of the counter and rewrite the program that can host multiple counters in a server.

ヒント: サーバにカウンタを生成する手続きを追加し、遠隔からアクセス可能にする。 個々の手続きには、カウンタのIDを含める。 Hints: Add a procedure that creates a counter, and other procures take the ID of a counter.

SunRPC, dRuby の場合は、名前が付けられないので面倒かもしれない。 Since SunRPC and dRuby do not allow naming objects, the program can be complex.

★問題(504) RPCプログラミング/RPC programming

次の練習問題の中で、1つを選び、回答しなさい。 Choose one question and answer it from the following questions. 今日の課題では、プログラミング言語としては、C言語、Java、Ruby、Python、Go、Rust、または、Kotolin を用いなさい。 また、RPC としては、この Web ページにあるものを使いなさい。

You can use the C, Java, Ruby, Python, Go, Rust, or Kotlin language. You should use an RPC mechanism in this Web page.

レポートには、次のものを含めなさい。 Your report must include following.

2023/05/22Glue (XML Web Services) のプログラムは、 次のホストで動きます。 The programs of Glue (XML Web Services) can run in the following hosts.

★問題(505) RPCにおける障害の観察/Experiences with RPC failures

問題(504) RPCプログラミング で作成したプログラムで、 RPCにおける障害を、1つ選び、起してみなさい。 その時のプログラム挙動を報告しなさい。

In the the program you wrote for Exercise (504) RPC programming, choose one of failures in RPC and cause the failure. Report the behavior of the programs.


Last updated: 2023/05/22 16:41:40
Yasushi Shinjo / <yas@cs.tsukuba.ac.jp>