プロセス間通信、マーシャリング、クライアントサーバモデル、遠隔手続き呼び出し

並行システム

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

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

■今日の重要な話

■プロセス間通信

2つの重要なパタン プロセス間通信の構造化。send, receive は、goto 相当。

◆プロセスとメッセージ

分散システム: 離れていても心は1つ

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

ノード:プロセス、あるいは、コンピュータ

メッセージ:ノード間で交換されるデータ

分散システムでは、次のような集中システムでは普通に使える共有資源が使えない。

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

◆通信プリミティブ

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

◆sendとreceive

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

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

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

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

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

分散システムでは、メッセージは、失われることがある。

信頼性に対する態度

◆結合(コネクション)が作られるか結合が作られないか

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

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

図? 結合が作られる通信プリミティブと結合が作られない通信プリミティブ

◆接続の確立と送受信

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

着信側

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

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

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

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

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

◆接続を用いない通信

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

プロセスA

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

◆アドレス指定

ほとんどの通信プリミティブは、間接。

直接の例:

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

解決

◆同期型/非同期

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

図? 同期型sendと非同期型send

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

非同期の方が、並列性が高い。しかし、、、

  1. 転送完了までメッセージ・バッファを書き換えられない
  2. 送信側は、転送が終了したかわからない
1. は、バッファリングで解決可能。しかし、無駄なコピー、フロー制御の問 題がある。

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

プログラミングの選択

時間切れ(timeout)。同期で使われる。

◆バッファリング

コピーするかしないか。

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

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

バッファがいっぱいだった時にどうするか。 いっぱいの時にだけ送信側をブロックさせる、という方法でいいのか。

◆帯域保証

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

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

◆遅延保証

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

◆単方向/双方向

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

◆メッセージの順序の保証

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

◆TCP/IP

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

◆IP

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

◆TCP

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

◆UDP

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

◆どの通信プリミティブを使うか

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

◆まとめ

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

■marshaling/unmarshaling

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

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

図? marshalingとunmarshaling

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

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

◆整数のmarshaling

分散プログラムでは、メッセージを送信するプロセスと受信するプロセスが異 なる CPU で実行されることがある。整数をmarshalingする時には、次のような 点を考慮する必要がある。

◆バイト・オーダ

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

C言語で扱える整数

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

2バイト、または、4バイトの整数をメモリに保存する方法 : メモリの下位番地に上位バイトを置くか下位バイトを置くか

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

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

図? バイト・オーダー

◆ビッグエンディアンとリトルエンディアンの比較

◆送り方

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

◆バイトオーダを変換するライブラリ関数

名前 方向 ビット数
uint32_t htonl(uint32_t hostlong) ホストからネットワークへ変換 32ビット
uint16_t htons(uint16_t hostshort) ホストからネットワークへ変換 16ビット
uint32_t ntohl(uint32_t netlong) ネットワークからホストへ変換 32ビット
uint16_t ntohs(uint16_t netshort) ネットワークからホストへ変換 16ビット

◆htonl() を使った整数の送信

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

◆snprintf()/strtol()

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

送信側:

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

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

◆文字列のmarshaling

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 * を通じて、構造体の読み書き。

◆その他

■クライアント・サーバ・モデル

手続き呼出しの形に見えたら RPC (Remote Procedure Call)。

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

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

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

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

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

◆クライアントとサーバに分けて考える意義

混沌とした通信を「構造化」してわかりやすくする。

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

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

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

図? 構造化されたもの

構造化プログラミング:分かりにくいgoto文をつかわないで、わかりやすい goto文だけ使う。

◆サービスの授受

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

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

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

◆利用者数

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

クライアント
一人で使うもの
サーバ
複数人で共有するもの

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

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

◆接続方法

TCP/IP の通信では、通信を始める前に、まず、通信路を作る作る必要がある。 これは、電話で話をする前に、まず、電話をかける操作を行うことと似ている。
クライアント
電話を掛ける方に相当する
サーバ
電話を待っている方

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

◆能動的・受動的

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

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

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

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

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

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

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

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

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

◆Peer to Peer (P2P)

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

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

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

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

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

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

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

検索は、サーバで索引を集めた方が速い。Web 上の検索エンジンなど。

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

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

◆RPC

クライアント・サーバ・モデルに基づくプロセス間通信で、 手続き呼出しの形に見えたら RPC (Remote Procedure Call) ( 遠隔手続き呼び出し )

