ネットワークインタフェース

クライアントサーバープログラム

もっとも一般的な分散型アプリケーションは、クライアントサーバーモデルです。この方式では、クライアントプロセスはサーバープロセスからのサービスを要求します。

代替方式として、休止しているサーバープロセスを削除できるサービスサーバーがあります。たとえば、インターネットサービスデーモン inetd(1M) は、構成ファイルを読み取ることにより、起動時に決定されるさまざまなポートで待機します。inetd(1M) のサービスを受けるポートで接続が要求されると、inetd(1M) はクライアントにサービスを行うために適切なサーバーを生成します。クライアントは、その接続で中間媒体が何らかの役割を果たしたことを認識しません。inetd(1M) については、inetd(1M) デーモン」で詳しく説明しています。

サーバー

ほとんどのサーバーには、既知のインターネットポート番号または UNIX ファミリ名でアクセスします。例 2-6 は、リモートログインサーバーのメインループを示しています。


例 2-6 リモートログインサーバー

main(argc, argv)
   int argc;
   char *argv[];
{
   int f;
   struct sockaddr_in6 from;
   struct sockaddr_in6 sin;
   struct servent *sp;
 
   sp = getservbyname("login", "tcp");
 
   if (sp == (struct servent *) NULL) {
      fprintf(stderr, "rlogind: tcp/login: unknown service");
      exit(1);
   }
   ...
   #ifndef DEBUG
   /* サーバーを制御端末から分離する。*/
   ...
   #endif
   	sin.sin6_port = sp->s_port;	/* 限られたポート */
      sin.sin6_addr.s6_addr = in6addr_any;
      ...
      f = socket(AF_INET6, SOCK_STREAM, 0);
      ...
      if (bind( f, (struct sockaddr *) &sin, sizeof sin ) == -1) {
      ...
      }
      ...
      listen(f, 5);
      while (TRUE) {
         int g, len = sizeof from;
         g = accept(f, (struct sockaddr *) &from, &len);
         if (g == -1) {
            if (errno != EINTR)
               syslog(LOG_ERR, "rlogind: accept: %m");
            continue;
         }
         if (fork() == 0) {
            close(f);
            doit(g, &from);
         }
         close(g);
      }
      exit(0);
}

例 2-7 は、サーバーがそのサービス定義を取得する方法を示しています。


例 2-7 リモートログインサーバー : 手順 1

sp = getservbyname("login", "tcp");
if (sp == (struct servent *) NULL) {
		fprintf(stderr, "rlogind: tcp/login: unknown service¥n");
		exit(1);
}

getservbyname(3SOCKET) の結果は、プログラムがサービス要求を待機するインターネットポートを定義するためにあとで使用されます。標準のポート番号の一部は、/usr/include/netinet/in.h に入っています。

例 2-8 は、オペレーションの非 DEBUG モードで、サーバーがその呼び出し側の制御端末との関連付けを解除する方法を示しています。


例 2-8 制御端末との関連付けを解除する

   (void) close(0);
   (void) close(1);
   (void) close(2);
   (void) open("/", O_RDONLY);
   (void) dup2(0, 1);
   (void) dup2(0, 2);
   setsid();

このコードは、サーバーが制御端末のプロセスグループからのシグナルを受信することを防ぎます。関連付けを解除したあと、サーバーはエラーレポートを端末に送信できないため、syslog(3C) を使用してエラーをログに記録する必要があります。

サーバーは次にソケットを作成し、サービス要求を待機します。bind(3SOCKET) が使用されると、サーバーは指定された位置で待機します (限られたポート番号で待機するリモートログインサーバーはスーパーユーザーとして動作する)。

例 2-9 は、ループの本体を示しています。


例 2-9 リモートログインサーバー : 本体

while(TRUE) {
		int g, len = sizeof(from);
		if (g = accept(f, (struct sockaddr *) &from, &len) == -1) {
			if (errno != EINTR)
				syslog(LOG_ERR, "rlogind: accept: %m");
			continue;
		}
		if (fork() == 0) {		/* 子 */
			close(f);
			doit(g, &from);
		}
		close(g);					/* 親 */
}

accept(3SOCKET) は、クライアントがサービスを要求するまでメッセージをブロックします。accept(3SOCKET) は、SIGCHLD のようなシグナルで割り込みを受ける場合、失敗を示すメッセージを返します。accept(3SOCKET) からの戻り値がチェックされ、エラーが発生している場合は syslog(3C) によってエラーが記録されます。

続いてサーバーは子プロセスの fork(2) を行い、リモートログインプロトコル処理の本体を呼び出します。接続要求を待ち行列に入れるために親プロセスが使用するソケットは、子プロセスで閉じられます。accept(3SOCKET) が作成するソケットは、親プロセスで閉じられます。クライアントのアドレスは、クライアントの認証を行うためにサーバーアプリケーションの doit() ルーチンに渡されます。このルーチンは、クライアントと共に実際のアプリケーションプロトコルを実行します。

クライアント

この節では、クライアントリモートログインプロセスで行われる処理について説明します。サーバー側と同様に、まずリモートログインのサービス定義の位置を確認します。

sp = getservbyname("login", "tcp");
if (sp == (struct servent *) NULL) {
		fprintf(stderr,"rlogin: tcp/login: unknown service");
		exit(1);
}

次に、getipnodebyname(3SOCKET) 呼び出しで宛先ホストを調べます。

hp = getipnodebyname (AF_INET6, argv[1], AI_DEFAULT, &errnum);
if (hp == (struct hostent *) NULL) {
		fprintf(stderr, "rlogin: %s: unknown host", argv[1]);
		exit(2);
}

次に、要求されたホストでサーバーに接続し、リモートログインプロトコルを開始します。アドレスバッファーはクリアされ、外部ホストのインターネットアドレスと、ログインサーバーが待機するポート番号が書き込まれます。

