このセクションでは一歩踏み込んだ XTI/TLI の概念を説明します。
ライブラリ呼び出し用の非ブロッキング (非同期) モード
XTI/TLI 環境での TCP および UDP オプションの設定および取得方法
サーバーによる複数の未処理接続要求のサポートおよびイベント方式の操作のプログラム例
多くの XTI/TLI ライブラリルーチンは受信イベントの待機を行うためブロックを行います。ただし、処理時間要求度の高いアプリケーションでは使用するべきではありません。アプリケーションは非同期 XTI/TLI イベントを待機している間にローカル処理が行えます。
XTI/TLI イベントの非同期処理は、非同期機能および XTI/TLI ライブラリルーチンの非ブロックモードの組み合わせによりアプリケーションによる使用が可能です。poll(2) システムコールおよび、ioctl(2) の I_SETSIG コマンドの使用による非同期イベント処理については『ONC+ 開発ガイド』に記述されています。
イベントのブロックを行う各 XTI/TLI ルーチンは特別な非ブロッキングモードで実行することが可能です。たとえば、t_listen(3NSL) は通常接続要求のブロックを行います。サーバーは非ブロッキング (または非同期) モードの t_listen(3NSL) に呼び出しを行うことにより定期的にトランスポートエンドポイント接続要求待機のポーリングを行うことができます。非同期モードはファイル記述子で O_NDELAY または O_NONBLOCK を設定することにより使用可能となります。これらのモードはフラグとして t_open(3NSL) 、または XTI/TLI ルーチンを呼び出す前に fcntl(2) を呼び出すことにより設定が可能です。このモードは常時 fcntl(2) によって有効化、無効化が行えます。この章のすべてのプログラム例ではデフォルトの同期処理モードを使用しています。
O_NDELAY と O_NONBLOCK は各 XLI/TLI ルーチンへ異なる影響を与えます。特定のルーチンへの影響は O_NDELAY と O_NONBLOCK の正確な意味論を判断する必要があります。
以下の例は重要な 2 つの概念を示しています。1 つ目はサーバーにおける複数の未処理接続要求への管理能力を示しています。2 つ目はイベント方式の XLI/TLI の使用法およびシステムコールインタフェースです。
例 3-4 のサーバーは 1 つの未処理接続要求しかサポートしませんが、XTI/TLI ではサーバーによる複数の未処理接続要求の管理が可能です。複数の接続要求を同時に受信する理由の 1 つは、クライアントを順位付けるからです。複数の接続要求を受信した場合、サーバーはクライアントの優先順位により接続要求を受け付けることが可能です。
複数の未処理接続要求を同時に処理する 2 つ目の理由はシングルスレッド処理の限界です。トランスポートプロバイダによっては、サーバーが 1 つの接続要求の処理を行っている間、他のクライアントはビジー状態の応答を受けます。同時に複数の接続要求の処理が可能な場合、クライアント側にビジー応答が渡されるのは、サーバーで同時に処理可能なクライアントの数を超過した場合のみです。
サーバーの例はイベント方式です。プロセスはトランスポートエンドポイントに受信する XTI/TLI イベントのポーリングを行い、受信したそれぞれのイベントに対し適切な処置を行います。例は受信イベントに対し複数のトランスポートエンドポイントのポーリングを行う能力を示しています。
例 3-9 の定義およびエンドポイント確立機能は、サーバーの例 3-4 と同じものです。
#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) 用に qlen を 1 より大きい値に設定します。これは、サーバーが複数の未処理接続要求の待機を行うことを指定します。サーバーは現在の接続要求の受け付けを、他の接続要求を受け付ける前に行います。この例では、一度に MAX_CONN_IND (の設定値) までの接続要求の待機が行えます。MAX_CONN_IND で指定されている未処理接続要求のサポートが行えない場合、トランスポートプロバイダは qlen の値を小さくするためにネゴシエーションを行うことが可能です。
サーバーはアドレスのバインドが完了し、接続要求処理の準備が整ったら、例 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 つ) は新しいイベントのチェックを受けます。revents が 0 の場合、エンドポイントにイベントの発生はなく、サーバーは次のエンドポイントへ処理を移します。revents が POLLIN の場合はエンドポイント上にイベントがあります。この場合、do_event が呼び出され、イベントの処理が行われます。revents がそれ以外の値の場合は、エンドポイント上のエラーを通知し、サーバーは終了します。複数のエンドポイントの場合は、サーバーはこの記述子を閉じて継続処理を行うことが処理上適しています。
ループの各繰り返しに対し、service_conn_ind が未処理接続要求の処理を行うために呼び出されます。他の接続要求が保留状態の場合、service_conn_ind は新しい接続要求を保存し、あとで応答します。
例 3-11 の do_event は受信するイベントを処理するために呼び出されます。
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_event は t_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 を参照してください。
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_errno に TLOOK を設定します (保留状態の接続要求イベントまたは切断要求イベントがトランスポートエンドポイントにある場合は、未処理の接続要求を受け取ることはできません)。
このエラーが発生したときは、応答するトランスポートエンドポイントは閉じられ、service_conn_ind はただちに戻ります (現在の接続要求は後で処理するために保存されます)。これにより、サーバーのメイン処理ループに入ることができ、新しいイベントが次の poll(2) への呼び出しによって発見されます。この方法で、ユーザーは複数の接続要求を順に処理できます。
結果的にすべてのイベントが処理され、service_conn_ind はそれぞれの接続要求を順に受け取ることができます。接続が確立してからは、例 3-5 でサーバーに使用された run_server ルーチンが呼び出され、データの転送を管理します。