プロセス間通信、マーシャリング、クライアントサーバモデル、遠隔手続き呼び出し / Interprocess communication, marshaling, client-server model, and Remote Procedure Call

並行システム

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

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

■今日の重要な話

■プロセス間通信/ Interprocess communication

2つの重要なパタン two important patterns プロセス間通信の構造化(structuring of interprocess communication)。 send(), receive() は、goto 文相当。

◆プロセスとメッセージ/process and messages

分散システム(a distributed system): 離れていても心は1つ sharing one heart between two separate lovers

ネットワークで接続された複数のコンピュータ(networked mulitple computers)上で複数のプロセス(multiple processes)を動作させる。プロセス は、全体として1つの仕事(a single task)を成し遂げる。

ノード(node):プロセス(process)、あるいは、コンピュータ(computer)

メッセージ(message):ノード間で交換されるデータ data exchanged among nodes

分散システムでは、次のような集中システムでは普通に使える共有資源が使えない。 A distributed system cannot use shared resources that are available in a centralized system.

分散共有メモリ(distributed shared memory)、 分散ファイルシステム(distributed file system)が使える 場合は使う方法も検討してもよい。

◆通信プリミティブ/communication primitives

メッセージを送信したり受信したりするライブラリ関数(library functions)や システム・コール(system calls)を通信プリミティブという。 最終的には、ハードウェア。 分散プログラムを記述する時には通信プリミティブを呼び出した段階まで考え ればよい。以後、プログラミング言語の実行時システムやOSが考える。

◆send() and receive()

通信の基本命令 basic operations
send()
メッセージを送信する
receive()
メッセージを受信する。

◆通信プリミティブの分類/classification

References: Andrew S. Tanenbaum and Robbert Van Renesse. Distributed operating systems. ACM Comput. Surv. Vol.17, No.4, pp.419-470 (December 1985). DOI=10.1145/6041.6074 http://doi.acm.org/10.1145/6041.6074

◆信頼性の有無(reliable or non-reliable)

信頼性性があるかないか。専門用語では、程度問題(高い低い)ではなく、有 るか無いか。

信頼性がある通信プリミティブを利用した場合、あるプロセスが送信したメッ セージは、途中で失われることはなく(without message loss)、有限時間以内 (within a finite time)に通信相手のプロセスに送り届けられる。

分散システムでは、メッセージは、失われることがある。 a message can be lost.

信頼性に対する態度 attitude to reliability

◆結合(コネクション)が作られるか結合が作られないか/connecton oriented or connectionless

結合が必要な通信プリミティブ connection oriented
実際に send や receive でメッセージを送受 信する前に結合を確立させる手順を踏む。電話(telephone)。
結合が作られない通信プリミティブ connectionless
送りたいデータをすぐに send することができる。郵便(physical mail)。

プロセスAとプロセスBの間に結合がつくられる

図? 結合が作られる通信プリミティブ/sending and recieving messagess with connection-oriented communication primitives

プロセスAとプロセスBの間に結合が作られない時

図? 結合が作られる通信プリミティブと結合が作られない通信プリミティブ/sending and recieving messagess with connectionless communication primitives

◆接続の確立と通信パタン/connection establishment and communication pattern

接続を作る通信プリミティブでは、send(), receive() で送受信する前に接続 を確立する必要がある。通信の確立では、 電話(teleophone)と似ていて、発信 側(caller)と着信側(callee)に分かれる。

着信側 callee

    make_port(); // 受付端の登録。
    accept();    // 実際の受付。connect() と対応。

    receive();
    send();
    receive();
    send();

    close();     // 接続の切断。
発信側 caller
    connect();   // 接続要求。accept() と対応。

    send();
    receive();
    send();
    receive();

    close();     // 接続の切断。

◆接続を用いない通信/connectionless communication pattern

接続を用いる操作がない。send() と receive() が対応しているだけ。

プロセスA

    receive();
    send();
    receive();
    send();
プロセスB
    send();
    receive();
    send();
    receive();

◆アドレス指定/addressing

直接 direct
プロセスを指定する。targeting a process
間接 indirect
メール・ボックス。 targeting a mailbox. 複数のプロセスによって共有されることもされない こともある。 a mail box can be shard among processes.

ほとんどの通信プリミティブは、間接。 Most communication primitives use indirect addressing.

直接の例: few examples of direct addressing

問題:直接アドレス指定では、receiveする前にsendされると、どのプロセス にメッセージを送っていいのかわからない。

解決

◆同期型/非同期(synchronous/asynchronous)

send() したプロセスはreceive()されるのを待たずにすぐ再開する。

図? 同期型send/synchronous send()

send() したプロセスがreceive()されるまで止まる。

図? 非同期型send/asynchronous send()

send,receive それぞれ2種類ある。

非同期の方が、並行性が高い。しかし、弱点も多い。 asynchronous communication has higher concurrency, but has many drawbacks.

  1. 転送完了までメッセージ・バッファを書き換えられない no modifications to a message buffer.
  2. 送信側は、転送が終了したかわからない A sender cannot know the completion of transmission.
1. は、バッファリング(buffering)で解決可能。しかし、無駄なコピー (copying)、フロー制御(flow control)の問題がある。

2. は、転送完了割り込み(interrupt)で解決可能。プログラミングが難しい。 割り込みよりは、マルチスレッドがよい。

プログラミングスタイルの選択 programming styles

◆バッファリング(buffering)

コピー(copy)するかしないか。

同期、非同期とは直行しているとも言えるが、普通は同期の場合にはバッファ リングを省略してコピーを減らす。

メールボックス(mailbox)には、しばしば有限のバッファ(bounded buffer)がも うけられる。

バッファがフルだった時にどうするか。 フルの時にだけ送信側をブロックさせる、という方法でいいのか。 In asyncronous primitives, can we block when a buffer is full?

◆帯域予約(bandwidth reservation)

帯域保証とは、たとえば、64k bps のように、毎秒、どのくらいデータ を送ることができるかを保証することである。

普通の固定電話(fixed-line phone)の場合、64k bps の帯域予約(bandwidth reservation)、帯域保証(bandwidth guarantee) がなされている。電話を掛け る人が増えてきたとしても、それが 32k bps に減らされるようなことはない。 その代りに、電話を掛けようとすると「通話中(busy)」というエラーが返り、 通信そのものが行えなくなる。