memset((char *) &server, 0, sizeof server);
bzero (&sin6, sizeof (sin6));
memcpy((char*) &server.sin6_addr,hp->h_addr,hp->h_length);
server.sin6_family = hp->h_addrtype;
server.sin6_port = sp->s_port;

ソケットが作成され、接続が開始されます。s のバインドが解除されるため、connect(3SOCKET) は暗黙に bind(3SOCKET) を実行します。

s = socket(hp->h_addrtype, SOCK_STREAM, 0);
if (s < 0) {
		perror("rlogin: socket");
		exit(3);
}
 ...
if (connect(s, (struct sockaddr *) &server, sizeof server) < 0) {
		perror("rlogin: connect");
		exit(4);
}

コネクションレス型のサーバー

サービスの中にはデータグラムソケットを使用するものがあります。rwho(1) サービスは、LAN に接続されたホストのステータス情報を提供します (in.rwhod(1M) は大量のネットワークトラフィックを発生させるため、実行を避けてください)。rwho(1) サービスを利用するには、特定のネットワークに接続されたすべてのホストに情報をブロードキャスト送信できる環境が必要です。これは、データグラムソケットを使用する例の 1 つです。

rwho(1) サーバーを実行するホスト上のユーザーは、ruptime(1) を使用して別のホストの現在のステータスを取得できます。典型的な出力を例 2-10 に示します。


例 2-10 ruptime(1) プログラムの出力

itchy up 9:45, 5 users, load 1.15, 1.39, 1.31
scratchy up 2+12:04, 8 users, load 4.67, 5.13, 4.59
click up 10:10, 0 users, load 0.27, 0.15, 0.14
clack up 2+06:28, 9 users, load 1.04, 1.20, 1.65
ezekiel up 25+09:48, 0 users, load 1.49, 1.43, 1.41
dandy 5+00:05, 0 users, load 1.51, 1.54, 1.56
peninsula down 0:24
wood down 17:04
carpediem down 16:09
chances up 2+15:57, 3 users, load 1.52, 1.81, 1.86

各ホストには、rwho(1) サーバープロセスによってステータス情報が周期的にブロードキャスト送信されます。このサーバープロセスもステータス情報を受信し、データベースを更新します。このデータベースは、各ホストのステータスのために解釈されます。サーバーはそれぞれ個別に動作し、ローカルネットワークとそのブロードキャスト機能によってのみ結合されます。

大量のネットトラフィックが生成されるため、ブロードキャストの使用はかなり困難です。サービスが広範に、かつ頻繁に使用されない限り、周期的なブロードキャストのために手間がかかり簡潔さが失われることになります。

例 2-11 は、rwho(1) サーバーの簡潔な例を示しています。このコードは、ネットワーク上のほかのホストによるステータス情報ブロードキャストを受信し、そのホストのステータスを供給するという 2 つのタスクを行います。最初のタスクは、プログラムのメインループで行われます。別の rwho(1) サーバープロセスによって送信されたことを確認するために rwho(1) ポートで受信されたパケットがチェックされ、到着時刻が記録されます。パケットは、続いてホストのステータスを使用してファイルを更新します。一定の時間内にホストからの通信がない場合、データベースルーチンはホストがダウンしていると想定し、そのことを記録します。ホストが稼動している間サーバーがダウンしていることがあるため、このアプリケーションはエラーになりがちです。


例 2-11 rwho(1) サーバー

main()
{
   ...
   sp = getservbyname("who", "udp");
   net = getnetbyname("localnet");
   sin.sin6_addr = inet_makeaddr(net->n_net, in6addr_any);
   sin.sin6_port = sp->s_port;
   ...
   s = socket(AF_INET6, SOCK_DGRAM, 0);
   ...
   on = 1;
   if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &on, sizeof on)
         == -1) {
      syslog(LOG_ERR, "setsockopt SO_BROADCAST: %m");
      exit(1);
   }
   bind(s, (struct sockaddr *) &sin, sizeof sin);
   ...
   signal(SIGALRM, onalrm);
   onalrm();
   while(1) {
      struct whod wd;
	      int cc, whod, len = sizeof from;
      cc = recvfrom(s, (char *) &wd, sizeof(struct whod), 0,
         (struct sockaddr *) &from, &len);
      if (cc <= 0) {
      if (cc == -1 && errno != EINTR)
         syslog(LOG_ERR, "rwhod: recv: %m");
      continue;
      }
      if (from.sin6_port != sp->s_port) {
         syslog(LOG_ERR, "rwhod: %d: bad from port",
            ntohs(from.sin6_port));
         continue;
      }
      ...
      if (!verify( wd.wd_hostname)) {
         syslog(LOG_ERR, "rwhod: bad host name from %x",
            ntohl(from.sin6_addr.s6_addr));
         continue;
      }
      (void) sprintf(path, "%s/whod.%s", RWHODIR, wd.wd_hostname);
      whod = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0666);
      ...
      (void) time(&wd.wd_recvtime);
      (void) write(whod, (char *) &wd, cc);
      (void) close(whod);
   }
   exit(0);
}

2 つ目のサーバータスクは、そのホストのステータスの供給です。このタスクでは、周期的にシステムステータス情報を取得し、その情報をメッセージとしてパッケージ化し、ほかの rwho(1) サーバーに知らせるためローカルネットワーク上でそれをブロードキャスト送信する必要があります。このタスクはタイマーで実行され、シグナルによって起動されます。システムステータス情報の位置確認が伴いますが、重要なものではありません。

ステータス情報が、ローカルネットワーク上でブロードキャスト送信されます。ブロードキャストをサポートしないネットワークでは、別の方式を使用してください。