並行分散ソフトウェア/並列分散ソフトウェア
電子・情報工学系
新城 靖
<yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.cs.tsukuba.ac.jp/~yas/sie/pdsoft-2005/2006-01-13
あるいは、次のページから手繰っていくこともできます。
http://www.cs.tsukuba.ac.jp/~yas/sie/
http://www.cs.tsukuba.ac.jp/~yas/
- mandatory lock
- ロックに関する命令を実行していなくても、操作ができなくなる。
- advisory lock
- ロックに関する命令を実行している時だけ、ブロックされる。
講義で説明したものは、どれも、advisory lock である。いくら mutex を使っ
たり、synchronized をつけたとしても、付け忘れた部分があれば、ロックは
働かない。
読み書きロックでも、読み込みだけ行うスレッドも読み込みで advisory ロックを行う必要がある。
ロックを行わないでアクセスしてはいけない。
例:銀行口座が円とドルで2種類あった時に、合計の残高を知りたい。
通信プリミティブの分類
- 同期か非同期か。
- アドレス指定。送り先の指定がプロセスかメール・ボックスか。
- 信頼性があるかないか。
- 結合が作られる/作られない
- 順序が保存される/されない
- バッファリングあり/なし
- 単方向/双方向
- 受け手が1つ/複数(マルチキャスト)
クライアント・サーバ型の通信。
- 3つの基本命令
- get_request()
- send_reply()
- do_operation()
- クライアント・サーバ型の通信、RPCの障害への対応
- セマンティクスとトレードオフ
- idempotent冪等な操作
- 無状態サーバ
グループ通信(マルチキャスト)の実現
- 動的なグループ生成、メンバの追加への対応
- アトミックをどこまでやるかトレードオフ
- アドレス指定
資料:
谷口 秀夫 (編),
谷口 秀夫, 佐藤一朗, 佐藤 文明, 柴田 義孝, 新城 靖, 横山 和俊 (著):
"情報処理学会編集 IT Text 分散処理", オーム社 (2005年9月).
ISBN: 4274201333.
第2章 基盤技術 (新城)
2つの重要なパタン
- クライアント・サーバ
- グループ通信(マルチキャスト)
プロセス間通信の構造化。send, receive は、goto 相当。
分散システム: 離れていても心は1つ
ネットワークで接続された複数のコンピュータ上で複数のプロセスを動作させ
る。プロセスは、全体として1つの仕事を成し遂げる。
ノード:プロセス、あるいは、コンピュータ
メッセージ:ノード間で交換されるデータ
分散システムでは、次のような集中システムでは普通に使える共有資源が使えない。
- メモリ
- ファイル
- 名前空間
- イベント
- セキュリティ、主体、認証
分散共有メモリ、分散ファイルシステムが使える場合は
使う方法も検討してもよい。
メッセージを送信したり受信したりするライブラリ関数やシステム・コールを
通信プリミティブという。
最終的には、ハードウェア。
分散プログラムを記述する時には通信プリミティブを呼び出した段階まで考え
ればよい。以後、プログラミング言語の実行時システムやOSが考える。
通信の基本命令
- send()。メッセージを送信する。
- receive()。メッセージを受信する。
- 同期/非同期
- バッファリングあり/なし
- 信頼性があるかないか
- 順序が保存される/されない
- アドレス指定。送り先の指定がプロセスかメール・ボックスか
- 結合が作られる/作られない
- 単方向/双方向
- マルチキャストあり/なし
- 帯域保証あり/なし
信頼性性があるかないか。専門用語では、程度問題(高い低い)ではなく、有
るか無いか。
信頼性がある通信プリミティブを利用した場合、あるプロセスが送信したメッ
セージは、途中で失われることはなく、有限時間以内に通信相手のプロセスに
送り届けられる。
分散システムでは、メッセージは、失われることがある。
- 伝送媒体に雑音がのってメッセージが壊れる
- 混んでいるルータでメモリが足りなくなってメッセージが捨て
られる
-
信頼性に対する態度
- 失われたメッセージを再送するなどして、送り届けるように努力する。
- send() は、何も保証しない。
- send() は、何も保証しない。上のレベルでやる(client-server, RPC)。
- 結合が必要な通信プリミティブ
- 実際に send や receive でメッセージを送受
信する前に結合を確立させる手順を踏む。電話。
- 結合が作られない通信プリミティブ
- 送りたいデータをすぐに send することができる。郵便。
図? 同期型sendと非同期型send
- 直接。プロセスを指定する。
- 間接。メール・ボックス。複数のプロセスによって共有されることもされない
こともある。
ほとんどの通信プリミティブは、間接。
直接の例:
問題:直接アドレス指定では、receiveする前にsendされると、どのプロセス
にメッセージを送っていいのかわからない。
解決
- 待たせる、タイムアウト
- メールボックス(mailbox)。受信の宛先だけを通信システムに事前に登録。
- (送らせない。エラーにする。)
- 同期型(synchronous)(ブロッキング型(blocking))
- 非同期型(asynchronous)(非ブロッキング型(nonblocking))
図? 同期型sendと非同期型send
send,receive それぞれ2種類ある。
- 同期send
- 非同期send
- 同期receive
- 非同期receive。バッファの位置だけ先にわかる。完了は、
- ポーリング
- 割込みで教えてもらう
- 別途 wait() 命令を行う(同期)
- 条件receive
- select()
非同期の方が、並列性が高い。しかし、、、
- 転送完了までメッセージ・バッファを書き換えられない
- 送信側は、転送が終了したかわからない
1. は、バッファリングで解決可能。しかし、無駄なコピー、フロー制御の問
題がある。
2. は、転送完了割り込みで解決可能。プログラミングが難しい。
割り込みよりは、マルチスレッドがよい。
プログラミングの選択
- 同期送信プリミティブ(CPUが遊ぶ)
- バッファリング付きの非同期通信プリミティブ(コピーのオーバヘッド)
- 割り込みと非同期通信プリミティブ(プログラミングが難しい)
時間切れ(timeout)。同期で使われる。
コピーするかしないか。
同期、非同期とは直行しているとも言えるが、普通は同期の場合にはバッファ
リングを省略してコピーを減らす。
メールボックスには、しばしば有限のバッファがもうけられる。
バッファがいっぱいだった時にどうするか。
いっぱいの時にだけ送信側をブロックさせる、という方法でいいのか。
通信の分類
- one-to-many, multicast
- one-to-one, point-to-point, unicast
放送(broadcast)は、特定の(物理的な)ネットワークに属するプロセス「全部」に送る。
グループ通信は、いろいろな分散アルゴリズムでよく使われる。
帯域保証とは、たとえば、64k bps のように、毎秒、どのくらいデータ
を送ることができるかを保証することである。
普通の固定電話の場合、64k bps の帯域保証がなされている。電話を掛ける人
が増えてきたとしても、それが 32k bps に減らされるようなことはない。そ
の代りに、電話を掛けようとすると「通話中」というエラーが返り、通信その
ものが行えなくなる。
send してから receive するまで、最長どのくらいの時間がかかるかを保証す
ることである。電話の場合、ある程度の遅延保証は行っているとも言える。テ
レビの衛星中継のように、遅延が大きい媒体では、普通の会話はできなくなる。
単方向の通信しかできな
い場合でも、単方向の通信を2つ逆方向で組み合わせることもできる。
メッセージをsendした時にその通りの順序で receive できるかどうかという
意味である。結合が作られる場合は、普通は順序も保証される。
| TCP | IP | UDP | イーサネット | 電話 | 郵便 |
send | 非同期 | 非同期 | 非同期 | 非同期 | 非同期 | 非同期 |
receive | 同期 | (非同期)* | 同期 | (非同期)* | 同期 | 非同期 |
信頼性 | あり | なし | なし | なし | あり | なし |
アドレス指定 | 間接 | 間接 | 間接 | 間接 | 間接 | 直接 |
結合 | あり | なし | なし | なし | あり | なし |
方向 | 双方向 | 単方向 | 単方向 | 単方向 | 双方向 | 単方向 |
マルチキャスト | 不可 | 可能 | 可能 | 可能 | 可能 | 不可 |
帯域保証 | なし | なし | なし | なし | あり | なし |
* OS 内。
IPデータグラム(datagram)転送サービス
- 信頼性がない
- 単方向
- データの送り手と受けての間に結合(通信路)が形成されない
- 送出したデータの順番が途中で変ることや送出したデータが失われるこ
とがある。その場合、システムは何もしない。
- アドレス指定は、IPアドレス。IPv4 で 32ビット。IPv6 で 128ビット。
ストリーム転送サービス
- 信頼性のある(reliable)
- 双方向
- 2つのプロセス間に結合(connection,通信路)が形成される
- 複数回に分けて送り出したデータでも順番が入れ替わらない
- データの区切りは保存されない
- アドレス指定は、結合で指定。結合は、次の4つで区別される。
- 送り手のIPアドレス
- 送り手のポート番号
- 受け手のIPアドレス
- 受け手のポート番号
データの区切りが保存されると、sequenced packet と呼ばれる。
IP層は、転送サービスとして信頼性のないデータグラムを提供する。TCP
層の仕事は、それを使って、双方向のストリーム転送サービス提供することで
ある。そのために、「再転送付き肯定確認応答(positive acknowledgement
with retransmission)」という、一般的によく用いられている技術が使われ
ている。この技術では、データの受け手は、データを受け取る度に、送り手に
確認応答(acknowledgement, しばしば ack と省略される)を返す。データの
送り手は、確認応答を受け付けると、次のデータを送る。ある時間がたっても
確認応答が来なかった場合、データの送り手は、再転送(retransmit)する。
ストリームを実現する技術として、TCPは、スライディング・ウィンドウ
(sliding window)と呼ばれる技術を用いている。この技術では、ウィンドウと
呼ばれる範囲を設定して、確認応答が来る前に、次々とウィンドウ内のパケッ
トを送出す。確認応答を受け取ると、ウィンドウを「スライド」させ、次のパ
ケットを送り出す。
フロー制御
受け手のバッファ(メモリ)は、有限である。送り手は、受け手消費する速度
に合わせてパケットを送出さなければならない。このような制御をフロー制御
という。TCPでは、スライディング・ウィンドウを用いてフロー制御を行って
いる。
プログラム中のデータ項目とネットワーク上を流れるメッセージに対応づける。
- marshaling (整列化)
- メモリ中からデータ項目を集めて、ネットワークでメッセージとして
転送するのに適した形式にまとめる。
- unmarshaling (非整列化)
- 逆。
英語の綴りは、l が1つのものと2つのもの(イギリス綴り)がある。教科書によって違う。
図? marshalingとunmarshaling
4個の要素からなる構造体を整列化して送信している。
- 整数
- 文字列
- ビットマップデータ(可変長)のバイト数
- ビットマップデータ(可変長)の本体
整列化する基本的な方法
- 構造体や配列の要素を先頭から順にバッファに追加する。
- 可変長のデータの場合、まず、要素数を追加し、それに続いて本体を追加する。
ネットワークからデータを受け取ると、先頭から解釈して元のデータを再現す
る。
ネットワーク上を流れている時には、整列化された
データの先頭にはネットワークのヘッダが付加されている。
◆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 * を通じて、構造体の読み書き。
分散プログラムでは、メッセージを送信するプロセスと受信するプロセスが
異なる CPU で実行されることがある。
整数を送るだけでも、バ
イトオーダに気をつける必要がある。
C言語で扱える整数
- 1 バイト(char)
- 2バイト(short)
- 4バイト(long)
現在のコンピュータのほとんどは、バイト単位でアドレスを付けているので、
1バイトの整数については、バイトオーダの問題はない。
2バイト、または、4バイトの整数をメモリに保存する方法
: メモリの下位番地に上位バイトを置くか下位バイトを置くか
- リトルエンディアン
- 下位番地に下位バイトを置く。Pentium。
- ビッグエンディアン。
- 下位番地に上位バイトを置く。PowerPC (Macintosh) や SPARC
図? バイト・オーダー
- リトルエンディアンがよい
- 32ビットの整数のうち、下位 8 ビットや 16 ビットだけが必要な場合、番地の計算をし直す必要がない。
- 多倍長の整数を足し算したい場合は、下位からアクセスする
- ビッグエンディアンがよい
- 送信側、または、受信側で相手に合わせて変換する。バイトオーダが同
じ場合は何もしない。
- 標準的なバイトオーダ(ネットワーク・バイト・オーダ) を定める。送
信側では、ネットワークにデータを流す時には、常に自分自身のバイト・オー
ダ(ホスト・バイト・オーダ)をネットワーク・バイト・オーダに変換する。
受信側では、ネットワーク・バイト・オーダから自分のホスト・バイト・オー
ダに変換する。
現在、ネットワーク・バイト・オーダとしては、ビッグエンディアンが広く
使われている。
- TCP/IP の IP アドレスやポート番号
- XDR
名前 | 方向 | ビット数 |
htonl() | ホストからネットワークへ変換 | 32ビット |
htons() | ホストからネットワークへ変換 | 16ビット |
ntohl() | ネットワークからホストへ変換 | 32ビット |
ntohs() | ネットワークからホストへ変換 | 16ビット |
long int hostlong, netlong;
hostlong = 0x12345678 ;
netlong = htonl( hostlong );
send(conn, &netlong, sizeof(netlong), 0);
snprintf() で文字列に直して送り、strtol() や atoi() でもどす方法もある。
文字列文化。インターネットのアプリケーションでよく使われる。
思ったほど遅くはない。
注意:sscanf() は、整数をデコードするために使う分には問題ないが、
文字列を受け取るために使うとバッファ・オーバーフローが生じる可能性があるので、
使わない方がよい。
- 文字コードを合わせる。
- 複数バイトの場合、バイト・オーダも合わせる。
Unicode BOM (byte order mark) 0xffef。
- XML でタグ付けすると、値だけでなく、意味まで送れる。
- CSV (Comma Separated Values)
- S式
ソケットAPIは、TCP/IP をBSD 系 Unix に導入する時に設計された API であ
る。
今後 TCP/IP 以外にも様々な通信プロトコルが開発され、Unix で利用できる
ように設計されている。TCP/IP で使う時には、煩雑である。
ドメイン | 型 | プロトコル |
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) |
名前 | 説明 |
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() はファイルと共通。
名前 | 説明 |
gethostbyname() | ホスト名から IP アドレスを調べる。 |
getaddrinfo() | ホスト名から IP アドレスを調べる。IPv6対応。 |
gethostbyaddr() | IPアドレスからホスト名を調べる。 |
getnameinfo() | IPアドレスからホスト名を調べる。IPv6対応。 |
freeaddrinfo() | getaddrinfo(), getnameinfo() で得られた構造体を解放する。 |
関数名には、IP以外も考えた名前にして欲しかった。
Java言語は、基本的に TCP/IP と UDP/IP しかサポートしていない。したがっ
て、TCP/IP や UDP/IP のプログラムを作成する場合には、分かりやすくなっ
ている。
TCP/IP では、クラ
イアント側とサーバ側でソケット・オブジェクトの作成するクラスが違ってい
る。
クラス名 | 説明 |
Socket | TCP/IP のクライアント側のソケット |
ServerSocket | TCP/IP のサーバ側のソケット |
DatagramSocket | UDP/IP のソケット |
Java でも、実際の通信には、ファイルと同じ API を用いる。
例:
Socket クラスのオブジェクトに対して getInputStream() というメソッドを実行する
と、InputStream クラスのオブジェクトが返される。
InputStream は、ファイルからの入力と共通。
以後、ネットワークか
ら文字列を入力するには、InputStreamReader や BufferedReader のオブジェ
クトを生成して利用する。
出力側では、Socket クラスのオブジェクトに対して getOutputStream() して、
OutputStream クラスのオブジェクトを得て、
PrintStream オブジェクトを生成して利用できる。
手続き呼出しの形に見えたら RPC (Remote Procedure Call)。
通信を構造化。send()/receive() を直接使うのは、goto (jump) でプログラ
ムを書くようなもの。call/if/while で書きたい。
プロセスを2種類に分類する。通信は、次のパタンを繰り返す。
- クライアント
- 先にメッセージ(要求)を send() 1回、後でメッセージ(応答)を receive() 1回
- サーバ
- 先にメッセージ(要求)を receive() 1回、後でメッセージ(応答)を send() 1回
send() の回数と receive() の回数は同じ。相互に繰り返す。