◆遅延保証(delay guarantee)

send してから receive するまで、最長どのくらいの時間がかかるかを保証す ることである。電話の場合、ある程度の遅延保証は行っているとも言える。テ レビの衛星中継(satellite transmission)のように、遅延が大きい媒体では、 普通の会話はできなくなる。

日本の電話の通信遅延

◆単方向/双方向(unidirectional/bidirectional)

単方向の通信しかできない場合でも、単方向の通信を2つ逆方向で組み合わせ ることもできる。

◆メッセージの順序の保証(preserving order of messages)

メッセージをsendした時にその通りの順序で receive できるかどうかという 意味である。結合が作られる場合は、普通は順序も保証される。

◆TCP/IP

TCP/IP は、性質が違う2つのプロトコルから構成されている。 普通にアプリケーションが使うのは、上位の TCP。

◆IP (Internet Protocol)

IPデータグラム転送サービス (datagram transport service)

◆TCP (Transmission Control Protocol)

ストリーム転送サービス (stream transport service) データの区切りが保存されると、sequenced packet と呼ばれる。

◆UDP (User Datagram Protocol)

IPと同じく、データグラム(datagram)転送サービス。 IPと違うのは、アドレス指定のみ。

◆どの通信プリミティブを使うか/choosing communication primitives

分散システムを構築する時に、どの通信プリミティブを使うか 独自の例 examples of specific protocols
順序付きパケット (sequenced packet)。
ストリームと似ているが、メッセージの区切りが保存される(preserving boundaries of messages)。 TCP で、メッセージ本体の前に、バイト数を送ることで実装できる。 sized_io library (initport()).
信頼性のあるデータグラム (reliable datagrams)
通常のデータグラムと似ているが、メッセージが紛失した時に再送する(retransmission)。

◆まとめ(summary)

TCP IP UDP イーサネット 電話 郵便
send   非同期 非同期 非同期 非同期 非同期 非同期
receive  同期 (非同期)* 同期 (非同期)* 同期 非同期
信頼性 あり なし なし なし あり なし
アドレス指定 間接 間接 間接 間接 間接 直接
結合   あり なし なし なし あり なし
方向 双方向 単方向 単方向 単方向 双方向 単方向
マルチキャスト 不可 可能 可能 可能 可能 不可
帯域保証 なし なし なし なし あり なし
* OS 内。

■Socket API

ソケットAPIは、TCP/IP をBSD 系 Unix に導入する時に設計された API であ る。

今後 TCP/IP 以外にも様々な通信プロトコルが開発され、Unix で利用できる ように設計されている。TCP/IP で使う時には、煩雑である。

◆ソケットAPIでのプロトコルの指定/protocol switch

    s = socket(domain, type, protocol);
主に domain と type で利用するプロトコルを指定する。 最後の引数 protocol は、普段は 0 を指定する。
ドメイン(domain) 型(type) プロトコル(protocol)
PF_INET SOCK_STREAM TCP(IPv4)
PF_INET SOCK_DGRAM UDP(IPv4)
PF_INET6 SOCK_STREAM TCP(IPv6)
PF_INET6 SOCK_DGRAM UDP(IPv6)
PF_UNIX SOCK_STREAM 同一ホスト内(UNIXドメイン)のストリーム
PF_UNIX SOCK_DGRAM 同一ホスト内(UNIXドメイン)のデータグラム
PF_NS SOCK_STREAM XNS のストリーム(SPP)
PF_NS SOCK_SEQPACKET XNS の順序付きパケット(IDP)
PF_NS SOCK_RDM XNSの信頼性のあるデータグラム(SPP)
特定の組み合わせのみ可能。
    s = socket(PF_INET,SOCK_STREAM,   0) // TCP, ok.
    s = socket(PF_INET,SOCK_SEQPACKET,0) // error.
    s = socket(PF_NS,  SOCK_SEQPACKET,0) // XNS sequenced packets, ok.

◆ソケットAPIの主要なシステムコール、または、ライブラリ関数/Important system calls and library functions in Socket API

名前(name) 説明(description)
socket() 通信プロトコルに対応したソケット・オブジェクトを作成する
connect() 結合(conection)を確立させる。サーバのアドレスを固定する。
listen() サーバ側で接続要求の待ち受けを開始する。
accept() サーバ側で接続されたソケットを得る。
bind() ソケットにアドレス(名前)を付ける。
getpeername() 通信相手のアドレス(名前)を得る。
getsockname() 自分のアドレス(名前)を得る。
send(), sendto(), sendmsg() メッセージを送信する。
recv(), recvfrom(), recvmsg() メッセージを受信する。
shutdown() 双方向の結合を部分的に切断する。
getsockopt() オプションの現在の値を取得する。
setsockopt() オプションを設定する。
select(), poll() 複数の入出力(通信を含む)を多重化する。
write() メッセージを送信する。
read() メッセージを受信する。
close() ファイル記述子を閉じる。他に参照しているファイル記述子がなければ、ソケット・オブジェクトを削除する。
write(), read(), close() はファイルと共通。

◆主要な IP アドレスを扱う関数/functions that manipulate IP addresses

名前(name) 説明(description)
gethostbyname() ホスト名から IP アドレスを調べる。
getaddrinfo() ホスト名から IP アドレスを調べる。IPv6対応。
gethostbyaddr() IPアドレスからホスト名を調べる。
getnameinfo() IPアドレスからホスト名を調べる。IPv6対応。
freeaddrinfo() getaddrinfo(), getnameinfo() で得られた構造体を解放する。
関数名には、IP以外も考えた名前にして欲しかった。

◆講義用TCP Library(講義用のTCP接続を作成するためのライブラリ)/TCP library functions for the class System Program of coins

