この章では、トランスポート層インタフェース (TLI) と X/Open トランスポートインタフェース (XTI) について説明します。非同期実行モードなどの拡張機能については、XTI/TLI の拡張機能で説明します。
分散/集中データ転送などの最近 XTI に追加された機能については、XTI インタフェースへの追加で説明します。
OSI モデル (第 4 層) のトランスポート層はアプリケーションと上位層の間でエンドツーエンドのサービスを提供するモデルの最下位層です。この層は、配下のネットワークのトポロジと特性をユーザーには見えないようにします。トランスポート層はまた、現在の数多くのプロトコル群 (OSI プロトコル、TCP および TCP/IP インターネットプロトコル群、Xerox Network Systems (XNS)、システムネットワークアーキテクチャ (System Network Architecture、SNA) など) に共通する一連のサービスを定義します。
TLI は、業界標準の Transport Service Definition (ISO 8072) でモデル化されています。TLI は、TCP と UDP の両方にアクセスするために使用できます。XTI と TLI はネットワークプログラミングインタフェースを構成するインタフェースセットです。 XTI は SunOS 4 プラットフォーム用の以前の TLI インタフェースを発展させたものです。Solaris オペレーティングシステムはどちらのインタフェースもサポートしますが、このインタフェースセットの将来の方向性を表しているのは XTI の方です。Solaris ソフトウェアは STREAMS 入出力メカニズムを使用して、XTI と TLI をユーザーライブラリとして実装しています。
この章で取り上げるインタフェースはマルチスレッドに対して安全です。これは XTI/TLI インタフェース呼び出しを含むアプリケーションがマルチスレッド化されたアプリケーション内で自由に使用できることを意味します。これらのインタフェース呼び出しは再入可能ではないので、スケーラビリティは直線的でありません。
XTI/TLI インタフェースの非同期環境における動作は特定されていません。これらのインタフェースはシグナルハンドラルーチンからは使用しないでください。
TLI は AT&T の System V Release 3 とともに 1986 年に導入されました。TLI は トランスポート層インタフェース API を規定しました。TLI は ISO Transport Service Definition が規定するモデルに基づいています。TLI は OSI トランスポート層とセッション層の間の API を提供します。TLI インタフェースは UNIX の AT&T System V Release 4 バージョンでさらに発展し、SunOS 5.6 オペレーティングシステムインタフェースにも取り入れられました。
XTI インタフェースは TLI インタフェースを発展させたもので、このインタフェースの将来の方向を表しています。TLI を使用するアプリケーションとの互換性が保証されています。ただちに TLI のアプリケーションを XTI のアプリケーションに移行する必要性はありませんが、新しいアプリケーションでは XTI インタフェースを使用し、必要に応じて、TLI アプリケーションを XTI に移行してください。
TLI はライブラリ (libns1) 内のインタフェース呼び出しセットとして実装され、そこにアプリケーションがリンクします。XTI アプリケーションは c89 フロントエンドを使用してコンパイルし、xnet ライブラリ (libxnet) とリンクする必要があります。XTI を使用するコンパイルの詳細についは、standards(5) のマニュアルページを参照してください。
XTI インタフェースを使用するアプリケーションは xti.h ヘッダーファイルを使用するのに対し、TLI インタフェースを使用するアプリケーションは tiuser.h ヘッダーファイルを使用しています。
XTI/TLI コードは第 4 章で説明されている追加のインタフェースとメカニズムと組み合わせと同時に使用することで、現在のトランスポートプロバイダに依存する必要がなくなります。SunOS 5.x はいくつかのトランスポートプロバイダ (たとえば、TCP) をオペレーティングシステムの一部として用意しています。トランスポートプロバイダはサービスを実行し、トランスポートユーザーはサービスを要求します。トランスポートユーザーがトランスポートプロバイダへサービス要求を行います。たとえば、TCP や UDP 上のデータ転送要求などがそれに当たります。
XTI/TLI は次の 2 つの構成要素を利用することによっても、トランスポートに依存しないプログラミングが可能になります。
トランスポート選択や名前からアドレスへの変換 (name-to-address) を始めとするトランスポートサービスを実行するライブラリルーチン。ネットワークサービスライブラリには ユーザープロセスで XTI/TLI を実装するインタフェースセットが用意されています。詳細は第 9 章「トランスポート選択と名前からアドレスへのマッピング」を参照してください。
TLI を使用するプログラムは libnsl ネットワークサービスライブラリにリンクする必要があります(コンパイル時に -l nsl オプションを使用)。
XTI を使用するプログラムは xnet ライブラリにリンクする必要があります (コンパイル時に -l xnet オプションを使用) 。
状態遷移規則は、トランスポートルーチンを呼び出すシーケンスを定義します。状態遷移規則の詳細については、状態遷移を参照してください。状態テーブルは状態およびイベントの処理に基づいて、ライブラリ呼び出しの正当なシーケンス定義します。これらのイベントには、ユーザー生成ライブラリ呼び出し、プロバイダ生成イベントのインジケータが含まれます。XLI/TLI のプログラマはインタフェースを使用する前にすべての状態遷移をよく理解しておく必要があります。
ユーザーがコネクション中に受信したデータを処理するために、既存のプログラム上 (/usr/bin/cat など) で exec(2) を使用してトランスポートコネクションを確立したいとします。既存のプログラムは read(2) および write(2) を使用します。XTI/TLI は直接トランスポートプロバイダへの読み取りインタフェースと書き込みインタフェースをサポートしていませんが、これを処理することが可能です。このインタフェースを使用すると、データ転送フェーズにおいて read(2) および write(2) 呼び出しをトランスポートコネクション上で実行できます。このセクションでは XTI/TLI のコネクションモードサービスへの読み取りインタフェースと書き込みインタフェースについて説明しています。なおこのインタフェースはコネクションレスモードサービスでは使用できません。
#include <stropts.h> . ./* 同一のローカル管理および接続確立手順 */ . if (ioctl(fd, I_PUSH, "tirdwr") == -1) { perror(“I_PUSH of tirdwr failed”); exit(5); } close(0); dup(fd); execl(“/usr/bin/cat”, “/usr/bin/cat”, (char *) 0); perror(“exec of /usr/bin/cat failed”); exit(6); }
クライアントは tirdwr をトランスポート終端に関連付けられたストリーム内にプッシュすることにより読み取りインタフェースと書き込みインタフェースを呼び出します。詳細については、streamio(7I) のマニュアルページの I_PUSH を参照してください。tirdwr モジュールはトランスポートプロバイダより上位に位置する XTI/TLI を純粋な読み取りインタフェースと書き込みインタフェースに変換します。モジュールが設置された段階で、クライアントは close(2) および dup(2) を呼び出してトランスポート終端を標準入力ファイルとして確立し、/usr/bin/cat を使用して入力を処理します。
tirdwr をトランスポートプロバイダにプッシュすると、XTI/TLI は read(2) および write(2) の意味論を使用するようになります。read および write の意味論を使用するとき、XTI/TLI はメッセージ境界を保持しません。トランスポートプロバイダから tirdwr をポップすると、XTI/TLI は本来の意味論に戻ります (streamio(7I) のマニュアルページの I_POP を参照)。
tirdwr モジュールをストリーム上にプッシュできるのは、トランスポート終端がデータ転送フェーズ中にある場合だけです。モジュールをプッシュしたあと、ユーザーは XLI/TLI ルーチンを呼び出すことはできません。ユーザーが XTI/TLI ルーチンを呼び出した場合、tirdwr はストリーム上に重大なプロトコルエラー EPROTO を生成し、使用不可であることを通知します。このとき、tirdwr モジュールをストリーム上からポップすると、トランスポートコネクションは中止されます。詳細については、streamio(7I) のマニュアルページの I_POP を参照してください。
write(2) を使用してトランスポートコネクションにデータを送信したあと、tirdwr はトランスポートプロバイダを通じてデータを渡します。メカニズム上は許可されていますが、ゼロ長のデータパケットを送った場合、tirdwr はメッセージを破棄します。トランスポートコネクションが中止された場合、ハングアップ状態がストリーム上に生成され、それ以降の write(2) 呼び出しは失敗し、errno は ENXIO に設定されます。この問題が発生するのは、たとえば、リモートユーザーが t_snddis(3NSL) を使用してコネクションを中止した場合があります。ハングアップ後も利用できるデータの取り出しは可能です。
トランスポートコネクションに着信したデータを読み取るには、read(2) を使用します。tirdwr はトランスポートプロバイダからデータを渡します。tirdwr モジュールは、トランスポートプロバイダからユーザーに渡されるその他のイベントまたは要求を次のように処理します。
read(2) はユーザーへ送られる優先データを識別できません。read(2) が優先データ要求を受信した場合、tirdwr はストリーム上に重大なプロトコルエラー EPROTO を生成します。このエラーが発生すると、後続のシステムコールは失敗します。優先データを受信するときには、read(2) を使用しないでください。
tirdwr は放棄型の切断要求を破棄し、ストリーム上にハングアップ状態を生成します。後続の read(2) 呼び出しには残りのデータを返し、すべてのデータを返した後の呼び出しにはファイルの終わりを示す 0 を返します。
tirdwr は正常型解放要求を破棄し、ゼロ長のメッセージをユーザーに配信します。read(2) のマニュアルページで説明するようにファイルの終わりを示す 0 をユーザーに返します 。
read(2) がその他の XTI/TLI 要求を受信した場合、tirdwr はストリーム上に重大なプロトコルエラー EPROTO を生成します。このエラーが発生すると、後続のシステムコールは失敗します。コネクションを確立したあと、ユーザーが tirdwr をストリーム上にプッシュした場合、tirdwr は要求を生成しません。
ストリーム上に tirdwr が存在する場合、コネクションの間はトランスポートコネクション上でデータの送受信が可能です。どちらのユーザーも、トランスポート終端に関連付けられたファイル記述子を閉じることにより、またはストリーム上から tirdwr モジュールをポップさせることによりコネクションを終了させることが可能です。どちらの場合も tirdwr は次の処理を行います。
正常型解放要求を受信した場合、tirdwr は要求をトランスポートプロバイダに渡してコネクションを正常に解放します。データ転送が完了すると、正常型解放手続きを実行したリモートユーザーは期待される結果を受信します。
切断要求を受信した場合、tirdwr は特別な処理を行いません。
正常型解放要求または切断要求のどちらも受信しない場合、tirdwr は切断要求をトランスポートプロバイダに渡してコネクションを中止します。
ストリーム上でエラーが発生したときに切断要求を受信しない場合、tirdwr は切断要求をトランスポートプロバイダに渡します。
tirdwr をストリーム上にプッシュしたあと、プロセスは正常型解放を実行できません。トランスポートコネクションの相手側のユーザーが解放を実行した場合、tirdwr は正常型解放を処理します。このセクションのクライアントがサーバープログラムと通信している場合、サーバーは正常型解放要求を使用してデータの転送を終了します。次に、サーバーはクライアントからの対応する要求を待ちます。この時点でクライアントは、トランスポート終端を終了して閉じます。ファイル記述子を閉じたあと、tirdwr はコネクションのクライアント側から正常解放型要求を実行します。この解放によって、サーバーをブロックする要求が生成されます。
データがそのままで配信されることを保証するために、この正常型解放を必要とするTCP などのプロトコルもあります。
このセクションでは高度な 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 を使用することによって各 XLI/TLI ルーチンに与える影響はそれぞれ異なります。特定のルーチンへの影響を知るには、O_NDELAY と O_NONBLOCK の正確な意味論を認識する必要があります。
例 8–2 に、2 つの重要な概念を示します。1 つ目はサーバーにおける複数の未処理のコネクション要求に対する管理能力。2 つ目はイベント方式の XLI/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 /*サーバー既知アドレス*/ 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 つのトランスポート終端だけが確立されます。ただし、例の残りの部分は複数のトランスポート終端を管理するために書かれています。例 8–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: /*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; } }
例 8–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 はそれぞれのコネクション要求を順に受け取ることができます。
この節では、XTI/TLI を使用して非同期ネットワーク通信を行う、リアルタイムアプリケーション用の手法について説明します。SunOS プラットフォームは、STREAMS の非同期機能と XTI/TLI ライブラリルーチンの非ブロッキングモードを組み合わせることによって、XTI/TLI イベントの非同期ネットワーク処理をサポートします。
ネットワーク転送はファイルやデバイスの入出力と同様に、プロセスサービス要求によって同期または非同期に実行できます。
同期ネットワーキングは、ファイルやデバイスの同期入出力と似ています。送信要求は write(2) インタフェースと同様に、メッセージをバッファーに入れたあとに返りますが、バッファー領域をすぐに確保できない場合、呼び出し元プロセスの実行を保留する可能性もあります。受信要求は read(2) インタフェースと同様に、必要なデータが到着するまで呼び出し元プロセスの実行を保留します。トランスポートサービスには保証された境界が存在しないため、同期ネットワーキングはほかのデバイスと関連しながらリアルタイムで動作する必要があるプロセスには不適切です。
非同期ネットワーキングは非ブロッキングサービス要求によって実現できます。コネクションが確立されるとき、データが送信されるとき、またはデータが受信されるとき、アプリケーションは非同期通知を要求できます。
非同期コネクションレスモードネットワーキングを行うには、終端を非ブロッキングサービス向けに構成して、次に、非同期通知をポーリングするか、データが転送されたときに非同期通信を受信します。非同期通知が使用された場合、実際のデータの受信は通常シグナルハンドラ内で行われます。
終端を非同期サービス向けに構成するには、t_open(3NSL) を使用して終端を確立したあと、t_bind(3NSL) を使用してその識別情報を確立します。次に、fcntl(2) インタフェースを使用して、終端に O_NONBLOCK フラグを設定します。すると、バッファー領域をすぐに確保できない場合でも、t_sndudata (3NSL) への呼び出しは -1 を返し、t_errno に TFLOW を設定します。同様に、データが存在しない場合でも、t_rcvudata(3NSL) への呼び出しは -1 を返し、t_errno に TNODATA を設定します。
アプリケーションは poll(2) を使用して終端にデータが着信したかどうかを定期的に確認したり、終端がデータを受信するまで待機したりできますが、データが着信したときには非同期通知を受信する必要があります。I_SETSIG を指定して ioctl(2) コマンドを使用すると、終端にデータが着信したときに SIGPOLL シグナルがプロセスに送信されるように要求できます。アプリケーションは複数のメッセージが単一のシグナルとして送信されないように確認する必要があります。
次の例で、アプリケーションによって選択されたトランスポートプロトコル名は protocol です。
#include <sys/types.h> #include <tiuser.h> #include <signal.h> #include <stropts.h> int fd; struct t_bind *bind; void sigpoll(int); fd = t_open(protocol, O_RDWR, (struct t_info *) NULL); bind = (struct t_bind *) t_alloc(fd, T_BIND, T_ADDR); ... /* バインディングアドレスを設定 */ t_bind(fd, bind, bin /* 終端を非ブロッキングにする */ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); /* SIGPOLL 用のシグナルハンドラを確立 */ signal(SIGPOLL, sigpoll); /* 受信データが使用できるときは SIGPOLL シグナルを要求 */ ioctl(fd, I_SETSIG, S_INPUT | S_HIPRI); ... void sigpoll(int sig) { int flags; struct t_unitdata ud; for (;;) { ... /* ud を初期化 */ if (t_rcvudata(fd, &ud, &flags) < 0) { if (t_errno == TNODATA) break; /* これ以上メッセージなし */ ... /* ほかのエラー状態を処理 */ } ... /* ud でメッセージを処理 */ }
コネクションモードサービスでは、アプリケーションはデータ転送だけではなく、コネクションの確立そのものを非同期的に行うように設定できます。操作手順は、プロセスがほかのプロセスに接続しようとしているかどうか、または、プロセスがコネクションを待機しているかどうかによって異なります。
プロセスはコネクションを非同期的に確立できます。プロセスはまず、接続用の終端を作成し、fcntl(2) を使用して、作成した終端を非ブロッキング操作向けに構成します。この終端はまた、コネクションレスデータ転送と同様に、コネクションが完了したときや以降のデータが転送されるときに非同期通知が送信されるようにも構成できます。次に、接続元プロセスは t_connect(3NSL) を使用して、転送設定を初期化します。それから、 t_rcvconnect(3NSL) を使用して、コネクションの確立を確認します。
非同期的にコネクションを待機する場合、プロセスはまず、サービスアドレスにバインドされた非ブロッキング終端を確立します。poll(2) の結果または非同期通知によってコネクション要求の着信が伝えられた場合、プロセスは t_listen(3NSL) を使用してコネクション要求を取得します。コネクションを受け入れる場合、プロセスは t_accept(3NSL) を使用します。応答用の終端を別に非同期的にデータを転送するように構成する必要があります。
#include <tiuser.h> int fd; struct t_call *call; fd = .../* 非ブロッキング終端を確立 */ call = (struct t_call *) t_alloc(fd, T_CALL, T_ADDR); .../* call 構造体を初期化 */ t_connect(fd, call, call); /* コネクション要求は現在非同期に行われている */ .../* コネクションが許可されたことの通知を受信 */ t_rcvconnect(fd, &call);
#include <tiuser.h> int fd, res_fd; struct t_call call; fd = ... /* 非ブロッキング終端を確立 */ .../*コネクション要求が到着したことの通知を受信 */ call = (struct t_call *) t_alloc(fd, T_CALL, T_ALL); t_listen(fd, &call); .../* コネクションを許可するかどうかを決定 */ res_fd = ... /* 応答のため非ブロッキング終端を確立 */ t_accept(fd, res_fd, call);
アプリケーションはリモートホストからマウントされたファイルシステムや初期化に時間がかかっているデバイスにある通常ファイルを動的に開く必要がある場合があります。しかし、このようなファイルを開く要求を処理している間、アプリケーションはほかのイベントにリアルタイムで応答できません。この問題を解決するために、SunOS ソフトウェアはファイルを実際に開く作業を別のプロセスに任せて、ファイル記述子をリアルタイムプロセスに渡します。
SunOS プラットフォームが提供する STREAMS インタフェースには、開いたファイル記述子をあるプロセスから別のプロセスに渡すメカニズムが用意されています。開いたファイル記述子を渡したいプロセスは、 コマンド引数 I_SENDFD を指定して ioctl(2) を使用します。ファイル記述子を取得したいプロセスは、コマンド引数 I_RECVFD を指定して ioctl(2) を使用します。
次の例では、親プロセスはまず、テストファイルについての情報を出力し、パイプを作成します。親プロセスは次に、テストファイルを開いて、開いたファイル記述子をパイプ経由で親プロセスに返すような子プロセスを作成します。そのあと、親プロセスは新しいファイル記述子の状態情報を表示します。
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stropts.h> #include <stdio.h> #define TESTFILE "/dev/null" main(int argc, char *argv[]) { int fd; int pipefd[2]; struct stat statbuf; stat(TESTFILE, &statbuf); statout(TESTFILE, &statbuf); pipe(pipefd); if (fork() == 0) { close(pipefd[0]); sendfd(pipefd[1]); } else { close(pipefd[1]) recvfd(pipefd[0]); } } sendfd(int p) { int tfd; tfd = open(TESTFILE, O_RDWR); ioctl(p, I_SENDFD, tfd); } recvfd(int p) { struct strrecvfd rfdbuf; struct stat statbuf; char fdbuf[32]; ioctl(p, I_RECVFD, &rfdbuf); fstat(rfdbuf.fd, &statbuf); sprintf(fdbuf, "recvfd=%d", rfdbuf.fd); statout(fdbuf, &statbuf); } statout(char *f, struct stat *s) { printf("stat: from=%s mode=0%o, ino=%ld, dev=%lx, rdev=%lx\n", f, s->st_mode, s->st_ino, s->st_dev, s->st_rdev); fflush(stdout); }
次の表は、XTI/TLI 関連のすべての状態遷移を説明します。
次の表に、XTI/TLI の状態遷移で使用される状態とサービスタイプを定義します。
表 8–1 XTI/TLI 状態遷移とサービスタイプ
状態 |
説明 |
サービスタイプ |
---|---|---|
T_UNINIT |
初期化が行われていない - インタフェースの初期状態と終了状態 |
|
T_UNBND |
初期化されているが、バインドされていない |
|
T_IDLE |
コネクションが確立されていない |
|
T_OUTCON |
クライアントに対する送信コネクションが保留中 |
|
T_INCON |
サーバーに対する受信コネクションが保留中 |
|
T_DATAXFER |
データ転送 |
|
T_OUTREL |
送信正常型解放 (正常型解放要求待ち) |
|
T_INREL |
受信正常型解放 (正常型解放要求の送信待ち) |
|
次の表に示す送信イベントは、指定されたトランスポートルーチンから返される状態に対応しており、このようなイベントにおいて、これらのルーチンはトランスポートプロバイダに要求または応答を送信します。この表で示すイベントの一部 (accept など) は、発生した時点におけるコンテキストによって意味が変わります。これらのコンテキストは、次の変数の値に基づきます。
ocnt – 未処理のコネクション要求の数
fd – 現在のトランスポート終端のファイル記述子
resfd – コネクションが受け入れられるトランスポート終端のファイル記述子
イベント |
説明 |
サービスタイプ |
---|---|---|
opened |
t_open(3NSL) が正常に終了した |
T_COTS、 T_COTS_ORD、T_CLTS |
bind |
t_bind(3NSL) が正常に終了した |
T_COTS、 T_COTS_ORD、T_CLTS |
t_optmgmt(3NSL) が正常に終了した |
T_COTS、 T_COTS_ORD、T_CLTS |
|
unbind |
t_unbind(3NSL) が正常に終了した |
T_COTS、 T_COTS_ORD、T_CLTS |
closed |
t_close(3NSL) が正常に終了した |
T_COTS、 T_COTS_ORD、T_CLT |
connect1 |
同期モードの t_connect(3NSL) が正常に終了した |
T_COTS 、T_COTS_ORD |
connect2 |
非同期モードの t_connect(3NSL) で TNODATA エラーが発生したか、あるいは、切断要求がトランスポート終端に着信したことにより TLOOK エラーが発生した |
T_COTS、 T_COTS_ORD |
accept1 |
ocnt == 1、fd == resfd を指定した t_accept(3NSL) が正常に終了した |
T_COTS、T_COTS_ORD |
accept2 |
ocnt== 1、fd!= resfd を指定した (t_accept 3NSL) が正常に終了した |
T_COTS、 T_COTS_ORD |
accept3 |
ocnt> 1を指定した t_accept(3NSL) が正常に終了した |
T_COTS、T_COTS_ORD |
snd |
t_snd(3NSL) が正常に終了した |
T_COTS、 T_COTS_ORD |
snddis1 |
ocnt <= 1 を指定した t_snddis(3NSL) が正常に終了した |
T_COTS、T_COTS_ORD |
snddis2 |
ocnt> 1 を指定した t_snddis(3NSL) が正常に終了した |
T_COTS、T_COTS_ORD |
sndrel |
t_sndrel(3NSL) が正常に終了した |
T_COTS_ORD |
sndudata |
t_sndudata(3NSL) が正常に終了した |
T_CLTS |
受信イベントは、指定されたルーチンが正常に終了したときに発生します。これらのルーチンは、トランスポートプロバイダからのデータやイベント情報を返します。ルーチンから返された値に直接関連付けられていない唯一の受信イベントは pass_conn であり、このイベントはコネクションがほかの終端に移行するときに発生します。終端で XTI/TLI ルーチンを呼び出さなくても、コネクションを渡している終端ではこのイベントが発生します。
次の表では、rcvdis イベントは、終端上の未処理のコネクション要求の数を示す ocnt の値によって区別されます。
表 8–3 受信イベント
イベント |
説明 |
サービスタイプ |
---|---|---|
listen |
t_listen(3NSL) が正常に終了した |
T_COTS、 T_COTS_ORD |
rcvconnect |
t_rcvconnect(3NSL) が正常に終了した |
T_COTS、 T_COTS_ORD |
rcv |
t_rcv(3NSL) が正常に終了した |
T_COTS、 T_COTS_ORD |
rcvdis1 |
onct <= 0 を指定した t_rcvdis(3NSL) rcvdis1t_rcvdis() が正常に終了した |
T_COTS、 T_COTS_ORD |
rcvdis2 |
ocnt == 1 を指定した t_rcvdis(3NSL) が正常に終了した |
|
rcvdis3 |
ocnt> 1 を指定した t_rcvdis(3NSL) が正常に終了した |
|
rcvrel |
t_rcvrel(3NSL) が正常に終了した |
|
rcvudata |
t_rcvudata(3NSL) が正常に終了した |
|
rcvuderr |
t_rcvuderr(3NSL) が正常に終了した |
|
pass_conn |
渡されたコネクションを受信した |
|
状態テーブルは、XTI/TLI の状態遷移を示します。状態テーブルの列には現在の状態を、行には現在のイベントを、行と列の交差する部分では次に発生する状態を示しています。次に発生する状態が空の場合は、状態とイベントの組み合わせが無効であることを意味します。また次に発生する状態には、動作一覧が示されている場合もあります。動作は、指定された順序で実行する必要があります。
状態テーブルを見る場合は、次の点を理解してください。
t_close(3NSL) はコネクション型トランスポートプロバイダ用に確立されたコネクションを終了します。コネクションの終了が正常型または放棄型のどちらで行われるかは、トランスポートプロバイダがサポートするサービスタイプによって決まります。詳細は、t_getinfo(3NSL) のマニュアルページを参照してください。
トランスポートユーザーがシーケンス外のインタフェース呼び出しを発行すると、そのインタフェースは失敗し、t_errno は TOUTSTATE に設定されます。この状態は変更できません。
t_connect(3NSL) のあとにエラーコード TLOOK または TNODATA が返されると、状態が変化する可能性があります。次の状態テーブルでは、XTI/TLI を正しく使用していることを前提としています。
インタフェースのマニュアルページに特に指定されていない限り、ほかのトランスポートエラーによって状態が変化することはありません。
サポートインタフェース t_getinfo(3NSL)、t_getstate(3NSL)、t_alloc(3NSL)、t_free(3NSL)、t_sync(3NSL)、t_look(3NSL)、および t_error(3NSL) は状態に影響しないため、この状態テーブルから除外されています。
次の表の状態遷移には、トランスポートユーザーが行う必要がある動作が記載されているものもあります。各動作は、次のリストから求められた数字によって表現されます。
未処理のコネクション要求の数に 0 を設定する
未処理のコネクション要求の数を 1 だけ増やす
未処理のコネクション要求の数を 1 だけ減らす
別のトランスポート終端にコネクションを渡す (t_accept(3NSL) のマニュアルページを参照)
次の表に、終端の確立の状態を示します。
表 8–4 コネクション確立時における状態
イベント/状態 |
|
|
|
---|---|---|---|
opened |
T_UNBND |
|
|
bind |
|
T_IDLE[1] |
|
|
|
T_IDLE |
|
unbind |
|
|
T_UNBND |
closed |
|
T_UNINIT |
|
次の表に、コネクションレスモードにおけるデータの転送の状態を示します。
表 8–5 コネクションモードにおける状態 — その 1
イベント/状態 |
T_IDLE |
T_OUTCON |
T_INCON |
T_DATAXFER |
---|---|---|---|---|
connect1 |
T_DATAXFER |
|
|
|
connect2 |
T_OUTCON |
|
|
|
rcvconnect |
|
T_DATAXFER |
|
|
listen |
T_INCON [2] |
|
T_INCON [2] |
|
accept1 |
|
|
T_DATAXFER [3] |
|
accept2 |
|
|
T_IDLE [3] [4] |
|
accept3 |
|
|
T_INCON [3] [4] |
|
snd |
|
|
|
T_DATAXFER |
rcv |
|
|
|
T_DATAXFER |
snddis1 |
|
T_IDLE |
T_IDLE [3] |
T_IDLE |
snddis2 |
|
|
T_INCON [3] |
|
rcvdis1 |
|
T_IDLE |
|
T_IDLE |
rcvdis2 |
|
|
T_IDLE [3] |
|
rcvdis3 |
|
|
T_INCON [3] |
|
sndrel |
|
|
|
T_OUTREL |
rcvrel |
|
|
|
T_INREL |
pass_conn |
T_DATAXFER |
|
|
|
T_IDLE |
T_OUTCON |
T_INCON |
T_DATAXFER |
|
closed |
T_UNINIT |
T_UNINIT |
T_UNINIT |
T_UNINIT |
次の表に、コネクションモードにおけるコネクションの確立、コネクションの解放、およびデータの転送の状態を示します。
表 8–6 コネクションモードにおける状態 — その 2
イベント/状態 |
T_OUTREL |
T_INREL |
T_UNBND |
---|---|---|---|
connect1 |
|
|
|
connect2 |
|
|
|
rcvconnect |
|
|
|
listen |
|
|
|
accept1 |
|
|
|
accept2 |
|
|
|
accept3 |
|
|
|
snd |
|
T_INREL |
|
rcv |
T_OUTREL |
|
|
snddis1 |
T_IDLE |
T_IDLE |
|
snddis2 |
|
|
|
rcvdis1 |
T_IDLE |
T_IDLE |
|
rcvdis2 |
|
|
|
rcvdis3 |
|
|
|
sndrel |
|
T_IDLE |
|
rcvrel |
T_IDLE |
|
|
pass_conn |
|
| |
optmgmt |
T_OUTREL |
T_INREL |
T_UNBND |
closed |
T_UNINIT |
T_UNINIT |
|
次の表に、コネクションレスモードにおける状態を示します。
表 8–7 コネクションレスモードにおける状態
イベント/状態 |
T_IDLE |
---|---|
snudata |
T_IDLE |
rcvdata |
T_IDLE |
rcvuderr |
T_IDLE |
XTI/TLI が提供する一連のサービスは、多くのトランスポートプロトコルに共通であり、XTI/TLI を使用すると、アプリケーションはプロトコルに依存しない処理が可能になります。ただし、すべてのトランスポートプロトコルが XTI/TLI をサポートしているわけではありません。ソフトウェアをさまざまなプロトコル環境で実行する必要がある場合は、共通のサービスだけを使用してください。
次に示すサービスはすべてのトランスポートプロトコルに共通とは限らないので、注意してください。
コネクションモードのサービスでは、すべてのトランスポートプロバイダで転送サービスデータユニット (TSDU) がサポートされるとは限りませんので、コネクションの際に論理的なデータ境界が保たれることを前提としないでください。
プロトコルおよび実装に固有なサービスの制限は、t_open(3NSL) および t_getinfo(3NSL) のルーチンによって返されます。これらの制限に基づいてバッファーを割り当て、プロトコルに固有なトランスポートアドレスおよびオプションを格納してください。
ユーザーデータを送信するときには、t_connect(3NSL) や t_snddis(3NSL) などのコネクション要求や切断要求を使用しないでください。これは、すべてのトランスポートプロトコルがこの方法を使用できるわけではないためです。
t_listen(3NSL) で使用される t_call 構造体のバッファーには、コネクション確立時にクライアントが送信するデータを格納できるだけの大きさが必要です。現在のトランスポートプロバイダのアドレス、オプション、およびユーザーデータを格納できるように、t_alloc(3NSL) に T_ALL 引数を使用して最大のバッファーサイズを設定してください。
クライアント側の終端では、t_bind(3NSL) のプロトコルアドレスを指定しないでください。トランスポートプロバイダがトランスポート終端に適切なプロトコルアドレスを割り当て、サーバーは、トランスポートプロバイダの名前空間を知らなくても、t_bind(3NSL) のプロトコルアドレスを取得できなければなりません。
トランスポートアドレスの形式を仮定しないでください。また、トランスポートアドレスをプログラム内定数にしないでください。トランスポート選択の詳細については、第 9 章「トランスポート選択と名前からアドレスへのマッピング」を参照してください。
t_rcvdis(3NSL) に関連付けられた理由コードはプロトコルに依存します。プロトコルに依存しないことが重要である場合、これらの理由コードを使用しないでください。
t_rcvuderr(3NSL) のエラーコードはプロトコルに依存します。プロトコルに依存しないことが重要である場合、これらのエラーコードを使用しないでください。
プログラム内にデバイス名をコーディングしないでください。デバイスノードは、特定のトランスポートプロバイダを指定し、プロトコルに依存します。トランスポート選択の詳細については、第 9 章「トランスポート選択と名前からアドレスへのマッピング」を参照してください。
複数のプロトコル環境で実行予定のプログラムでは、t_sndrel(3NSL) および t_rcvrel(3NSL) が提供するコネクションモードサービスの正常型解放機能 (オプション) を使用しないでください。正常型解放機能は、すべてのコネクション型トランスポートプロトコルでサポートされているわけではありません。この機能を使用すると、解放型システムと正常に通信できなくなることがあります。
XTI/TLI とソケットとでは、同じタスクでも処理方法が異なります。どちらも機能的に似ているメカニズムとサービスを提供しますが、ルーチンや低レベルのサービスには 1 対 1 の互換性があるわけではありません。アプリケーションを移植しようとする場合は、XTI/TLI インタフェースとソケットベースのインタフェースとの間の類似点や相違点をよく知る必要があります。
トランスポートの独立性に関しては、次の問題があります。これらの問題は、RPC アプリケーションにも関係があります。
特権ポート (Privileged ports) – 特権ポートは、TCP/IP インターネットプロトコルのバークレー版ソフトウェア配布(BSD) を実装するための機能です。特権ポートは移植可能ではありません。特権ポートの概念は、トランスポートに依存しない環境ではサポートされません。
隠されたアドレス (Opaque addresses) – トランスポートに依存しない形態では、ホストを指定するアドレス部分とそのホスト上でサービスを指定するアドレス部分とを区別できません。ネットワークサービスのホストアドレスを認識できることを前提としたコードは必ず変更してください。
次の表に、XTI/TLI インタフェースとソケットインタフェースのおおまかな対応関係を示します。コメント列には、相違点を示します。コメントがない場合、インタフェースがほとんど同じであるか、または一方のインタフェースに相当する関数が存在しないことを意味します。
表 8–8 TLI 関数とソケット関数の対応表
XNS 5 (Unix98) 標準に新規の XTI インタフェースが導入されました。これらの XTI インタフェースについて、次に簡単に説明します。詳細については、関連するマニュアルページを参照してください。なお、TLI ユーザーはこれらのインタフェースを使用できません。分散および集中データ転送インタフェースは次のとおりです。
XTI ユーティリティインタフェース t_sysconf(3NSL) は構成可能な XTI 変数を取得します。t_sndreldata(3NSL) インタフェースは、ユーザーデータを使用して正常型解放を発行したり、正常解放に応答したりします。t_rcvreldata(3NSL) は、正常型解放の指示やユーザーデータが含まれる確認を受信します。
追加のインタフェースである t_sndreldata(3NSL) および t_rcvreldata(3NSL) は「最小 OSI」と呼ばれる特定のトランスポートだけで使用されますが、最小 OSI は Solaris プラットフォームではサポートされません。これらのインタフェースは、インターネットトランスポート (TCP または UDP) と併用することはできません。