図? 通信のパタンからみたクライアントとサーバの定義
混沌とした通信を「構造化」してわかりやすくする。

図? 構造化されていないもの

図? 構造化されたもの
構造化プログラミング:分かりにくいgoto文をつかわないで、わかりやすい
goto文だけ使う。
元々の意味
- クライアント(client)
- サービスを受ける方、顧客
- サーバ(server)
- サービス(service)を提供する方

図? サービスの授受によるクライアントとサーバの定義
サービスを提供する方は、1つのプログラム(コンピュータ)で複数の利用者
の面倒をみる。その結果、1台のサーバに複数のクライアントがつながる。
- クライアント
- 一人で使うもの
- サーバ
- 複数人で共有するもの

図? 複数のクライアントによるサーバの共有
TCP/IP の通信では、通信を始める前に、まず、通信路を作る作る必要がある。
これは、電話で話をする前に、まず、電話をかける操作を行うことと似ている。
- クライアント
- 電話を掛ける方に相当する
- サーバ
- 電話を待っている方
以上のように、クライアントとサーバは、いろいろな意味で使われる。これら
の意味は、多くの場合、一致しているが、一致していないこともある。
通信を開始するパタンで、コンピュータ、プログラム、人間は、次の2つに分
類される。
- 能動的(active)
- ほっといても自分でメッセージを発信し始める
- 受動的(passive)、受け身
- 何か言われると答えるが、自分ではメッセージを発信し始めることはない
クライアントとサーバから作られたシステムは、クライアントが能動的になり、
サーバは、受動的になることが多い。