情報科学類の講義、 システムプログラムで 新城が例題を示すために作成したAPI。 実際の通信は、send(), recv(), write(), read() 等で行う。
tcp_acc_port( int portno ) (サーバ側)
TCP/IP で、サーバ側の接続を受け付けるためのソケットを作る。引数 portno は、サーバ側の TCP のポート番号。これ以降、クライアントは接続要 求を行える。
int tcp_connect( char *server, int portno ) (クライアント側)
サーバ名 server のポート番号 portno に TCP の接続を確立させる。
その他に、サーバ側では、Socket API のaccept() をそのまま使う。 accept() は、1つのクライアントから接続要求を受け付ける。 第1引数の socket は、tcp_acc_port() で作成したソケットを渡す。

◆JavaのソケットAPI/Socket API in Java

Java言語は、基本的に TCP/IP と UDP/IP しかサポートしていない。したがっ て、TCP/IP や UDP/IP のプログラムを作成する場合には、分かりやすくなっ ている。

TCP/IP では、クライアント側とサーバ側でソケット・オブジェクトの作成する クラスが違っている。

クラス名(class name) 説明 (description)
Socket TCP/IP のクライアント側のソケット
ServerSocket TCP/IP のサーバ側のソケット
DatagramSocket UDP/IP のソケット
Java でも、実際の通信には、ファイルと同じ API を用いる。 例: Socket クラスのオブジェクトに対して getInputStream() というメソッドを実行する と、InputStream クラスのオブジェクトが返される。 InputStream は、ファイルからの入力と共通。

以後、ネットワークか ら文字列を入力するには、InputStreamReader や BufferedReader のオブジェ クトを生成して利用する。

出力側では、Socket クラスのオブジェクトに対して getOutputStream() して、 OutputStream クラスのオブジェクトを得て、 PrintStream オブジェクトを生成して利用できる。

■marshaling/unmarshaling

プログラム中のデータ項目とネットワーク上を流れるメッセージに対応づける。 Map data structures in memory to messages over a network.
marshaling (整列化)
メモリ中からデータ項目を集めて、ネットワークでメッセージとして 転送するのに適した形式にまとめる。
unmarshaling (非整列化)
逆。
英語の綴りは、l が1つのものと2つのもの(イギリス綴り)がある。教科書によって違う。

プロセスAがメモリ中のデータを1個にまとめてネットワークに送り出す。プロセスBが受け取り元に戻す

図? marshalingとunmarshaling

4個の要素からなる構造体(a structure with four components)を整列化して送信している。 整列化する基本的な方法 fundamental method unmarshaling ネットワークからデータを受け取ると、先頭から解釈して元のデータを再現す る。

ネットワーク上を流れている時には、整列化されたデータの先頭にはネットワー クのヘッダ(network headers)が付加されている。

◆整数のmarshaling/marshaling an integer

分散プログラムでは、メッセージを送信するプロセスと受信するプロセスが異 なる CPU で実行されることがある。整数をmarshalingする時には、次のような 点を考慮する必要がある。 厳密な規格では、バイト(byte) の代わりに (octet) を使う。

◆バイト・オーダ(byte order)

整数を送るだけでも、バイトオーダに気をつける必要がある。

C言語で扱える整数 Integers in a C languages

(現在のコンピュータのほとんどは、バイト単位でアドレスを付けているので、 1バイトの整数については、バイトオーダの問題はない。)

2バイト以上の整数をメモリに保存する方法: メモリの下位番地 に上位バイト(most significant byte)を置くか下位バイトを置くか

リトルエンディアン (little endian)
下位番地に下位バイトを置く。x86 (Intel Pentium/Core, AMD Athlon/Phenom)。
ビッグエンディアン (big endian)。
下位番地に上位バイトを置く。PowerPC, SPARC, m68k
PowerPC は、両方切り替え可能だが、ビッグエンディアンで使うことが多い。

◆0x2000番地に0x12345678を保存/saving 0x12345678 to the address 0x2000

main()
{
    int *p;
        p = (int *)0x2000;
        *p = 0x12345678;
}
        .globl  _main
        .align  4, 0x90
_main:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    $305419896, 8192
        popq    %rbp
        ret

0x12345678を2000番地に保存する。

図? バイト・オーダ(byte order)

◆ビッグエンディアンとリトルエンディアンの比較/comparision of big endian and little endian

◆送り方/transmitting an integer

現在、ネットワーク・バイト・オーダとしては、ビッグエンディアンが広く 使われている。

◆バイトオーダを変換するライブラリ関数/ library functions for converting byte order

名前 Name 方向 direction ビット数 # of bits
uint32_t htonl(uint32_t hostlong) ホストからネットワークへ変換 convert host to network byte order 32ビット
uint16_t htons(uint16_t hostshort) ホストからネットワークへ変換 convert host to network byte order 16ビット
uint32_t ntohl(uint32_t netlong) ネットワークからホストへ変換 convert network to host byte order 32ビット
uint16_t ntohs(uint16_t netshort) ネットワークからホストへ変換 convert network to host byte order 16ビット

◆htonl() を使った整数の送信/ sending an integer with htonl()

unsigned long int hostlong, netlong;    
hostlong = 0x12345678 ;    
netlong = htonl( hostlong );    
send(conn, &netlong, sizeof(netlong), 0);

◆snprintf()/strtol()

snprintf() で文字列に直して送り、strtol() や atoi() でもどす方法もある。 文字列文化。インターネットのアプリケーションでよく使われる。

送信側(sender):

    char buf[BUFSIZE];
    hostlong = 0x12345678 ;    
    snprintf(buf,BUFSIZE,"%d\n",hostlong );
    send(conn, buf, strlen(buf), 0);
思ったほど遅くはない。

注意:sscanf() は、整数をデコードするために使う分には問題ないが、文字列 を受け取るために使うとバッファ・オーバーフロー(buffer overflow)が生じる 可能性があるので、使わない方がよい。

◆文字列のmarshaling/marshaling strings

Unicode BOM (byte order mark) 0xffef。

◆XDR

SunRPC (後述) で使われているデータ形式。バイナリ文化。

rpcgen というスタブ・コンパイラがある。 データ構造を与えると、marshaling を行う手続きを自動生成する。

SunRPC を使わなくて、XDR だけを使う方法もある。

xdrmem_create(XDR *, const caddr_t, const uint_t, const enum xdr_op)
メモリの指定されたの番地に保存/回復/メモリの解放。
void xdrstdio_create(XDR *, FILE *, const enum xdr_op)
FILE * を通じて、構造体の読み書き。

