コンピューター:C言語講座:RPCについて(1)
概要
C言語講座もだいぶ増えて来ましたが、全体を見るとプロセス間通信が実に豊富に揃っていることがわかります。個人的に別々のプロセスが連携するということが非常に興味があったのでこういう結果になっているわけですが、実は避けて来た話題があります。それがRPCです。リモートプロシジャーコールですが、正直な所、使ったことがありませんでした。
ネットワーク上の他のマシンの関数を自由に使用できるような物だろうというイメージを持っていたのですが、何しろRPCに関する文献が非常に少なく(とくに日本語の)、これまで手を出さずにいたわけですが、ここまで来たらやっておかねばということで、取り上げることにしました。
RPCはどの様な所で使用されているかというと、mountd,ypserv,ypbind,rusersd,walldなど、ネットワークを介していろいろなサービスを行なうようなデーモンがRPCを使用して実現されています。
RPCを使用する場合に開発方法として、高レベル関数群を使用して、自分でゼロから作る方法と、rpcgenというRPCプロトコルコンパイラを使用した方法があります。また、RPCは調べてみると、予想外にいろいろな場面に応じて考えないといけない面があり、C言語講座で全て取り上げるのは無理と考え、とりあえず、どんなものかを紹介する程度にして、あとは文献を詳しく見ていただくようにしたいと思います。
RPCについて(1)では高レベル関数群を使用して、自分でゼロから作る方法を紹介します。
サンプル
RPCでは使用する関数の説明をばらばらに行なうより、実際に使用しているサンプルを見たほうがわかりやすいと思いますので、いきなりサンプルです。
リモートのマシンの指定したディレクトリを見るという単純なサンプルです。サービスを提供するサーバと、問い合わせるクライアントを作成します。
<サーバ>
#include <sys/types.h>
#include <dirent.h>
#include <rpc/rpc.h>
#define BUF_SIZE 8192
#define RLS_PROG ((u_long)0x20000000)
#define RLS_VER ((u_long)1)
#define RLS_PROC ((u_long)1)
void main()
{
bool_t xdr_rls();
char *do_rls();
registerrpc(RLS_PROG,RLS_VER,RLS_PROC,do_rls,xdr_rls,xdr_rls);
svc_run();
}
char *do_rls(buf)
char *buf;
{
DIR *dirp;
struct dirent *dp;
dirp=opendir(buf);
if(dirp==NULL){
strcpy(buf,"opendir error");
return(NULL);
}
buf[0]=NULL;
while(dp=readdir(dirp)){
sprintf(buf,"%s%s\n",buf,dp->d_name);
}
closedir(dirp);
return(buf);
}
bool_t xdr_rls(xdrs,objp)
XDR *xdrs;
char *objp;
{
return(xdr_string(xdrs,&objp,BUF_SIZE));
}
RPCではプログラム番号とバージョン番号、関数番号を使用してクライアントがサーバをコールします。RLS_PROGがプログラム番号、RLS_VERがバージョン番号、RLS_PROCが関数番号です。クライアントからコールする場合にこの3つが一致しないと呼び出されません。
なお、プログラム番号はSUNが管理しており、0x00000000〜0x1FFFFFFFまではSUNによって定義済みで、ユーザ定義用には0x20000000〜0x3FFFFFFFを使用します。
registerrpc()でサービスを登録します。svc_run()でリクエスト待ちに入ります。do_rls()がリクエストが来た場合に呼ばれる関数で、内容は「ディレクトリ内容の読み出し」を参考にしてください。xdr_rls()はわかりにくいのですが、RPCではアーキテクチャの異なるマシン間でもデータをやりとりする為にXDRライブラリを使用し、マシンに依存しない形式に変換してやりとりをします。このサンプルでは単純な文字列なので、xdr_string()というXDRライブラリで変換させています。同じアーキテクチャのマシンで行なう場合は不要と思いますが、ルールのようなものなので、従いましょう。
サーバは以上のようにただサービスを登録して待つだけのシンプルなものです。
<クライアント>
#include <stdio.h>
#include <strings.h>
#include <rpc/rpc.h>
#define BUF_SIZE 8192
#define RLS_PROG ((u_long)0x20000000)
#define RLS_VER ((u_long)1)
#define RLS_PROC ((u_long)1)
void main(argc,argv)
int argc;
char *argv[];
{
char buf[BUF_SIZE];
if(argc<=2){
fprintf(stderr,"rls hostname path\n");
exit(-1);
}
strcpy(buf,argv[2]);
call_rls(argv[1],buf);
printf("%s\n",buf);
exit(0);
}
int call_rls(host,buf)
char *host;
char *buf;
{
bool_t xdr_rls();
enum clnt_stat clnt_stat;
clnt_stat=callrpc(host,RLS_PROG,RLS_VER,RLS_PROC,xdr_rls,buf,xdr_rls,buf);
if(clnt_stat!=0){
clnt_perrno(clnt_stat);
}
return(0);
}
bool_t xdr_rls(xdrs,objp)
XDR *xdrs;
char *objp;
{
return(xdr_string(xdrs,&objp,BUF_SIZE));
}
クライアントは内容を見たいホスト名と見たいパスを引数で渡し、起動するようにします。call_rls()でcallrpc()を使用してリモートプロシジャーコールを行なっています。xdr_rls()はサーバと同様に同じアーキテクチャでは不要ですが、ルールですので、使用します。
使い方としてはサービスを提供したいホストでサーバプログラムを起動しておき、クライアントプログラムを
rls ホスト名 パス
という形式で起動すると、リモートホストのディレクトリの内容が表示されます。
まとめ
サンプルのように、リモートホストのdo_rls()関数をあたかも自分の関数のように呼び出してしまうのがRPCです。異機種間での動作を考慮してXDRライブラリを使用するのがわかりにくいかも知れませんが、RPCについて(2)で使用するrpcgenを使用するとその辺は簡単に通過できます。
このサンプル程度のことであればrshを使用すれば出来るではないかと考えるかも知れませんが、rshを使用するには相手のホストに自分のアカウントが必要で、/etc/hosts.equivまたは~/.rhostsに許可されていない場合はいちいちパスワードを入力しなければなりません。従って、単独使用ならまだしも、プログラムに組み込む場合には非常に注意が必要になりますが、RPCの場合はサーバが起動していれば呼び出しは簡単に行なえます。サーバをrootで起動しておけば一般ユーザでは見れない場所を見ることも出来てしまいます(便利だけど危険)。また、アーキテクチャが異なるマシンでも確実にデータを受け渡せるなど、汎用性の高いサービスを構築できます。
なお、今回のように高レベル関数を使用したRPCの場合、内部的にはUDPを使用します。UDPはTCPのようにコネクションを張らないので高速ですが、データの保証がされません。やりとりするデータのサイズが8KByte以下の場合は一度のパケットで通信しますので、UDPでも問題無い場合が多いのですが、それ以上のデータ量のやりとりの場合はTCPを使用したほうが無難です。RPCについて(2)で使用するprcgenを用いればTCP,UDPともに使用可能なソースが生成されます。