図? 能動的なクライアントと受動的なサーバ
例:WWWサーバは、WWWクライアントから何か要求が来ない限り、ずっと
黙っている。
コンピュータを使う時には、人間が能動的になり、コンピュータが受動的にな
る。
テレビを見ている時には、人間が受動的になり、テレビが能動的になる。
講義形式の授業では、サービスの授受では、教官がサーバで、学生がクライア
ントになる。通信の開始の方法では、教官が能動的になり、学生が受動的にな
る。
大学以上では、学生は、能動的になることが求められている。
P2P (Peer to Peer) という用語の意味は、怪しい。
混沌とした通信を
構造化
してわかりやすくしたものが、クライアント・サーバ・モデルである。
サーバあるシステムでは、サーバが落ちるとシステム全体が動作しなくなる。
このように複数の要素から構成されているシステムで、ある要素が故障した時
に、全体が動作しなくなるような場所を、単一障害個所(single point of
failure) という。
コンピュータサイエンスでは、古くから、単一障害個所を避けるための研究が
行われてきている。もっとも成功している方法は、サーバを複数用意する方法
である。
サーバがないシステムでは、下手に作るとどの要素が故障してもシステム全体
が止まってしまうことになる。
サーバがないシステムで成功している例はある。
- インターネットの基幹のルータ。(IPv4 の BGP (Border Gateway Protocol) は、スケーラビリティ的に厳しい所には来ている。)
- ニュースシステム
peer は、「対等の仲間」の意味。「通信相手」という意味もある。
検索は、サーバで索引を集めた方が速い。Web 上の検索エンジンなど。
サーバがない方法の利点(特徴)
- うまく作れば、単一障害点がなくなる。
- サーバを維持するコストが不用である。
- サーバを経由しないで通信が行われると、サーバの負荷が減る。
- 無政府的で面白い。
サーバがない方法の問題点
- 下手に作ると、どの要素が故障しても全体が止る
(single point of failure はないが、multiple points of failure になる)
- 検索などは遅い
- 責任の所在が不明になる
- 通信相手が本物かどうか確かめるのがたいへん
Napster
- 1999年起業。
- サーバでは、音楽ファイルの索引を置く。
- 音楽データの交換は、サーバを経由することなく、個々のプログラム間で
行われる。
- 2000年、全米レコード協会が提訴
- 2001年、Napster 敗訴。
- その後、別の会社買収し、ブランド名だけ残る。
今は、独自の著作権管理技術を使っている。
Napster は、学問的には、特に目立った新技術はない。
- get_request(port_t serviceport,message_t request/*in*/): サーバ・プロセスにより使われる。クライアント・プロ
セスから送られてくる要求メッセージを待つ。
- send_reply(port_t client,message_t reply): サーバ・プロセスにより使われる。クライアント・プロ
セスに対して応答メッセージを送る。
- do_operation(port_t server,message_t request/*out*/, message_t reply/*in*/): クライアント・プロセスにより使われる。サーバへ要
求を送り、サーバからの応答が返ってくるまで待つ。
サーバは、普通、get_request() で、クライアントから何か要求メッセージが
届くのを待っている。
クライアントが何かサーバにして欲しい時には、do_operation() で、サーバ
に要求メッセージを送る。そして、サーバからの応答メッセージを待つ。
サーバは、要求メッセージを受けとると、何か仕事をして、send_reply() で、
クライアントに応答メッセージを返す。そして、再び次の要求メッセージ
を待つ。
1つのサーバに複数のクライアントがアクセスすることがある。1つのクライ
アントも、複数のサーバに同時にアクセスすることもできる。