◆その他(others)

■クライアント・サーバ・モデル/Client Server Model

通信を構造化(structuring of interprocess communication)。 send()/receive() を直接使うのは、goto (jump) でプログラムを書くようなも の。call/if/while で書きたい。

プロセスを2種類に分類する。通信は、次のパタンを繰り返す。 Classify processes into two types.

クライアント client
先にメッセージ(要求(request message))を send() 1回、後でメッセージ(応答(reply message, response message))を receive() 1回
サーバ server
先にメッセージ(要求(request message))を receive() 1回、後でメッセージ(応答(reply message))を send() 1回
send() の回数と receive() の回数は同じ。相互に繰り返す。

図? send(),receive()と繰り返すクライアントと receive(),send() と繰り返すサーバ

図? 通信のパタンからみたクライアントとサーバの定義

◆クライアントとサーバに分けて考える利点/advantages of classifying processes into clients and servers

混沌としたプロセス間通信(chaotic interprocess communication)を「構造化(structuring)」してわかりやすくする。

図? プロセス5つ、構造化されていない通信パタン

図? 構造化されていないもの(unstructured communication)

図? プロセス5つ、構造化された通信パタン

図? 構造化されたもの(structured communication)

構造化プログラミング(structured programming):制御構造(control flow)で 分かりにくいgoto文をつかわないで、わかりやすいgoto文だけ使う。

◆サービスの授受/providing and receiving services

元々の意味
クライアント(client)
サービスを受ける方、顧客 service recipient
サーバ(server)
サービス(service)を提供する方 service provider

図? サービスの授受によるクライアントとサーバの定義

図? サービスの授受によるクライアントとサーバの定義/definition of client and server by service providing and receiving

◆利用者数/ number of users

サービスを提供する方は、1つのプログラム(コンピュータ)で複数の利用者(multiple users) の面倒をみる。その結果、1台のサーバに複数のクライアント(multiple clients)がつながる。

クライアント client
一人で使うもの used by a single user
サーバ server
複数人で共有するもの used by multiple users

図? 複数のクライアントによるサーバの共有

図? 複数のクライアントによるサーバの共有/sharing of a server by multiple clients

◆接続方法/connection establishment

TCP/IP の通信では、通信を始める前に、まず、 結合(connection)を作る作る必要がある。 これは、電話で話をする前に、まず、電話をかける操作(making a telephone call)を行うことと似ている。
クライアント client
電話を掛ける方に相当する caller of a telephone call
サーバ server
電話を待っている方 callee of a telephone call

以上のように、クライアントとサーバは、いろいろな意味で使われる。これら の意味は、多くの場合、一致しているが、一致していないこともある。

◆能動的・受動的/passive and active objects

通信を開始するパタンで、コンピュータ、プログラム、人間は、次の2つに分 類される。

能動的(active)
ほっといても自分でメッセージを発信し始める
受動的(passive)、受け身
何か言われると答えるが、自分ではメッセージを発信し始めることはない
クライアントとサーバから作られたシステムは、クライアントが能動的になり、 サーバは、受動的になることが多い。

図? 能動的なクライアントと受動的なサーバ

図? 能動的なクライアントと受動的なサーバ/active clients and a passive server

例:Webサーバは、WWWクライアントから何か要求が来ない限り、ずっと 黙っている。

コンピュータを使う時には、人間が能動的になり、コンピュータが受動的にな る。

テレビを見ている時には、人間が受動的になり、テレビが能動的になる。

講義形式の授業では、サービスの授受では、教官がサーバで、学生がクライア ントになる。通信の開始の方法では、教官が能動的になり、学生が受動的にな る。

大学以上では、学生は、能動的になることが求められている。

◆Peer to Peer (P2P)

P2P (Peer to Peer) という用語の意味は、怪しい。

混沌とした通信を 構造化(structuring) してわかりやすくしたものが、クライアント・サーバ・モデル(a client server model)である。

サーバあるシステムでは、サーバが落ちるとシステム全体が動作しなくなる。 このように複数の要素から構成されているシステムで、ある要素が故障した時 に、全体が動作しなくなるような場所を、単一障害個所(single point of failure) という。

コンピュータサイエンスでは、古くから、単一障害個所を避けるための研究が 行われてきている。もっとも成功している方法は、サーバを複数(multiple servers)用意する方法である。

サーバがないシステムでは、下手に作るとどの要素が故障してもシステム全体 が止まってしまうことになる。

サーバがないシステムで成功している例はある。

peer は、「対等の仲間」の意味。「通信相手」という意味もある。

サーバがない方法の利点(特徴)

サーバがない方法の問題点

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

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

遠隔手続き呼び出しは、手続き呼び出しの一種ではない。プロセス間通信の一 種。 RPC is not procedure call.

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 http://doi.acm.org/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): カウンタ(a counter) SunPRC での記述方法。The interface description of a hash table in SunRPC.
   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 ;
インタフェース定義をスタブ生成器(stub generator)に与えると、クライアン ト側スタブ(client stub)、サーバ側スタブ(server stub)が自動的に生成され る。

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

自動生成されるもの generated programs

◆遠隔手続き呼び出しのまとめ/Remote procedure call summary

  1. ある手続きがクライアント側スタブを通常の方法で呼び出す。
  2. クライアント側スタブは、引数を整列化することでネットワーク用のメッ セージを作成し、そのローカルのオペレーティング・システムを呼び出す。
  3. クライアント側のオペレーティング・システムは、メッセージをリモート のサーバ側のオペレーティング・システムに送る。
  4. サーバ側のオペレーティング・システムは、メッセージをサーバ側のスタ ブに渡す。
  5. サーバ側のスタブは、メッセージを非整列化することで引数を取り出し、 サーバ側の手続きを呼び出す。
  6. サーバ側の手続きは、その仕事を行い、結果をサーバ側スタブに返す。
  7. サーバ側スタブは、結果を整列化することでネットワーク用のメッセージ を作成し、そのローカルのオペレーティング・システムを呼び出す。
  8. サーバ側のオペレーティング・システムは、そのメッセージをクライアン トに送る。
  9. クライアント側のオペレーティング・システムは、メッセージをクライア ント側スタブに渡す。
  10. クライアント側スタブは、メッセージを非整列化して、結果を取り出し、 その結果を呼び出された手続きに返す。

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

