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

高度なプログラム例

以下の例は重要な 2 つの概念を示しています。1 つ目はサーバーにおける複数の未処理接続要求への管理能力を示しています。2 つ目はイベント方式の XLI/TLI の使用法およびシステムコールインタフェースです。

例 3-4 のサーバーは 1 つの未処理接続要求しかサポートしませんが、XTI/TLI ではサーバーによる複数の未処理接続要求の管理が可能です。複数の接続要求を同時に受信する理由の 1 つは、クライアントを順位付けるからです。複数の接続要求を受信した場合、サーバーはクライアントの優先順位により接続要求を受け付けることが可能です。

複数の未処理接続要求を同時に処理する 2 つ目の理由はシングルスレッド処理の限界です。トランスポートプロバイダによっては、サーバーが 1 つの接続要求の処理を行っている間、他のクライアントはビジー状態の応答を受けます。同時に複数の接続要求の処理が可能な場合、クライアント側にビジー応答が渡されるのは、サーバーで同時に処理可能なクライアントの数を超過した場合のみです。

サーバーの例はイベント方式です。プロセスはトランスポートエンドポイントに受信する XTI/TLI イベントのポーリングを行い、受信したそれぞれのイベントに対し適切な処置を行います。例は受信イベントに対し複数のトランスポートエンドポイントのポーリングを行う能力を示しています。

例 3-9 の定義およびエンドポイント確立機能は、サーバーの例 3-4 と同じものです。


例 3-9 エンドポイント確立 (複数接続へ変更可能)

#include <tiuser.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <stropts.h>
#include <signal.h>

#define NUM_FDS 1
#define MAX_CONN_IND 4
#define SRV_ADDR 1                 /*サーバー既知アドレス*/

int conn_fd;                       /*サーバーの接続*/
extern int t_errno;
/*接続要求を格納*/
struct t_call *calls[NUM_FDS][MAX_CONN_IND];

main()
{
   struct pollfd pollfds[NUM_FDS];
   struct t_bind *bind;
   int i;

   /*
    * 1 つのトランスポートエンドポイントをオープンし、バインドする
    * 複数の指定も可能
    */
   if ((pollfds[0].fd = t_open("/dev/tivc", O_RDWR,
         (struct t_info *) NULL)) == -1) {
      t_error("t_open failed");
      exit(1);
   }
   if ((bind = (struct t_bind *) t_alloc(pollfds[0].fd, T_BIND,
         T_ALL)) == (struct t_bind *) NULL) {
      t_error("t_alloc of t_bind structure failed");
      exit(2);
   }
   bind->qlen = MAX_CONN_IND;
   bind->addr.len = sizeof(int);
   *(int *) bind->addr.buf = SRV_ADDR;
   if (t_bind(pollfds[0].fd, bind, bind) == -1) {
      t_error("t_bind failed");
      exit(3);
   }
   /*正しいアドレスがバインドされたかどうか */
   if (bind->addr.len != sizeof(int) ||
      *(int *)bind->addr.buf != SRV_ADDR) {
      fprintf(stderr, "t_bind bound wrong address¥n");
      exit(4);
   }
}

t_open(3NSL) によって戻されるファイル記述子はトランスポートエンドポイントの受信データのポーリングを制御する pollfd 構造体に格納されます (poll(2) を参照)。この例では 1 つのトランスポートエンドポイントのみが確立されます。しかしながら、例の残りの部分は複数のトランスポートエンドポイントを管理するために書かれています。例 3-9 へ少し変更を加えることにより複数のトランスポートエンドポイントをサポートできるようになります。

このサーバーでは t_bind(3NSL) 用に qlen1 より大きい値に設定します。これは、サーバーが複数の未処理接続要求の待機を行うことを指定します。サーバーは現在の接続要求の受け付けを、他の接続要求を受け付ける前に行います。この例では、一度に MAX_CONN_IND (の設定値) までの接続要求の待機が行えます。MAX_CONN_IND で指定されている未処理接続要求のサポートが行えない場合、トランスポートプロバイダは qlen の値を小さくするためにネゴシエーションを行うことが可能です。