図? クライアント・サーバ・モデルに基づく通信で使う3つの命令
例1:7種類のメッセージを使う方法
- REQ
- REP
- ACK
- AYA (Are You Alive) -- 忙しいのか死んでいるか
- IAA (I Am Alive)
- TA (Try Again) -- receive() が呼び出されていない
- AU (Address Unknown)
例2:3種類(回数)
- Request
- Request-Reply
- Request-Reply-Acknowledge
例3:2種類(回数)
RPC (Remote Procedure Call)
(
遠隔手続き呼び出し
)
は、分散システムを構築する時に広く使われている「プロセス間通信」の方法。
NFS (Network File System) を始めとする分散ファイル・システムの構築や、
や
NIS (Network Information Service) (パスワード・ファイルなどの共有)
で使われている。
(<−>文化的には、TCP/IP上のテキストベースのプロトコルとは対照。)
TCP/IP で提供されているストリームや UDP/IP で提供されているデー
タグラムと比較して、RPC には次のような特徴がある。
- プロセス間通信におけるクライアント・サーバ・モデルに基づいている。
(TCP/IPやUDP/IPでも上位プロトコルではクライアント・サーバ・モデルを使っ
ているのがほとんど。)
- 普通の手続き呼出し(C言語では関数呼出し)と似た方法で通信を行うこ
とができる。
- 結合(connection)が作られない。(UDP/IP のデータグラムと同じ。)
- 同期式の通信を提供する。
- 双方向の通信プリミティブである。(TCP/IPと同じ。UDP/IPでも双方向が普通。)
RPCで「遠隔(remote)」というのは、もともとはネットワークに接続された別
のコンピュータという意味であったが、最近では、同じコンピュータの内部で
もRPCが使われる。「遠隔(remote)」とは、「別のコンピュータ」という意味
ではなく、「別のアドレス空間」という意味もある。
RPCでは、別のアドレス空間の間でデータがやり取りされるので、基本的に
「ポインタ」を受け渡しすることはできない。しかし、SunRPC では、ポイン
タの先を再帰的に「コピー」する機能がある。
下位層では、上であげた3つの基本命令が使われている。
このRPCの3つの命令は、システムコール、または、ライブラリで実現される。
SunRPCには、get_request() に相当する命令が定義されていない。
RPCでプログラムを作成するときには、3つの基本命令を利用することは、ほ
とんどない。
スタブ生成器(stub generator)
を使えば、インタフェースの定義から3つの基本命令を呼び出すようなプログ
ラムが自動生成される(lex、yacc )。
スタブ(stub)
は、プロセス間通信を普通の手続き呼出しと全く同じ形式で行なうことができ
るようにするためのプログラム。
もともとは木の切株の意味。

