並行システム システム情報系情報工学域, システム情報工学研究科コンピュータサイエンス専攻 新城 靖 <yas@cs.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25
あるいは、次のページから手繰っていくこともできます。
http://www.cs.tsukuba.ac.jp/~yas/cs/
http://www.cs.tsukuba.ac.jp/~yas/
ネットワークで接続された複数のコンピュータ(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.
信頼性性があるかないか。専門用語では、程度問題(高い低い)ではなく、有 るか無いか。
信頼性がある通信プリミティブを利用した場合、あるプロセスが送信したメッ セージは、途中で失われることはなく(without message loss)、有限時間以内 (within a finite time)に通信相手のプロセスに送り届けられる。
分散システムでは、メッセージは、失われることがある。 a message can be lost.
図? 結合が作られる通信プリミティブ/sending and recieving messagess with connection-oriented communication primitives
図? 結合が作られる通信プリミティブと結合が作られない通信プリミティブ/sending and recieving messagess with connectionless communication primitives
着信側 callee
make_port(); // 受付端の登録。 accept(); // 実際の受付。connect() と対応。 receive(); send(); receive(); send(); close(); // 接続の切断。発信側 caller
connect(); // 接続要求。accept() と対応。 send(); receive(); send(); receive(); close(); // 接続の切断。
プロセスA
receive(); send(); receive(); send();プロセスB
send(); receive(); send(); receive();
ほとんどの通信プリミティブは、間接。 Most communication primitives use indirect addressing.
直接の例: few examples of direct addressing
問題:直接アドレス指定では、receiveする前にsendされると、どのプロセス にメッセージを送っていいのかわからない。
解決
図? 同期型send/synchronous send() 図? 非同期型send/asynchronous send()
send,receive それぞれ2種類ある。
非同期の方が、並行性が高い。しかし、弱点も多い。 asynchronous communication has higher concurrency, but has many drawbacks.
2. は、転送完了割り込み(interrupt)で解決可能。プログラミングが難しい。 割り込みよりは、マルチスレッドがよい。
プログラミングスタイルの選択 programming styles
同期、非同期とは直行しているとも言えるが、普通は同期の場合にはバッファ リングを省略してコピーを減らす。
メールボックス(mailbox)には、しばしば有限のバッファ(bounded buffer)がも うけられる。
バッファがフルだった時にどうするか。 フルの時にだけ送信側をブロックさせる、という方法でいいのか。 In asyncronous primitives, can we block when a buffer is full?
普通の固定電話(fixed-line phone)の場合、64k bps の帯域予約(bandwidth reservation)、帯域保証(bandwidth guarantee) がなされている。電話を掛け る人が増えてきたとしても、それが 32k bps に減らされるようなことはない。 その代りに、電話を掛けようとすると「通話中(busy)」というエラーが返り、 通信そのものが行えなくなる。
TCP | IP | UDP | イーサネット | 電話 | 郵便 | |
send | 非同期 | 非同期 | 非同期 | 非同期 | 非同期 | 非同期 |
receive | 同期 | (非同期)* | 同期 | (非同期)* | 同期 | 非同期 |
信頼性 | あり | なし | なし | なし | あり | なし |
アドレス指定 | 間接 | 間接 | 間接 | 間接 | 間接 | 直接 |
結合 | あり | なし | なし | なし | あり | なし |
方向 | 双方向 | 単方向 | 単方向 | 単方向 | 双方向 | 単方向 |
マルチキャスト | 不可 | 可能 | 可能 | 可能 | 可能 | 不可 |
帯域保証 | なし | なし | なし | なし | あり | なし |
今後 TCP/IP 以外にも様々な通信プロトコルが開発され、Unix で利用できる ように設計されている。TCP/IP で使う時には、煩雑である。
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.
名前(name) | 説明(description) |
socket() | 通信プロトコルに対応したソケット・オブジェクトを作成する |
connect() | 結合(conection)を確立させる。サーバのアドレスを固定する。 |
listen() | サーバ側で接続要求の待ち受けを開始する。 |
accept() | サーバ側で接続されたソケットを得る。 |
bind() | ソケットにアドレス(名前)を付ける。 |
getpeername() | 通信相手のアドレス(名前)を得る。 |
getsockname() | 自分のアドレス(名前)を得る。 |
send(), sendto(), sendmsg() | メッセージを送信する。 |
recv(), recvfrom(), recvmsg() | メッセージを受信する。 |
shutdown() | 双方向の結合を部分的に切断する。 |
getsockopt() | オプションの現在の値を取得する。 |
setsockopt() | オプションを設定する。 |
select(), poll() | 複数の入出力(通信を含む)を多重化する。 |
write() | メッセージを送信する。 |
read() | メッセージを受信する。 |
close() | ファイル記述子を閉じる。他に参照しているファイル記述子がなければ、ソケット・オブジェクトを削除する。 |
名前(name) | 説明(description) |
gethostbyname() | ホスト名から IP アドレスを調べる。 |
getaddrinfo() | ホスト名から IP アドレスを調べる。IPv6対応。 |
gethostbyaddr() | IPアドレスからホスト名を調べる。 |
getnameinfo() | IPアドレスからホスト名を調べる。IPv6対応。 |
freeaddrinfo() | getaddrinfo(), getnameinfo() で得られた構造体を解放する。 |
tcp_acc_port( int portno )
(サーバ側)
int tcp_connect( char *server, int portno )
(クライアント側)
accept()
をそのまま使う。
accept()
は、1つのクライアントから接続要求を受け付ける。
第1引数の socket は、tcp_acc_port() で作成したソケットを渡す。
TCP/IP では、クライアント側とサーバ側でソケット・オブジェクトの作成する クラスが違っている。
クラス名(class name) | 説明 (description) |
---|---|
Socket | TCP/IP のクライアント側のソケット |
ServerSocket | TCP/IP のサーバ側のソケット |
DatagramSocket | UDP/IP のソケット |
以後、ネットワークか ら文字列を入力するには、InputStreamReader や BufferedReader のオブジェ クトを生成して利用する。
出力側では、Socket クラスのオブジェクトに対して getOutputStream() して、 OutputStream クラスのオブジェクトを得て、 PrintStream オブジェクトを生成して利用できる。
図? marshalingとunmarshaling
4個の要素からなる構造体(a structure with four components)を整列化して送信している。ネットワーク上を流れている時には、整列化されたデータの先頭にはネットワー クのヘッダ(network headers)が付加されている。
C言語で扱える整数 Integers in a C languages
2バイト以上の整数をメモリに保存する方法: メモリの下位番地 に上位バイト(most significant byte)を置くか下位バイトを置くか
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
図? バイト・オーダ(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ビット |
unsigned long int hostlong, netlong; hostlong = 0x12345678 ; netlong = htonl( hostlong ); send(conn, &netlong, sizeof(netlong), 0);
送信側(sender):
char buf[BUFSIZE]; hostlong = 0x12345678 ; snprintf(buf,BUFSIZE,"%d\n",hostlong ); send(conn, buf, strlen(buf), 0);思ったほど遅くはない。
注意:sscanf() は、整数をデコードするために使う分には問題ないが、文字列 を受け取るために使うとバッファ・オーバーフロー(buffer overflow)が生じる 可能性があるので、使わない方がよい。
rpcgen というスタブ・コンパイラがある。 データ構造を与えると、marshaling を行う手続きを自動生成する。
SunRPC を使わなくて、XDR だけを使う方法もある。
通信を構造化(structuring of interprocess communication)。 send()/receive() を直接使うのは、goto (jump) でプログラムを書くようなも の。call/if/while で書きたい。
プロセスを2種類に分類する。通信は、次のパタンを繰り返す。 Classify processes into two types.
図? 通信のパタンからみたクライアントとサーバの定義
図? 構造化されていないもの(unstructured communication)
図? 構造化されたもの(structured communication)
構造化プログラミング(structured programming):制御構造(control flow)で 分かりにくいgoto文をつかわないで、わかりやすいgoto文だけ使う。
図? サービスの授受によるクライアントとサーバの定義/definition of client and server by service providing and receiving
図? 複数のクライアントによるサーバの共有/sharing of a server by multiple clients
以上のように、クライアントとサーバは、いろいろな意味で使われる。これら の意味は、多くの場合、一致しているが、一致していないこともある。
通信を開始するパタンで、コンピュータ、プログラム、人間は、次の2つに分 類される。
図? 能動的なクライアントと受動的なサーバ/active clients and a passive server
例:Webサーバは、WWWクライアントから何か要求が来ない限り、ずっと 黙っている。
コンピュータを使う時には、人間が能動的になり、コンピュータが受動的にな る。
テレビを見ている時には、人間が受動的になり、テレビが能動的になる。
講義形式の授業では、サービスの授受では、教官がサーバで、学生がクライア ントになる。通信の開始の方法では、教官が能動的になり、学生が受動的にな る。
大学以上では、学生は、能動的になることが求められている。
混沌とした通信を 構造化(structuring) してわかりやすくしたものが、クライアント・サーバ・モデル(a client server model)である。
サーバあるシステムでは、サーバが落ちるとシステム全体が動作しなくなる。 このように複数の要素から構成されているシステムで、ある要素が故障した時 に、全体が動作しなくなるような場所を、単一障害個所(single point of failure) という。
コンピュータサイエンスでは、古くから、単一障害個所を避けるための研究が 行われてきている。もっとも成功している方法は、サーバを複数(multiple servers)用意する方法である。
サーバがないシステムでは、下手に作るとどの要素が故障してもシステム全体 が止まってしまうことになる。
サーバがないシステムで成功している例はある。
peer は、「対等の仲間」の意味。「通信相手」という意味もある。
サーバがない方法の利点(特徴)
遠隔手続き呼び出しは、手続き呼び出しの一種ではない。プロセス間通信の一 種。 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
例(example):手続き put()。ハッシュ表(hash table)にデータを格納する手続
きで、引数(arguments)にキー(key)となる文字列(string)と値(value)となる整
数(integer)を取る。
図? スタブによる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 ;インタフェース定義をスタブ生成器(stub generator)に与えると、クライアン ト側スタブ(client stub)、サーバ側スタブ(server stub)が自動的に生成され る。
インタフェース記述の内容 Interface description
int | 構造体 | オブジェクト | 配列 | |
C言語 | 値 | 値 | - | 自動ポインタ値化* |
Java | 値 | - | 参照 | 参照 |
Ruby | 参照** | - | 参照 | 参照 |
** 変更不可(immutable)。 言語が持つ意味を、RPCでは再現できないことがある。
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
%
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() の型宣言を見ないと、変化するか変化しないかわからな い。「型宣言を見る」という手間の分だけ、プログラムが読みにくくなる。 全部の関数について、見ていたら疲れる。
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 の値が変化することが推察される。値が変化する 可能性について、ポインタを渡している関数だけ注意すればよい。
main() { int a[10] ; f( a ); f( &a ); f( &a[0] ); }配列を自動的にポインタに変換する歴史的な理由
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);
}
%
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
%
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 ; }単純に行えば、上のように余計なコピーが入ることがある。 スタブで最適化すれば、クライアントからサーバへのコピーを減らせる。
図? RPCのバインディング
クライアント側のプログラムとサーバ側のプログラムの対応はもはや1対1で はない。XML-RPC, SOAPなど、テキストを使う RPC もある。
rpcgen
というコマンド。
rpcgen
コマンドを使うには、次のようなファイルを作成する。
図? rpcgenによるRPCプログラム開発で利用するファイル
開発者は次のようなプログラムを記述する。 A developer writes following programs.name.x
name_client.c
name_server.c
% rpcgen name.x
次の4つのファイルが生成される。
The rpcgen command generates four files.
name.h
name_clnt.c
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
/etc/rpc
にある。
SunRPCでは, 最終的にはTCP/IPまたはUDP/IPでデータが送られる。プログラム 番号などTCP/IPまたはUDP/IPのポート番号を対応させる必要がある。
各ホストには
portmap (portmapper)
というサーバがいて、3つ組
<Program number,version number,protocol>を、TCP/IPまたはUDP/IPのポート番号へ変換する。
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
void up(void)
int getvalue(void)
void reset(int)
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-2016/2016-04-25/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
%
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
番号の括弧が取り囲んでいる。
システムによって生成されるものが異なる。 ヘッダファイルの主要部分は、以下の通りである。
... 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 *); ...
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
しているので何もしない。)
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)
へのプログラム番号とバージョン番号の登録される。
% mkdir counter-rpc
% cd counter-rpc
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/counter-client.c
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/counter-server.c
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/counter.x
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/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 は、World Wide Web (WWW) で使われている HTML (Hyper Text Markup Language) と同様に、マークアップ言語(markup language)の一種。
マークアップ言語とは、文書(テキスト)に、「ここは表題」、「ここは箇条 書」といった、文書の構造を示す目印(マーク)を付ける機能を持つ言語。
マークアップ言語の種類
HTML でも XML でも、マークアップのために、テキストにタグ(tag)を埋め込 む。HTML の場合、タグの種類と意味があらかじめ規定されており、その規定 の範囲でしか WWW データを表現することができない。
これに対して、XML では、定められた方式で新たなタグを定義することが可能 になっている。XML を使えば、特定の応用分野に特化したマークアップ言語を 文書の作成者が設計することができるようになる。
XML 形式の文書は、WWW ブラウザでの利用のように、人間が目にすることがあ る場所でも使われるが、むしろプログラムとプログラムがネットワークを越え てデータを交換するために使われることが多い。
World Wide Web コンソーシアムのXMLのページ
http://www.w3.org/XML/
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」と言う。
Web サービスでは、異なるプログラミング言語で書かれたコンポーネントも相 互に接続できる。
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 で標準化が行われ ている。
WSDL で記述するもの
We can generate client stubs and server stubs from 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 では、UDDI レジストリ(registry)と呼ばれるデータベースに次のような 情報を格納する。
UDDI を利用する Web サービスのクライアント(client)
HTTP サーバを内蔵しているので、手軽に試すのに適している。 WSDL を自動生成する機能がある。
The Mind Electric社は、2003年にwebMethods社により買収された。 webMethods社は、2007年に Software AG社によって買収された。
1: // 2: // ICounter.java 3: // 4: 5: public interface ICounter 6: { 7: void up(); 8: int getValue(); 9: void reset(int newVal); 10: };単に利用するだけならば、インタフェースを定義しなくてもよいが、リモート との比較のためにあえて定義する。カウンタは、3つの手続きを持つものであ る。
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つの手続きとコンストラクタを実現している。
// // 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-2016/2016-04-25/ex/Makefile
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/ICounter.java
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/Counter.java
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/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 される。
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つの引数を取る。
HTTP.Startup() で、Glue に含まれている Web サービスのサーバを起動して いる。その結、果新しいスレッドが作られるので、このプログラムは、main() が終了しても、動き続けることになる。
Registry.publish() は、Glue に含まれている機能である。 これは、与えられた名前で、オブジェクトをアクセス可能にする。
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つの引数を取る。
サーバは、は自動的に終了しないので、実験が終わったら ^C (Control-C)で 殺す。
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/Makefile
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/ICounter.java
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/Counter.java
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/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 で止める)
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/Makefile
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/ICounter.java
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/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 サービスを利用する場合には、いくつかの方法が選べる。
% 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 で止める)
% 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の例。
Web サービスでは、WWW の技術を使ってアクセス制御を行うことができる。
別の Java 仮想計算機間オブジェクトのメソッドを呼び出す仕組み。 RMI は、いくつかの層を見えなくする。
比較的軽い。組込みシステムで使われているような遅めのコンピュータに向い ている。
src/java/rmi/Remote.java public interface Remote {}これを見つけると、コンパイラが特殊なコードを生成する。
java.lang.Object (class) | +--java.rmi.server.RemoteObject (class) | +--java.rmi.server.RemoteServer (class) | +--java.rmi.server.UnicastRemoteObject (class)これに加えて、interface Remote を implements する。
クライアント側は、これに比べて簡単。違いは、サーバに接続する部分部分と、 分散固有の例外を受ける部分。
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:
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:
オブジェクトは、serialize (直列化) されてコピーで渡される。
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 が使える。
port は、rmiregistry が使うポート番号で、デフォルトでは 1099。 大勢で1つのホストを使うとぶつかる。
string は単なる文字列。フラットな名前空間。
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: };
一方のウインドウで rmiregistry を起動する。ポート番号は、ぶつからない ように uid を使う。rmiregistry は自動的に終了しないので、実験が終わっ たら ^C (Control-C)で殺す。
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/Makefile
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/IRCounter.java
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/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-2016/2016-04-25/ex/Makefile
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/IRCounter.java
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/RCounter.java
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/RCounterServer.java
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/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 で止める)
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/Makefile
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/IRCounter.java
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/RCounter.java
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/RCounterClient.java
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/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
%
Java の標準のセキュリティのポリシー(java コマンドで利用される)は、 jre/lib/security/java.policy に記述されている。
System.setSecurityManager( new RMISecurityManager() ) した時には、 標準よりきつくなる。実験する時には、少し緩めないとつながらないことがある。
以下の例では、localhost と *.tsukuba.ac.jp からのアクセスを許している。
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 プロパティを使う。
RMI でオブジェクトを渡す時には、interface Serializable を implements する。
serialize 不要のフィールドには、transient をつける。
serialization は、RMI だけでなく、オブジェクトをファイルに落とす時にも 使える。
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
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-2016/2016-04-25/ex/Makefile
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/counter.rb
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/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 される。
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.
DRb.start_service() で、dRuby のサーバを起動している。その結、果新しい スレッドが作られる。このプログラムは、main() が終了してしまうと全体が終 了してしまうので、sleep() を呼んでmain() が終了しないようにしている。
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.
サーバは、は自動的に終了しないので、実験が終わったら ^C (Control-C)で 殺す。
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/Makefile
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/counter.rb
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/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 で止める)
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/ex/Makefile
% wget http://www.cs.tsukuba.ac.jp/~yas/cs/csys-2016/2016-04-25/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回実行しても、同じカウンタが使われている所
に注意する。
XML Webサービス のプログラムは、次の環境で動作する。 You can run the programs of XML Web Services in the following environments.
レポートには、次のものを含めなさい。 A report must include following.
締切りは、2016年5月1日、 23:59:59 とする。
Write a client program of public XML Web service in XMethods, Amazon Web Services, or other public XML Web services.
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.
ヒント: 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.