遠隔手続き呼び出しの意味「意味(semantics)」を、通常の手続き呼出しと同じ (透明)にしたい。しかし、完全に同じ「意味」を提供にすることは難しい。

◆通常の手続き呼出し/Passing arguments in regular (local) procedures

プログラミング言語における手続き呼出しでの引数の渡し方 プログラム言語によって、方法が違う。
int 構造体 オブジェクト 配列
C言語 - 自動ポインタ値化*
Java - 参照 参照
Ruby 参照** - 参照 参照
* C言語の配列を「参照呼び」と説明している教科書もある。

** 変更不可(immutable)。 言語が持つ意味を、RPCでは再現できないことがある。

◆call-by-value

C言語の通常の方式。変数の値のコピーが渡される。
   1:	#include <stdio.h>
   2:	
   3:	int square( int a ) {
   4:	    a =  a * a ;
   5:	    return( a );
   6:	}
   7:	
   8:	main() {
   9:	    int x, y, result;
  10:	    x = 10;
  11:	    y = 20;
  12:	    result = square( x );
  13:	    printf("x==%d, result==%d\n", x, result);
  14:	    result = square( y++ );
  15:	    printf("y==%d, result==%d\n", y, result);
  16:	}
実行結果
% make square-value [←]
cc     square-value.c   -o square-value
% ./square-value  [←]
x==10, result==100
y==21, result==400
% []

◆call-by-reference

C++ の参照型は、call-by-reference に近い。
   1:	#include <stdio.h>
   2:	
   3:	int square( int &a ) {
   4:	    a =  a * a ;
   5:	    return( a );
   6:	}
   7:	
   8:	main() {
   9:	    int x, y, result;
  10:	    x = 10;
  11:	    y = 20;
  12:	    result = square( x );
  13:	    printf("x==%d, result==%d\n", x, result);
  14:	//  result = square( y++ );
  15:	//  printf("y==%d, result==%d\n", y, result);
  16:	}
実行結果。引数で渡した変数 x の値が書き換えられている。
% make square-ref [←]
g++     square-ref.cc   -o square-ref
% ./square-ref  [←]
x==100, result==100
% []
C++の参照型では、引数には、変数しかかけない。y++ のような式では、コンパ イル時にエラーになる。
% cat -n square-ref.cc [←]
     1  #include <stdio.h>
     2
     3  int square( int &a ) {
     4      a =  a * a ;
     5      return( a );
     6  }
     7
     8  main() {
     9      int x, y, result;
    10      x = 10;
    11      y = 20;
    12      result = square( x );
    13      printf("x==%d, result==%d\n", x, result);
    14      result = square( y++ );
    15      printf("y==%d, result==%d\n", y, result);
    16  }
% make square-ref [←]
g++     square-ref.cc   -o square-ref
square-ref.cc: In function 'int main()':
square-ref.cc:14: error: invalid initialization of non-const reference of type 'int&' from a temporary of type 'int'
square-ref.cc:3: error: in passing argument 1 of 'int square(int&)'
make: *** [square-ref] Error 1
C++の参照型は、プログラムが読みにくくなるので使ってはいけない。
main() {
   int x ;
   x = 10 ;
   f( x );
   printf("%d\n",x);
}
C言語のレベルでは、f(x) と呼んでも、決して x の値は、変化しない。C++の 参照型を使えば、f() の型宣言を見ないと、変化するか変化しないかわからな い。「型宣言を見る」という手間の分だけ、プログラムが読みにくくなる。 全部の関数について、見ていたら疲れる。

◆ポインタ pointers

「参照(reference)」は、ポインタ(pointer)に似ている。ただし、C言語で渡 されるのは、ポインタのコピー(値)が渡される。
   1:	#include <stdio.h>
   2:	
   3:	int square( int *a ) {
   4:	    *a =  *a * *a ;
   5:	    return( *a );
   6:	}
   7:	
   8:	main() {
   9:	    int x, y, result, *xp, *yp;
  10:	    x = 10; xp = &x;
  11:	    y = 20; yp = &y;
  12:	    result = square( xp );
  13:	    printf("x==%d, result==%d, &x==0x%x, xp==0x%x\n", 
  14:	           x, result, &x, xp);
  15:	    result = square( yp++ );
  16:	    printf("y==%d, result==%d, &y==0x%x, yp==0x%x\n", 
  17:	           y, result, &y, yp);
  18:	}
% make square-pointer [←]
cc     square-pointer.c   -o square-pointer
% ./square-pointer  [←]
x==100, result==100, &x==0xbffff534, xp==0xbffff534
y==400, result==400, &y==0xbffff538, yp==0xbffff53c
% []
C++の参照型は、プログラムが読みにくくなるので使ってはいけないが、 C言語のポインタ渡しは、使ってもよい。
main() {
   int x ;
   x = 10 ;
   f( &x );
   printf("%d\n",x);
}
C言語のレベルでは、f(&x) と&を付けた段階で、 f() の内容を知らなくてもx の値が変化することが推察される。値が変化する 可能性について、ポインタを渡している関数だけ注意すればよい。

◆配列(array)

C言語では、配列を引数に渡す場合には、自動的にポインタ(先頭要素の番地) に変換される。 次のプログラムでは、どれも関数 f() に同じ値(配列の先頭番地)を渡してい る。配列の名前を書いても、& を書いたものと同じになる。
main() {
   int a[10] ;
   f( a );
   f( &a );
   f( &a[0] );
}
配列を自動的にポインタに変換する歴史的な理由

◆マクロ展開(macro expansion)

call-by-name は、C言語のマクロ展開に近い意味がある。
   1:	#include <stdio.h>
   2:	
   3:	#define square( a ) ((a)*(a))
   4:	
   5:	main() {
   6:	    int x, y, result;
   7:	    x = 10;
   8:	    y = 20;
   9:	    result = square( x );
  10:	    printf("x==%d, result==%d\n", x, result);
  11:	    result = square( y++ );
  12:	    printf("y==%d, result==%d\n", y, result);
  13:	}