図? スタブによる遠隔手続き呼出しの実現
スタブの分類
- クライアント側スタブ
- 手続き呼出しの形で呼び出される。do_operation() 命令を実行する。
- サーバ側スタブ
- 無限ループを持つプログラム。 get_request() でクライアントからのメッ
セージを受け取り、対応する手続きを呼び出し、結果をsend_reply() により
返す。
スタブでは、パラメタ(引数と結果)の
整列化(marshalling)
(
パック
)
と
非整列化(unmarshalling)
(
アンパック
)
も行なわれる。SunRPC では、
XDR (eXternal Data Representation)
と呼ばれている方法を使っている。
SunRPC には、rpcgen というスタブ・コンパイラがある。
rpcgen コマンドを使うには、次のようなファイルを作成する。

図? rpcgenによるRPCプログラム開発で利用するファイル
-
name.x
-
インタフェースを記述。
-
name_client.c
-
クライアント側の main プログラム。
-
name_server.c
-
サーバ側で、RPC で呼び出されるプログラム。
(main は、rpcgen により自動生成される。)
◆rpcgenコマンドの使い方
% rpcgen name.x
次の4つのファイルが生成される。
-
name.h
-
そのRPCのプログラムで使う定数、データ構造、スタブ手続きのインタフェー
ス。
-
name_clnt.c
-
クライアント側のスタブ。
-
name_xdr.c
-
name.x で定義したデータ構造について、
XDR
のための手続き(整列化と非整列化を行なう手続き) 。
クライアント側とサーバ側の両方で使われる。
-
name_svc.c
-
サーバ側の main 関数とディスパッチ手続き。受け付けた RPC の要求を解析
して、開発者が定義した手続きを呼び出す。
これらのファイルの内容は、人間が十分読めるも。
◆rpcgenのインタフェース定義の例
ハッシュ表
- int put(key_t key,int value)
- int getvalue(key_t key)
- keyarray_t getkeys(void)
typedef char *key_t;
typedef key_t *keyarray_t;
typedef string key_t<256>;
struct keyvalue_t {
key_t key;
int value ;
};
typedef key_t keyarray_t<>;
program HASHTABLE_PROG {
version HASHTABLE_VERSION {
int PUT(keyvalue_t) = 11 ;
int GETVALUE(key_t) = 12 ;
keyarray_t GETKEYS(void) = 13 ;
} = 1 ;
} = 0x20051001 ;
遠隔手続き呼出しでは、送受信できるデータは基本的には値だけであり、
ポインタを送ることはできない。
SunRPC では、ポインタの先の1要素だけコピーして送る機能がある。SunRPC
では、ポインタによる単純なリストや木構造を送ることができる。
双方向リストなど、内部にループを含むものは SunRPC では送ることができな
い。また、ポインタで実現された有向非循環グラフを送ると木構造に展開され
てしまう。
クライアントとサーバを結び付ける。
RPC では、動的(dynamic)になる。
ローカルの手続き呼出しでは、リンク時に固定される。
クライアントとサーバは1対1ではない。
- 1つのサーバは、複数のクライアントにサービスを提供するのが普通である。
- 1つのクライアントは、利用可能な複数のサーバの中から
選択して利用することが多い(同時に複数のサーバを使うことは少ない)。
binding のための命令
- 登録、export (サーバ側)
- 削除 (サーバ側)
- 検索 (クライアント側)
SunRPCでは、多くの場合、TCP/IPやUDP/IPを使ってメッセージを送る。この場
合、手続きをを次の5つの番号で区別する。
- サーバのアドレス(IPアドレス)
- プロトコル。TCP か UDP。
- プログラム番号。SunRPCで独自に定義している32ビットの整数。
- バージョン。各RPCのプログラムで定義。
- 手続き番号。各RPCのプログラムで定義。
広く使われるプログラム番号は、
/etc/rpc
というファイルに含まれている。
TCP/IPまたはUDP/IPでデータを送るにはポート番号が必要になる。
サーバが動作しているホストには、
portmapper とよばれる特殊な RPC のサーバが動作している。
サーバは起動時に自分の<プログラム番号, バージョン,
プロトコル, ポート番号>をPortmapper に登録する
(pmap_set())。
クライアントは、実際にサーバに接続する前に、Portmapper に<プ
ログラム番号, バージョン, プロトコル>を送り、
TCP/IPまたはUDP/IPのポート番号を得る(pmap_getport())。最終的には, この
ポート番号を使ってメッセージを送る。
Portmapper 自身のポート番号は、111 に固定されている。