■Socket API

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

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

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

    int socket = socket(int domain, int type, int 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)

◆ソケットAPIの主要なシステムコール、または、ライブラリ関数

名前 説明
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 アドレスを扱う関数

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

◆講義用TCP Librar(講義用のTCP接続を作成するためのライブラリ)

情報科学類の講義、 システムプログラムで 新城が例題を示すために作成した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

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)

手続き呼出しの形でプロセス間通信を行う方法。 1984年 Birrel and Nelson。

◆スタブによるRPCの実現

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

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

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

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

◆スタブの作り方

◆スタブの自動生成

インタフェース記述の例: ハッシュ表 key_t は文字列。 SunPRC での記述方法
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 ;
インタフェース定義をスタブ生成器に与えると、クライアント側スタブ、サー バ側スタブが自動的に生成される。

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

自動生成されるもの

◆遠隔手続き呼び出しのまとめ

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

◆遠隔手続き呼び出しと通常の手続き呼出しの違い

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

◆通常の手続き呼出し

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

言語が持つ意味を、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() の型宣言を見ないと、変化するか変化しないかわからな い。「型宣言を見る」という手間の分だけ、プログラムが読みにくくなる。 全部の関数について、見ていたら疲れる。

◆ポインタ

「参照」は、ポインタに似ている。ただし、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 の値が変化することが推察される。値が変化する 可能性について、ポインタを渡している関数だけ注意すればよい。

◆配列

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

◆マクロ展開

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でread()

例: ファイルの内容を読むシステム・コール 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 を整列化する。
	サーバへ要求メッセージとして送る。
	サーバから応答メッセージを受け取る。
	応答メッセージを非整列化して、結果の read_bytes と読んだ内容を取り出す。
	読んだ内容を buf read_bytes 分へコピーする。
	return read_bytes ;
}
単純に行えば、上のように余計なコピーが入ることがある。 スタブで最適化すれば、クライアントからサーバへのコピーを減らせる。

◆SunRPCでのポインタの扱い

SunRPC では、ポインタの先の1要素だけコピーして送る機能がある。 この機能を使って遅れるもの。 送れないもの

◆バインディング

バインディングとはクライアントとサーバの対応関係を、個々 の手続き呼出しの前に決定すること。

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

図? RPCのバインディング

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

◆サーバの複製(replication)

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

◆RPCの実装例

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

◆RPCの利点

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

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

■練習問題4 プロセス間通信、マーシャリング、クライアントサーバモデル、遠隔手続き呼び出し

欠席した人は、後日、以下のクイズの回答をレポートとして提出しなさい。

★問題(401) プロセス間通信としての遠隔手続き呼び出し

遠隔手続き呼び出しは、プロセス間通信として見た時にどのような性質がある か。以下の項目を答えない。

★問題(402) marshaling

htonl() を使った整数の送信では、バイ ト・オーダを考慮しながら、32ビットの整数(4バイトの整数, long型)を送信す るプログラムの一部をしめした。これに対して、 次のプログラムは、バイト・オーダを考慮しながら、32ビットの整数(4バ イトの整数, long型)を受け取るプログラムの一部である。空欄を埋めて、プ ログラムを完成させなさい。ただし、conn は、TCP/IPにより実現されたスト リームを指定するファイル記述子、receive() は、データを受信するシステム・ コールである。
unsigned long int hostlong, netlong;    
receive(conn, /*(A)*/, /*(B)*/, 0);
/*(C)*/ = /*(D)*/ ;
printf("%d\n",hostlong ); // 受け取ったデータの表示。

★問題(403) クライアント・サーバ・モデルと遠隔手続き呼び出し

クライアント・サーバ・モデルに基づき分散プログラムを記述することを考え る。この時、単に Socket API を使う方法と比較して、遠隔手続き呼び出しを 使う方法の利点を1つ上げて、簡単に説明しなさい。

★問題(404) 手続き呼び出しと遠隔手続き呼び出し

遠隔手続き呼び出しと通常の手続き呼出しで異なる点を1つあげ、簡単に説明 しなさい。異なる点としては、今日の講義の中で説明したものの中から選びな さい。
Last updated: 2011/12/22 08:34:27
Yasushi Shinjo / <yas@cs.tsukuba.ac.jp>