サーバーはアドレスのバインドが完了し、接続要求処理の準備が整ったら、例 3-10 で示す動作を行います。


例 3-10 接続要求の処理

pollfds[0].events = POLLIN;

while (TRUE) {
	if (poll(pollfds, NUM_FDS, -1) == -1) {
   perror("poll failed");
   exit(5);
	}
	for (i = 0; i < NUM_FDS; i++) {
   switch (pollfds[i].revents) {
      default:
         perror("poll returned error event");
      exit(6);
      case 0:
         continue;
      case POLLIN:
         do_event(i, pollfds[i].fd);
         service_conn_ind(i, pollfds[i].fd);
   	}
   }
}

pollfd 構造体の events フィールドは POLLIN に設定され、サーバーへ受信する XTI/TLI イベントを通知します。次にサーバーは無限ループに入り、トランスポートエンドポイントのポーリングを行い、発生したイベントの処理を行います。

poll(2) 呼び出しは無期限に受信イベントのブロックを行います。応答時に各エントリ (各トランスポートエンドポイントに対し 1 つ) は新しいイベントのチェックを受けます。revents0 の場合、エンドポイントにイベントの発生はなく、サーバーは次のエンドポイントへ処理を移します。reventsPOLLIN の場合はエンドポイント上にイベントがあります。この場合、do_event が呼び出され、イベントの処理が行われます。revents がそれ以外の値の場合は、エンドポイント上のエラーを通知し、サーバーは終了します。複数のエンドポイントの場合は、サーバーはこの記述子を閉じて継続処理を行うことが処理上適しています。

ループの各繰り返しに対し、service_conn_ind が未処理接続要求の処理を行うために呼び出されます。他の接続要求が保留状態の場合、service_conn_ind は新しい接続要求を保存し、あとで応答します。

例 3-11do_event は受信するイベントを処理するために呼び出されます。


例 3-11 イベント処理ルーチン

do_event( slot, fd)
int slot;
int fd;
{
   struct t_discon *discon;
   int i;

   switch (t_look(fd)) {
   default:
      fprintf(stderr, "t_look: unexpected event¥n");
      exit(7);
   case T_ERROR:
      fprintf(stderr, "t_look returned T_ERROR event¥n");
      exit(8);
   case -1:
      t_error("t_look failed");
      exit(9);
   case 0:
      /*POLLIN の戻りのため、本来起きるべきではない*/
      fprintf(stderr,"t_look returned no event¥n");
      exit(10);
   case T_LISTEN:
      /*calls 配列内の未使用要素を捜す*/
      for (i = 0; i < MAX_CONN_IND; i++) {
         if (calls[slot][i] == (struct t_call *) NULL)
            break;
      }
      if ((calls[slot][i] = (struct t_call *) t_alloc( fd, T_CALL,
               T_ALL)) == (struct t_call *) NULL) {
         t_error("t_alloc of t_call structure failed");
         exit(11);
      }
      if (t_listen(fd, calls[slot][i] ) == -1) {
         t_error("t_listen failed");
         exit(12);
      }
      break;
   case T_DISCONNECT:
      discon = (struct t_discon *) t_alloc(fd, T_DIS, T_ALL);
      if (discon == (struct t_discon *) NULL) {
         t_error("t_alloc of t_discon structure failed");
         exit(13)
      }
      if(t_rcvdis( fd, discon) == -1) {
         t_error("t_rcvdis failed");
         exit(14);
      }
      /*配列内から切断要求エントリを見つけ、削除*/
      for (i = 0; i < MAX_CONN_IND; i++) {
         if (discon->sequence == calls[slot][i]->sequence) {
            t_free(calls[slot][i], T_CALL);
            calls[slot][i] = (struct t_call *) NULL;
         }
      }
      t_free(discon, T_DIS);
      break;
   }
}

