このセクションでは高度な XTI/TLI の概念を説明します。
「非同期実行モード」では、いくつかのライブラリ呼び出しで使用するオプションの非ブロッキング (非同期) モードについて説明します。
「XTI/TLI の高度なプログラミング例」では、複数の未処理コネクション要求をサポートし、イベント方式で動作するサーバーのプログラム例を示します。
多くの XTI/TLI ライブラリルーチンは受信イベントの発生を待機するブロックを行います。ただし、処理時間の条件が高いアプリケーションではこれを使用しないでください。アプリケーションは、非同期 XTI/TLI イベントを待機する間にローカル処理が行えます。
アプリケーションが XTI/TLI イベントの非同期処理にアクセスするには、XTI/TLI ライブラリルーチンの非同期機能と非ブロッキングモードを組み合わせて使用する必要があります。poll(2) システムコールと I_SETSIG ioctl(2) コマンドを使用してイベントを非同期的に処理する方法については、『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 を使用することによって各 XTI/TLI ルーチンに与える影響はそれぞれ異なります。特定のルーチンへの影響を知るには、O_NDELAY と O_NONBLOCK の正確な意味論を認識する必要があります。
例 9–2 に、2 つの重要な概念を示します。1 つ目はサーバーにおける複数の未処理のコネクション要求に対する管理能力。2 つ目はイベント方式の XTI/TLI の使用法およびシステムコールインタフェースです。
XTI/TLI を使用すると、サーバーは複数の未処理のコネクション要求を管理できます。複数のコネクション要求を同時に受信する理由の 1 つは、クライアントを順位付けることです。複数のコネクション要求を受信した場合、サーバーはクライアントの優先順位に従ってコネクション要求を受け付けることが可能です。
複数の未処理コネクション要求を同時に処理する理由の 2 つ目、シングルスレッド処理の限界です。トランスポートプロバイダによっては、あるサーバーが 1 つのコネクション要求を処理する間、他のクライアントからはそのサーバーがビジーであるように見えます。複数のコネクション要求を同時に処理する場合、サーバーがビジーになるのは、サーバーを同時に呼び出そうとするクライアントの数が最大数を超える場合だけです。
次のサーバーの例はイベント方式です。 プロセスはトランスポート終端をポーリングして、XTI/TLI 受信イベントが発生しているかを確認し、受信したイベントに適切な処理を行います。複数のトランスポート終端をポーリングして、受信イベントが発生しているか確認する例を示します。
#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 /* server's well known address */ int conn_fd; /* server connection here */ extern int t_errno; /* holds connect requests */ struct t_call *calls[NUM_FDS][MAX_CONN_IND]; main() { struct pollfd pollfds[NUM_FDS]; struct t_bind *bind; int i; /* * Only opening and binding one transport endpoint, but more can * be supported */ 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); } /* Was the correct address bound? */ 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 つのトランスポート終端だけが確立されます。ただし、例の残りの部分は複数のトランスポート終端を管理するために書かれています。例 9–2 を少し変更することにより複数のトランスポート終端をサポートできるようになります。
このサーバーは t_bind(3NSL) 用に qlen を 1 より大きな値に設定します。この値は、サーバーが複数の未処理のコネクション要求を待ち行列に入れる必要があるということを指定します。サーバーは現在のコネクション要求の受け付けを行なってから、別のコネクション要求を受け付けます。この例では、MAX_CONN_IND 個までのコネクション要求を待ち行列に入れることができます。MAX_CONN_IND 個の未処理のコネクション要求をサポートできない場合、トランスポートプロバイダはネゴシエーションを行なって qlen の値を小さくすることができます。
アドレスをバインドし、コネクション要求を処理できるようになったあと、サーバーは次の例に示すように動作します。
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 の値を確認し、新しいイベントが発生しているかを確認します。revents が 0 の場合、この終端上ではイベントが生成されていないので、サーバーは次の終端に進みます。revents が POLLIN の場合は終端上にイベントがあるため、do_event を呼び出してイベントを処理します。revents がそれ以外の値の場合は、終端上のエラーを通知し、サーバーは終了します。終端が複数ある場合、サーバーはこのファイル記述子を閉じて、処理を継続します。
サーバーはループを繰り返すごとに service_conn_ind を呼び出して、未処理のコネクション要求を処理します。他のコネクション要求が保留状態の場合、service_conn_ind は新しいコネクション要求を保存し、あとでそれを処理します。
次に、サーバーが 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: /* since POLLIN returned, this should not happen */ fprintf(stderr,"t_look returned no event\n"); exit(10); case T_LISTEN: /* find free element in calls array */ 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); } /* find call ind in array and delete it */ 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; } }
例 9–4 の引数は番号の 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 番号と一致するコネクション要求があるかどうかを走査します。一致するコネクション要求が見つかった場合、サーバーはその構造体を解放して、エントリを NULL に設定します。
トランスポート終端上にイベントが見つかった場合、サーバーは終端上の待ち行列に入っているすべてのコネクション要求を処理するために service_conn_ind を呼び出します。
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 はそれぞれのコネクション要求を順に受け取ることができます。