実行結果。y++ と 1 度書いただけなのに、2 増えている。
% make square-macro [←]
cc     square-macro.c   -o square-macro
% ./square-macro  [←]
x==10, result==100
y==22, result==400
% []
マクロ展開だけして止めてみるとわかる。
% cc -E square-macro.c > square-macro.i [←]
% wc  square-macro.i [←]
     421    1135    9164 square-macro.i
% tail -13 square-macro.i  [←]
# 2 "square-macro.c" 2



main() {
    int x, y, result;
    x = 10;
    y = 20;
    result = ((x)*(x));
    printf("x==%d, result==%d\n", x, result);
    result = ((y++)*(y++));
    printf("y==%d, result==%d\n", y, result);
}
% []

◆call-by-value-restore

古い Fortran の実装。コンパイル時に変数がすべて決まる。スタックに引数を 置かなくてもよい。再帰がなかったので、そもそもスタックが不要で、戻り番 地を手続きごとの変数に書いていた。
   1:	#include <stdio.h>
   2:	
   3:	int square_a;
   4:	int square() {
   5:	    square_a = square_a * square_a ;
   6:	    return( square_a );
   7:	}
   8:	
   9:	main() {
  10:	    int x, y, result;
  11:	    x = 10;
  12:	    y = 20;
  13:	    square_a = x ; result = square(); x = square_a ;
  14:	    printf("x==%d, result==%d\n", x, result);
  15:	    square_a = y++ ; result = square(); y = square_a ;
  16:	    printf("y==%d, result==%d\n", y, result);
  17:	}
実行結果。
% make square-copy-restore [←]
cc     square-copy-restore.c   -o square-copy-restore
% ./square-copy-restore  [←]
x==100, result==100
y==400, result==400
% []

◆RPCの基本的な考え方と制約 RPC restrictions

この制約から、インタフェースを変えなければならないことがある。

◆インタフェース変更の例: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.

◆バインディング/binding

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

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

図? RPCのバインディング

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

◆サーバの複製(replication)

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

◆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など、テキストを使う RPC もある。

■SunRPC

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

◆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)

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 と 決まっている。 MacOSX 10.9 等では、portmap が動作していないことがある。rpcinfo -p で 何 も表示されない時には、root で 次のようにして実行する。

# launchctl start com.apple.rpcbind [←]

■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: 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-2015/2015-10-13/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 では、手続き呼び出しの形でインターネットを記述する。 marshaling と unmeaning を意識する必要はあまりない。

   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: 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);
  49:             if( counter_getvalue_result == (void *) NULL )
  50:             {
  51:                 clnt_perror(clnt, "call failed -- getvalue");
  52:                 exit( 2 );
  53:             }
  54:             printf("value==%d\n", *counter_getvalue_result);
  55:             xdr_free( (xdrproc_t)xdr_int, (char *)counter_up_result );
  56:         }
  57:         clnt_destroy( clnt );
  58: }
この関数の中心部分は、自動生成されたスタブ 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:         Created on: 2011/12/21 21:09:47
   5: */
   6: 
   7: #include <stdio.h>      /* stderr, fprintf() */
   8: #include <stdlib.h>     /* strtol() */
   9: #include <string.h>     /* memcpy() */
  10: #include "counter.h"
  11: 
  12: static int val;
  13: 
  14: void *
  15: counter_up_1_svc(void *argp, struct svc_req *rqstp)
  16: {
  17:         static char* result;
  18:         val ++;
  19:         return((void*) &result);
  20: }
  21: 
  22: int *
  23: counter_getvalue_1_svc(void *argp, struct svc_req *rqstp)
  24: {
  25:         static int  result;
  26:         result = val;
  27:         return(&result);
  28: }
  29: 
  30: void *
  31: counter_reset_1_svc(int *argp, struct svc_req *rqstp)
  32: {
  33:         static char* result;
  34:         val = *argp;
  35:         return((void*) &result);
  36: }
サーバ側では、手続き 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-2015/2015-10-13/ex/counter-client.c [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/counter-server.c [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/counter.x [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/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 が作られる。

例題を実行するには、クライアント用とサーバ用に端末を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
% []

■XML

XML(eXtensible Markup Language)

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

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

マークアップ言語の種類

◆拡張可能性(extensible)

HTML でも XML でも、マークアップのために、テキストにタグ(tag)を埋め込 む。HTML の場合、タグの種類と意味があらかじめ規定されており、その規定 の範囲でしか WWW データを表現することができない。

これに対して、XML では、定められた方式で新たなタグを定義することが可能 になっている。XML を使えば、特定の応用分野に特化したマークアップ言語を 文書の作成者が設計することができるようになる。

XML 形式の文書は、WWW ブラウザでの利用のように、人間が目にすることがあ る場所でも使われるが、むしろプログラムとプログラムがネットワークを越え てデータを交換するために使われることが多い。

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

■XML Web Services

◆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 サービスでよく使われている遠隔手続き呼出しの仕組みは、 XML-RPC と SOAP である。

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

◆XML-RPC and SOAP

XML-RPC と SOAP は、両方とも、 XML の技術を使って遠隔手続き呼出し(RPC using XML)を実現したもの。 XML-RPC の方が SOAP より古い。

XML-RPC では、クライアントとサーバの間の通信は、XML により行われる。ク ライアントは、手続きの名前や入力パラメタを XML の形式にまとめて、サー バへ送る。サーバでは、手続きが実行され、その結果もXMLの形でクライアン トへ返される。 XML-RPC では、クライアントとサーバの間の通信には、WWW で使わ れている HTTP が使われる。

XML-RPC では、メッセージが長くなり過ぎたり、型付けに問題があっ た。これを解決するために、SOAP (Simple Object Access Protocol) が作られた。

SOAP は、最初は、Simple Object Access Protocol の略であったが、 Object-Oriented ではないということで、この略は捨てられた。

XML-RPC では、データを交換するための中間形式として XML を使っていただ けであったが、SOAP では、XML のデータを直接 XML として渡すことができる。

SOAP では、HTTP の他に電子メールも通信媒体として使う事ができる。 SOAP 用に、HTTP のヘッダがいくつか拡張されたが、 実際問題として活用しにくい。 SOAP は、Microsoft 社などにより提案され、その後、W3C で標準化が行われ ている。

http://www.w3.org/TR/soap/

◆XML Web service and World Wide Web

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

◆XML web servicesの2つの開発形態/Two development methods of XML Web services

◆WSDL (Web Services Description Language)

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

WSDL で記述するもの

・データ型。Data types.
Web サービスのクライアントとサーバの間で交換され るデータ形式の定義。
・メッセージ。 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.
Web サービスでは、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>

◆UDDI (Universal Description, Discovery, and Integration)

UDDI は、Web サービスによる電子商取引を活性するための XML Web Service を検索する(search)ための仕組み。

UDDI では、UDDI レジストリ(registry)と呼ばれるデータベースに次のような 情報を格納する。

ビジネス(business):
サービス提供者の会社名、企業コード、分類情報など。
サービス(service):
Web サービスの集合。各Webサービスごとに、名前やバ インディング・テンプレートを登録する。
バインディング・テンプレート(binding template):
1つのWeb サービスを利用するた めに必要な情報(TModel)への参照。
UDDI を利用する Web サービスのサーバ TModel は、WSDL などWeb サービスを利用するために必要な情報を含んでいる。

UDDI を利用する Web サービスのクライアント(client)

インターネット上で利用可能な Web サービスのための UDDI レジ ストリとしては、IBM やマイクロソフトが運営しているもがある。

◆Webサービスの開発キット/XML Web Service Development Kits

XML を扱う環境は、Java が強い。スクリプト言語からも使える。

◆Glue

Glue は、The Mind Electric社(リンク切れ) により開発された Web サービス開発のためのツールキット。

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());
	}
    }
};
coins での実行例:
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/ICounter.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/Counter.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/CounterLocal.java [←]
% emacs Makefile (環境に合わせて Makefile の修正) [←]
% 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

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