引数は番号 (slot) とファイル記述子 (fd) です。slot は各トランスポートエンドポイントのエントリを持つグローバル配列 calls のインデックスです。各エントリはエンドポイント宛に受信される接続要求を格納する t_call 構造体の配列です。

do_eventt_look(3NSL) を呼び出し、fd により指定されたエンドポイント上の XTI/TLI イベントの識別を行います。イベントが接続要求 (T_LISTEN イベント) の場合、または切断要求 (T_DISCONNECT イベント) の場合、イベントは処理されます。それ以外の場合、サーバーはエラーメッセージを出力し、終了します。

接続要求の場合、do_event は最初の未使用エントリを捜すため未処理接続要求配列の走査を行います。エントリには t_call 構造体が割り当てられ、接続要求は t_listen(3NSL) によって受信されます。配列は同時に扱える未処理接続要求の最大数を保持するのに十分な大きさを持っています。接続要求の処理は延期されます。

接続の切断要求は事前に送られた接続要求と対応していなければなりません。do_event は、要求を受信するために t_discon 構造体を割り当てます。この構造体には以下のフィールドが存在します。

struct t_discon {
 	struct netbuf udata;
 	int reason;
 	int sequence;
}

udata は接続の切断要求とともに送信されたデータを持っています。reason にはプロトコル固有の接続の切断理由コードが含まれています。sequence が接続の切断要求と対応する接続要求を特定します。

t_rcvdis(3NSL) は接続の切断要求を受信するために呼び出されます。接続要求の配列は、接続の切断要求の sequence 番号と一致する sequence 番号を持つ接続要求を捜すために走査されます。一致する接続要求が見つかった時点で、構造体は解放され、エントリは NULL に設定されます。

トランスポートエンドポイント上にイベントが発見された場合、service_conn_ind がエンドポイント上のすべての待機状態の接続要求の処理を行うために呼び出されます。例 3-12 を参照してください。


例 3-12 すべての接続要求の処理

service_conn_ind(slot, fd)
{
   int i;

	for (i = 0; i < MAX_CONN_IND; i++) {
      if (calls[slot][i] == (struct t_call *) NULL)
         continue;
      if((conn_fd = t_open( "/dev/tivc", O_RDWR,
            (struct t_info *) NULL)) == -1) {
         t_error("open failed");
         exit(15);
      }
      if (t_bind(conn_fd, (struct t_bind *) NULL,
            (struct t_bind *) NULL) == -1) {
         t_error("t_bind failed");
         exit(16);
      }
      if (t_accept(fd, conn_fd, calls[slot][i]) == -1) {
         if (t_errno == TLOOK) {
            t_close(conn_fd);
            return;
         }
         t_error("t_accept failed");
         exit(167);
      }
      t_free(calls[slot][i], T_CALL);
      calls[slot][i] = (struct t_call *) NULL;
      run_server(fd);
   }
}

それぞれのトランスポートエンドポイントについて、未処理の接続要求の配列が走査されます。サーバーは各要求ごとに、応答するトランスポートエンドポイントを開き、エンドポイントにアドレスを設定し、エンドポイントへの接続を受け取ります。現在の要求を受け取る前に他のイベント (接続要求または切断要求) を受信した場合、t_accept(3NSL) は失敗し、t_errnoTLOOK を設定します (保留状態の接続要求イベントまたは切断要求イベントがトランスポートエンドポイントにある場合は、未処理の接続要求を受け取ることはできません)。

このエラーが発生したときは、応答するトランスポートエンドポイントは閉じられ、service_conn_ind はただちに戻ります (現在の接続要求は後で処理するために保存されます)。これにより、サーバーのメイン処理ループに入ることができ、新しいイベントが次の poll(2) への呼び出しによって発見されます。この方法で、ユーザーは複数の接続要求を順に処理できます。

結果的にすべてのイベントが処理され、service_conn_ind はそれぞれの接続要求を順に受け取ることができます。接続が確立してからは、例 3-5 でサーバーに使用された run_server ルーチンが呼び出され、データの転送を管理します。