図? SunRPC での binding (portmapper)
(RPCを実現する上での問題)
- サーバが応答しなかったらどうするか
- 重複する要求の処理
- 応答が失われた時
- メッセージの削減。1つの要求・応答メッセージを複数のデータグラムで運ぶ
- クライアントがサーバを見つけられない
- 要求メッセージが紛失
- 応答メッセージが紛失
- 要求受信後、サーバがクラッシュ
- 要求送信後、クライアントがクラッシュ
時間切れ再要求。簡単。
難しい。単純な時間切れだとまずい。
例: 銀行預金転送。
対策:
- 操作を idempotent にする。
- クライアントから最後のメッセージ番号を保持して、再要求を拒否する。
- 再要求を示すフラグを付ける。
番号では対応できない。要求実行前と実行後を区別できない。
RPCのセマンティクス
- exactly once semantics。実現不可能。
- at least once semantis。応答するまで要求し続ける。
- at most once semantics。失敗したらあきらめる。
- なにもしない。
集中だと、クライアントもサーバもいっしょに死ぬので、問題はない。
孤児問題(orphan problem)。
親(クライアント)がいない計算を孤児という。
対応方法
- 根絶(extermination)。
- RPC 前にログに書く。クラッシュしたらログを見て根絶する。重たい。
孫の孤児(grandorphan)問題がある。
- 再生(reincarnation)
- リブートすると、タイムスタンプをサーバに投げる。サーバは、
全孤児を消去する。
- 穏和な再生(gentle reincarnation)
- タイムスタンプを受信すると、サーバは親を探し、見つからない時だけ孤児を消去する。
- 期限切れ(expiration)
- RPC に標準時間Tを与え、その時間内に終了しないものを消去する。
それ以上かかる時には、明示的に要求する。クラッシュ後、サーバがTだけ待てば
孤児は消える。
孤児が、ロックを持っていたら、孤児を消しただけでは話を終わらない。
冪等(idempotent)な操作とは、
その操作を何回繰り返しても、1回だけ実行した時と同じ結果になるもの。
例:
- 足し算。関数でもある(値の書換えがない、引数だけで結果が決まる)。
- 位置を指定したファイルの読み込み(pread())
- 位置を指定したファイルの書き込み(pwrite())
idempotentではない操作
- 変数を1つ増やす
- 位置を指定しないファイルの読み込み(Unix stream風read())
- 位置を指定しないファイルの書き込み(Unix stream風write())
無状態サーバ(stateless server)
とは、サーバ内部に状態を保持しないようなサーバ。
状態の例
- Unix のカーネル内部で持っているファイルを読み書きする位置(シー
ク・ポインタ)
RPCで冪等な操作や無状態サーバを実現すると、クラッシュに強いシステムを
作れる。クライアントは、サーバから応答がない場合、何度要求を再送信して
もよい。
例:NFS Version 2
サーバは、ファイルに対する書き込み要求を受け付けると、ディスクへ
の書き込みを完了してから応答を返す。
応答が返ってきた要求は、
ディスクへの書き込みが完了したことが保証されている。
この段階でサーバがクラッシュしたとしても、なにも失われない。
しかし、、、重たい。NFS Version 3 では、状態付きのサーバになった。
表? NFSで使われているRPCの手続き
手続き名 | 意味 | 関連するコマンド、 システムコール |
null() | 何もしない | rpcinfo -u hostname nfs コマンド |
getattr() | 属性の読み出し | ls -l コマンド, stat システムコール , open システムコール |
setattr() | 属性の設定 | chmod , chown コマンド |
lookup() | ファイルの検索 | open システムコール |
readlink() | シンボリックリンクの読み出し | ls -l コマンド, readlink システムコール |
read() | ファイルの読み出し | read システムコール |
write() | ファイルの書き込み | write システムコール |
create() | ファイルの作成 | creat システムコール, open システムコール |
remove() | ハードリンクの削除 | rm コマンド, unlink システムコール |
rename() | ファイル名前の変更 | mv コマンド, rename システムコール |
link() | ハードリンクの作成 | ln コマンド, link システムコール |
symlink() | シンボリックリンクの作成 | ln -s コマンド, symlink システムコール |
mkdir() | ディレクトリの作成 | mkdir コマンド |
rmdir() | ディレクトリの削除 | rmdir コマンド |
readdir() | ディレクトリの読み出し | ls コマンド |
statfs() | ファイルシステムの利用状況 | df コマンド, statfs システムコール |
commit()* | ディスクへの書き込み | fsync システムコール |
access()* | アクセス権のチェック | access システムコール |
* は、NFS Version 3 の新しい手続き。
RPC のようにコネクションが作られない
通信サービスを使う時に
冪等や無状態といった性質を実現する時に必要になる技術。
例:NFSでのディレクトリの読み込み手続き nfsproc_readdir() で、1回の
RPC で全部のデータを返せないことが起きる。
ディレクトリのどの位置まで読み込んだかを
示す中間状態を
クッキー(cookie)
という形でクライアントに返す。
クライアントは、次の RPC の呼び出しで、
前回受けとった応答の中のクッキーを、サーバへの要求に含めて送す。
/usr/include/rpcsvc/nfs_prot.x:
const NFS_COOKIESIZE = 4;
typedef opaque nfscookie[NFS_COOKIESIZE];
/*
* Arguments to readdir
*/
struct readdirargs {
nfs_fh dir; /* directory handle */
nfscookie cookie;
unsigned count; /* number of directory bytes to read */
};
struct entry {
unsigned fileid;
filename name;
nfscookie cookie;
entry *nextentry;
};
struct dirlist {
entry *entries;
bool eof;
};
union readdirres switch (nfsstat status) {
case NFS_OK:
dirlist reply;
default:
void;
};
nfsproc_readdir() で、1回目と2回目の RPC の間にディレクトリの内容が
更新された場合、どのような結果になるのか不明。
HTTP では、
TCP/IP というコネクションが作られる通信サービスが使われいるが、
1ページ1ページを転送する度にコネクションを切っているので、
複数ページ
のアクセスの時にはコネクションが作られない通信サービスを使っているのと
論理的に同じ。
途中経過を保存したい時:
- パスワードを打って利用者を確認した後
- 買い物かごの中身
- IPアドレスが毎回違うような場合、前回アクセスしてきた人を確認する
WWWで途中経過を保存するためには、cookie が使われる。
- WWWサーバが、最初にアクセスした時に利用者ごとにクッキーを生成
し、ブラウザに返す。
- ブラウザは、返されたクッキーをファイルに保存しておく。
- ブラウザは、次に同じサーバに要求を送る時に、ファイルに保存してあ
るクッキーを読み出して要求とともにサーバに送る。
- 要求を受け取ったサーバは、要求に含まれているクッキーから、そのブ
ラウザを使っていた人が前回どのページを訪れたかを知ることができる。
サーバは、その情報を利用して、適切なページ(たとえば前回最後に訪れたペー
ジ)を表示させるようにすることができる。
User Agent は、WWW ブラウザと思ってよい。
4. EXAMPLES
4.1 Example 1
Most detail of request and response headers has been omitted. Assume
the user agent has no stored cookies.
1. User Agent -> Server
POST /acme/login HTTP/1.1
[form data]
User identifies self via a form.
2. Server -> User Agent
HTTP/1.1 200 OK
Set-Cookie2: Customer="WILE_E_COYOTE"; Version="1"; Path="/acme"
Cookie reflects user's identity.
3. User Agent -> Server
POST /acme/pickitem HTTP/1.1
Cookie: $Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"
[form data]
User selects an item for "shopping basket".
4. Server -> User Agent
HTTP/1.1 200 OK
Set-Cookie2: Part_Number="Rocket_Launcher_0001"; Version="1";
Path="/acme"
Shopping basket contains an item.
5. User Agent -> Server
POST /acme/shipping HTTP/1.1
Cookie: $Version="1";
Customer="WILE_E_COYOTE"; $Path="/acme";
Part_Number="Rocket_Launcher_0001"; $Path="/acme"
[form data]
User selects shipping method from form.
6. Server -> User Agent
HTTP/1.1 200 OK
Set-Cookie2: Shipping="FedEx"; Version="1"; Path="/acme"
New cookie reflects shipping method.
7. User Agent -> Server
POST /acme/process HTTP/1.1
Cookie: $Version="1";
Customer="WILE_E_COYOTE"; $Path="/acme";
Part_Number="Rocket_Launcher_0001"; $Path="/acme";
Shipping="FedEx"; $Path="/acme"
[form data]
User chooses to process order.
8. Server -> User Agent
HTTP/1.1 200 OK
Transaction is complete.
The user agent makes a series of requests on the origin server, after
each of which it receives a new cookie. All the cookies have the
same Path attribute and (default) domain. Because the request-URIs
all path-match /acme, the Path attribute of each cookie, each request
contains all the cookies received so far.
現在の Cookie の実現では、利用者のプライバシーを犯す危険性が高いという
問題が指摘されている。
普通のWWWサーバでは、要求を送ってきたコンピュータのIPアドレスを記
録しているので、コンピュータ単位でのアクセス状況を記録することはできる
が、個人を特定することはできない。
クッキーを利用することにより、コンピュータではなくどの個人がアクセスし
てきたかを記録することができる。
クッキーから電子メールのアドレスや氏名まで調べることはできない。
しかし、インターネットをサーフしている間にどこかでそれを打ち込んだが最
後、クッキーと電子メール・アドレスや氏名との対応が記録されてしまう危険
性がある。
参考
RFC2965 HTTP State Management Mechanism
Netscape社によるWWWにおけるクッキー実現の案
http://wp.netscape.com/newsref/std/cookie_spec.html
HTTP では、
TCP/IP というコネクションが作られる通信サービスが使われいるが、
1ページ1ページを転送する度にコネクションを切っているので、
複数ページ
のアクセスの時にはコネクションが作られない通信サービスを使っているのと
論理的に同じ。
HTTP でも、クライアントの状態をサーバ側で保存するためにクッキーが使わ
れることがある。
グループ通信で何が難しいか
- グループが動的
- 使えるハードウェア。
下のネットワークで放送やマルチキャストがサポートされているか。
なければ、unicast で実現される。
- バッファリング。再転送のためのコピーをいつまで持つか。
- atomicuty。all or nothing。望ましいが、難しい。
- クローズ/オープン。グループ外から送れるか。
- 同等/階層(コーディネータ付き)
並列処理では、クローズが多い。
IP MBone のように、オープンでないとどうしようもないものもある。
対等だと、クラッシュに強い(single point of failure がない)が、何をす
るにもすぐ投票が必要になる。
グループサーバを持つか持たないか。
難しいのは、メンバのプロセスがクラッシュした時。
何も言わずにグループから抜ける。
- プロセスの名前付けと同じように、1つのアドレスで指定する(直接、間接)
- 個々のアドレスのリストを使う。メンバの管理を呼出し側でやる。
- 述語アドレス指定。全部に送られるが、メッセージに含まれている述語が真の時だけ受け取られる。
one-to-oneの通信と同じにしたい。しかし、
- RPC の意味。SunRPC の放送型RPC。
- atomicity
- メッセージの順番
障害がなければ、簡単。
- 送信プロセスが、下位層の放送機能などを使って
グループ内のプロセスに
メッセージを送る。(メッセージは落ちるかもしれない。)
タイマを設定し、必要に応じて再転送する。
- 各プロセスは、メッセージを受け取ると、始めて受け取ったものならば、
グループの他のプロセスに送る。タイマを設定し、必要に応じて再転送する。
一度受け取ったものなら、なにもしない。
one-to-one でも問題があるのに、group 通信だとさらに複雑。
total ordering は、グループ通信では実現が難しすぎる。弱いものが実現さ
れる。
プロセスが複数のグループに属していると、1つのメッセージについての順序
だけ考えていてはすまなくなる。
数が増えた時に動くか。
ネットワークのダウンに対応するには、ネットワークを冗長に
しないといけない。冗長性があると、メッセージの重複を
うまく扱う必要がでてくる。
下手をすると、メッセージが増幅しながいつまでもぐるぐる回る。
分散アプリケーションのためのツールキット。
コーネル大学。
- 同期システム(synchronous system)
-
イベント(マルチキャスト)の順序が厳密に逐次的に発生する。オーバーラップしない。
イベント(マルチキャストを含む)は、完了までの時間は、0。
実現不可能。
- 緩やかな同期システム(loosly synchronous system)
- イベントは有限時間で届く。
- 仮想的な同期システム(virtually synchronous system)
- 因果関係が成り立つようにがんばる。(並行なものは、手抜き。)
- ABCAST
- 緩やかな同期
- CBCAST
- 仮想的な同期
- GBCAST
- 仮想的な同期(グループのメンバシップ用)
最初の実現:2相コミットによる ABCAST 。重すぎる。
- 送信者は、タイムスタンプを含むメッセージを全てのメンバに送る。
- 各メンバは、送信、または、受信したメッセージで最大のものを最初の送信者に送る。
- 送信者は、全ての返事を受け取ると、最大のものを選び、メンバにコミット・メッセージを送る。コミット・メッセージは、タイムスタンプの順に届けられる。
CBCASTの実現
メンバの数:n
各プロセスは、グループごとに、長さnのベクトルVを持つ。
i番目の要素は、プロセスiから正しい順序で受信したメッセージの最後の番号。
0に初期化される。
- プロセスは、送信すべきメッセージがあると、
ベクトルの自分のスロットを増加させ、メッセージの一部として送信する。
- メッセージのベクトルをV、メモリ中のベクトルをLとする。
メッセージがjから送られてきたものとすると、次の条件の時に受け取る。
そうでないものは、この条件が満たされるまでバッファリングされる。

図? ISISでのCBCASTの実現。
マルチキャストの実装。
- atomic ではない。
- 因果律も満たさない。
- 非常に高いスケーラビリティを持つ。
- 冗長性が実現できる。
↑[もどる]
←[12月22日]
・[1月13日]
→[1月20日]
Last updated: 2006/01/18 19:08:03
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>