◆サーバ 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 に含まれている Web サービスのサーバを起動して いる。その結、果新しいスレッドが作られるので、このプログラムは、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-2015/2015-10-13/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/ICounter.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/Counter.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/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-2015/2015-10-13/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/ICounter.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/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
% ls [←]
CounterClient.class     ICounter.class          Makefile
CounterClient.java      ICounter.java
% make run-CounterClient [←]
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterClient.java
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 [←]
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterClient.java
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回実行しても、同じカウンタが使われている所 に注意する。

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

◆サーバ側のエラー 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 でアクセス可能になる。WWW ブラウザで読むこともできる。下の 例では、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 では、クライアント、サーバ、相互にアクセス制御を行う必要がある。

Web サービスでは、WWW の技術を使ってアクセス制御を行うことができる。

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 が開発された。

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

◆Java で TCP/IP を直接使う Using TCP/IP in Java

RMI より下のレベルでは、TCP/IP を直接使うクラスがある。
java.net.Socket, java.nio.channels.SocketChannel
TCP/IP クライアント側
java.net.ServerSocket, java.nio.channels.ServerSocketChannel
TCP/IP サーバ側
java.net.DatagramSocket, java.nio.channels.DatagramChannel
UDP/IP
java.net.MulticastSocket, java.nio.channels.DatagramChannel+IP_MULTICAST_IF
UDP/IP マルチキャスト

◆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.
binding のために registry がある。転送には、TCP/IP が使われる。

◆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 する。

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

■ローカルのカウンタ 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.*;
   6: import java.rmi.server.*;
   7: import java.rmi.registry.*;
   8: 
   9: public class RCounterServer
  10: {
  11:     public static void main(String argv[])
  12:     {
  13:         if( argv.length != 1 )
  14:         {
  15:             System.err.println("Usage% java RCounterServer rmiregistry-portno");
  16:             System.exit( 1 );
  17:         }
  18:         int rmiregport = Integer.parseInt( argv[0] );
  19: 
  20:         if( System.getSecurityManager() == null )
  21:             System.setSecurityManager( new SecurityManager() );
  22: 
  23:         try
  24:         {
  25:             IRCounter c1 = new RCounter( 10 );
  26:             String name = "/Counter/c1" ;
  27:             Registry registry = LocateRegistry.getRegistry( rmiregport );
  28:             registry.rebind( name, c1 );
  29:         }
  30:         catch (Exception e)
  31:         {
  32:             System.err.println("RCounterServer error:"+e.getMessage());
  33:             e.printStackTrace();
  34:         }
  35:     }
  36: };

rmiregistry に登録する時には、次のような URL が使える。

myhost は、自分自身のホスト名。実験では、よく localhost を使う。

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

string は単なる文字列。フラットな名前空間。

◆クライアント a client program

[RCounterClient.java]

2015/10/13 18:30. 以下に掲載されたプログラムには、hostname を見ていない という問題があったので、修正しました。上のリンクや wget でアクセス可能 なプログラムは、修正済みだったので、その問題はありませんでした。

   1: //
   2: // RCounterClient.java
   3: //
   4: 
   5: import java.rmi.*;
   6: import java.rmi.registry.*;
   7: 
   8: class RCounterClient
   9: {
  10:     public static void main(String argv[])
  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:         int rmiregport = Integer.parseInt( argv[1] );
  19:         if( System.getSecurityManager() == null )
  20:             System.setSecurityManager( new SecurityManager() );
  21:         try
  22:         {
  23:             String name = "/Counter/c1" ;
  24:             Registry registry = LocateRegistry.getRegistry( hostname, rmiregport );
  25:             IRCounter c1 = (IRCounter) registry.lookup( name );
  26:             for( int i=0 ; i<3 ; i++ )
  27:             {
  28:                 c1.up();
  29:                 System.out.println("c1.value=="+c1.getValue());
  30:             }
  31:         }
  32:         catch (Exception e)
  33:         {
  34:             e.printStackTrace();
  35:         }
  36:     }
  37: };

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

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

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

% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/IRCounter.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/RCounter.java [←]
% emacs Makefile (環境に合わせて Makefile の修正) [←]
% make run-rmiregistry [←]
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar IRCounter.java
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar RCounter.java
rmiregistry 8080
(最後に ^C で止める)
一方のウインドウでサーバを動作させる。サーバもは自動的に終了しないので、 実験が終わったら ^C (Control-C)で殺す。
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/IRCounter.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/RCounter.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/RCounterServer.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/RCounterServer.policy [←]
% emacs Makefile (環境に合わせて Makefile の修正) [←]
% 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
% make run-RCounterServer [←]
java -Djava.security.policy=./RCounterServer.policy RCounterServer 8080
(最後に ^C で止める)

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

クライアント側では、ウインドウを1枚開く。
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/IRCounter.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/RCounter.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/RCounterClient.java [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/RCounterClient.policy [←]
% emacs Makefile (環境に合わせて Makefile の修正) [←]
% make RCounterClient [←]
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 RCounterClient.java
% make run-RCounterClient [←]
java -Djava.security.policy=./RCounterClient.policy RCounterClient localhost 8080
c1.value==11
c1.value==12
c1.value==13
% make run-RCounterClient [←]
java -Djava.security.policy=./RCounterClient.policy RCounterClient localhost 8080
c1.value==14
c1.value==15
c1.value==16
% []

◆セキュリティ Security

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

Java の標準のセキュリティのポリシー(java コマンドで利用される)は、 jre/lib/security/java.policy に記述されている。

System.setSecurityManager( new RMISecurityManager() ) した時には、 標準よりきつくなる。実験する時には、少し緩めないとつながらないことがある。

以下の例では、localhost と *.tsukuba.ac.jp からのアクセスを許している。

[RCounterServer.policy]

   1: //
   2: // RCounterServer.policy
   3: //
   4: 
   5: grant { 
   6:         permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect,resolve";
   7:         permission java.net.SocketPermission "*.tsukuba.ac.jp:1024-", "accept,listen,connect,resolve";
   8: };
[RCounterClient.policy]
   1: //
   2: // RCounterClient.policy
   3: //
   4: 
   5: grant { 
   6:         permission java.net.SocketPermission "localhost:1024-", "listen,connect,resolve";
   7:         permission java.net.SocketPermission "*.tsukuba.ac.jp:1024-", "listen,connect,resolve";
   8: };
クライアント側では、うまく設定すると、サーバ側に置かれたクラスを動的に http や ftp でロードして実行できる。その時には、 java.rmi.server.codebase プロパティを使う。

◆直列化(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

◆ローカル/利用 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() を作成している。

coins での実行例:

% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/counter.rb [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/counter-local.rb [←]
% emacs Makefile (環境に合わせて Makefile の修正) [←]
% 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-2015/2015-10-13/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/counter.rb [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/counter-server.rb [←]
% emacs Makefile (環境に合わせて Makefile の修正) [←]
% make run-counter-server.rb [←]
ruby 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-2015/2015-10-13/ex/Makefile [←]
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2015/2015-10-13/ex/counter-client.rb [←]
% emacs Makefile (環境に合わせて Makefile の修正) [←]
% make run-counter-client.rb [←]
ruby 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 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 のライブラリ。クライア ント側でも非同期的なインタフェースがある。

練習問題

以下の問題(202-204)の中で、1つを選び、回答しなさい。 今日の課題では、プログラミング言語としては、C言語、Java、または、Rubyを用いなさい。 また、RPC としては、この Web ページに説明があるものを使いなさい。 Choose one question and answer it from the following questions (202-204). You can use the C, Java, or Ruby language. You should use an RPC mechanism which is described in this Web page.

XML Webサービス のプログラムは、次の環境で動作する。 You can run the programs of XML Web Services in the following environments.

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

この内容を含む「テキスト」ファイルを作成し、 レポート提出ページ から提出しなさい。

締切りは、2015年10月18日、 23:59:59 とする。

★問題(201) パブリックなWebサービスの利用 Using a publix XML Web service

XMethods で紹介されているもの、 Amazon Web Services、または、その他のパブリック に利用になっている Web サービスのクライアント側のプログラムを記述しなさい。

Write a client program of public XML Web service in XMethods, Amazon Web Services, or other public XML Web services.

★問題(202) コンテナ Containers

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

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

★問題(203) 複数インスタンス可能なカウンタ multiple counter instances

カウンタの例( SunRPC, XML Webサービス, Java RMI,および, dRuby) では、カウンタは1つしか作られない。これらのいずれかを修正して、複数の カウンタが使えるようにしなさい。 Each counter server in this Web page ( SunRPC, XML Web Service, Java RMI,and, dRuby ) deals with a single counter object in it. Rewrite one of these programs to one that can deal with multiple counter instances.

ヒント: main() では、カウンタそのものではなく、カウンタ・オブジェクト を生成するオブジェクトを追加し、遠隔からアクセス可能にする。新たにカウ ンタ・オブジェクトを生成するたびにそれを遠隔からアクセス可能にする。 Hints: the main() method creates a factory object that creates counter objects. The factory object must create a remote object for each local counter object.

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

★問題(204) カウンタへのメソッドの追加 Adding methods to a counter object

カウンタの例( SunRPC, XML Webサービス, Java RMI,および, dRuby) から1つを選び、次のようなメソッドを追加しなさい。 Choose one counter object in this Web page ( SunRPC, XML Web Service, Java RMI, or dRuby ), and add the following method.

★問題(205) ディレクトリ一覧 directory server

次のような機能を持つ RPC のサーバとクライアントを実現しなさい。 Realize the client and server in RPC that have following functions. なお SunRPC のソース(リンク切れ)には、 demo/dir に類似のプログラムが含まれている。 Note that the source code of SunRPC from Sun Microsystems(dead link) once contained the similar program in demo/dir.

★問題(206) Java言語以外の言語による XML Web サービス / XML Web services in a non-Java language

XML Web Serviceによるカウンタのクライアント、ま たは、サーバをJava言語以外の言語を使って記述しなさい。 そして、Java 言語で記述したものと接続して動作させなさい。 Rewrite the client or the server of the conter program of XML Web Service in Java to one in a non-Java language. Execute the program that is connected to the program in Java.
Last updated: 2015/10/13 18:43:49
Yasushi Shinjo / <yas@cs.tsukuba.ac.jp>