コネクションモードサービスの基本概念をクライアントプログラムとそのサーバープログラムを使用して説明します。例ではセグメントを使用しています。
例の中ではクライアントがサーバー処理への接続を確立します。サーバーはクライアントへファイルの転送を行います。クライアントはそのファイルを受信し、標準出力へ書き出します。
クライアントとサーバーの接続が行われる前に、それぞれが t_open(3NSL) を使用してトランスポートプロバイダ (トランスポートエンドポイント) へのローカル接続を行い、t_bind(3NSL) によりその識別 (またはアドレス) を確立する必要があります。
多くのプロトコルでは XTI/TLI で定義されているサービスのサブセットの実行が可能です。各トランスポートプロバイダは提供するサービスを決定したり、サービスを制限したりする特性を持っています。トランスポート特性を定義しているデータは t_info 構造体で t_open(3NSL) によって返されます。表 3-6 は t_info 構造体のフィールドの一覧です。
表 3-6 t_info 構造体
フィールド |
内容 |
---|---|
addr |
トランスポートアドレスの最大サイズ |
options |
トランスポートユーザーとトランスポートプロバイダ間を送信可能なプロトコル固有オプションの最大バイト数 |
tsdu |
コネクションモードまたはコネクションレスモードで送信可能な最大メッセージサイズ |
etsdu |
トランスポート接続によって送信可能な優先データ最大メッセージサイズ |
connect |
接続の確立時にユーザー間で送信可能なユーザーデータの最大バイト数 |
discon |
接続の放棄型解放時にユーザー間で送信可能なユーザーデータの最大バイト数 |
servtype |
トランスポートプロバイダによりサポートされているサービスのタイプ |
XTI/TLI により定義されている 3 つのサービスタイプは以下の通りです。
T_COTS - トランスポートプロバイダはコネクションモードサービスをサポートしているが 、正常型解放機能を提供していない。接続の終了は放棄型解放によって行われ、送信されていないデータは破棄される。
T_COTS_ORD - トランスポートプロバイダは正常型解放機能を持つコネクションモードサービスを提供する。
T_CLTS - トランスポートプロバイダはコネクションレスモードサービスを提供する。
t_open(3NSL) により識別されるトランスポートプロバイダに関連付けが行えるのは 1 つのサービスのみです。
t_open(3NSL) はトランスポートエンドポイントのデフォルトプロバイダ特性を戻します。エンドポイントが開かれると変化する特性もあります。これはネゴシエーションを行ったオプションで発生します (オプションのネゴシエーションについてはこの章の以降のページで説明する)。t_getinfo(3NSL) はトランスポートエンドポイントの現在の特性を戻します。
ユーザーが選択したトランスポートプロバイダとエンドポイントを確立した後に、クライアントおよびサーバーは識別の確定を行う必要があります。これを行うのがトランスポートエンドポイントへトランスポートアドレスのバインドを行う t_bind(3NSL) です。サーバーの場合、このルーチンが接続要求待機にエンドポイントが使用されていることをトランスポートプロバイダへ通知します。
t_optmgmt(3NSL) はローカル管理フェーズ中に使用できます。ユーザーによるトランスポートプロバイダとのプロトコルオプション値のネゴシエーションを可能にします。各トランスポートプロトコルは quality-of-service パラメータなど独自のネゴシエーション可能なプロトコルオプションを定義します。オプションがプロトコル固有のものであるため、特定のプロトコル用に作成されたプログラムだけがこの機能を使用できます。
これらの機能の詳細を説明するためにクライアントおよびサーバーのローカル管理の必要条件を例に示します。例 3-3 ではクライアントプログラムで必要とされる定義を行ってから必要なローカル管理を行うステップを示しています。
#include <stdio.h> #include <tiuser.h> #include <fcntl.h> #define SRV_ADDR 1 /* サーバーのアドレス*/ main() { int fd; int nbytes; int flags = 0; char buf[1024]; struct t_call *sndcall; extern int t_errno; if ((fd = t_open("/dev/exmp", O_RDWR, (struct t_info *),NULL)) == -1) { t_error("t_open failed"); exit(1); } if (t_bind(fd, (struct t_bind *) NULL, (struct t_bind *) NULL) == -1) { t_error("t_bind failed"); exit(2); }
t_open(3NSL) の最初の引数はトランスポートプロトコルの識別を行うファイルシステムオブジェクトのパスです。/dev/exmp は汎用通信ベースのトランスポートプロトコルの識別を行う特別なファイル名の例です。2 つ目の引数 O_RDWR は読み書き可能なオープンを指定しています。3 つ目の引数は t_info 構造体を指定し、トランスポートのサービス特性が戻されます。
このデータはプロトコルに依存しないソフトウェアに適しています (「プロトコルに依存しない処理に関する指針」を参照)。この例では NULL ポインタが渡されています。例 3-3 においては、トランスポートプロバイダは以下の特性を持っている必要があります。
例は正常型解放を使用しているためトランスポートプロバイダは T_COTS_ORD
サービスタイプをサポートする。
トランスポートプロバイダはプロトコル固有オプションを必要としない。
ユーザーが T_COTS_ORD
以外のサービスを必要とする場合、別のトランスポートプロバイダをオープンすることが可能です。T_CLTS
サービス要請についての例は 「読み取り/書き込み用インタフェース」 で説明しています。
t_open(3NSL) は後に続くすべての XTI/TLI 関数呼び出しで使用されるトランスポートエンドポイントファイルハンドルを戻します。識別子はトランスポートプロトコルファイルをオープンして得られるファイル記述子です (open(2) を参照)。
クライアントはエンドポイントにアドレスを割り当てるため t_bind(3NSL) を呼び出します。t_bind(3NSL) の最初の引数はトランスポートエンドポイントハンドルです。2 つ目の引数はエンドポイントへバインドするアドレスを示す t_bind 構造体を指定します。3 つ目の引数はプロバイダがバインドしたアドレスを示す t_bind 構造体を指定します。
クライアントのアドレスは多くの場合、他の処理がアクセスを行わないため重要性を持ちません。そのため t_bind(3NSL) への 2 つ目および 3 つ目の引数は NULL です。2 つ目の NULL 引数がユーザー用のアドレスの選択のためトランスポートプロバイダへ指示を行います。
t_open(3NSL) または t_bind(3NSL) が失敗した場合、プログラムは stderr による適切なエラーメッセージを表示するために t_error(3NSL) を呼び出します。整数型の外部変数 t_error(3NSL) はエラー値に割り当てられます。エラー値のセットが tiuser.h に定義されています。
t_error(3NSL) は perror(3C) と類似しています。トランスポート機能エラーがシステムエラーの場合、t_errno(3NSL) は TSYSERR に設定され、errno は適切な値に設定されます。
サーバーの例においても接続要求待機を行うためにトランスポートエンドポイントを確立する必要があります。 例 3-4 では定義とローカル管理を行うステップを例で示しています。
#include <tiuser.h> #include <stropts.h> #include <fcntl.h> #include <stdio.h> #include <signal.h> #define DISCONNECT -1 #define SRV_ADDR 1 /* サーバーのアドレス*/ int conn_fd; /* ここで接続の確立*/ extern int t_errno; main() { int listen_fd; /* トランスポートエンドポイント待機*/ struct t_bind *bind; struct t_call *call; if ((listen_fd = t_open("/dev/exmp", O_RDWR, (struct t_info *) NULL)) == -1) { t_error("t_open failed for listen_fd"); exit(1); } if ((bind = (struct t_bind *)t_alloc( listen_fd, T_BIND, T_ALL)) == (struct t_bind *) NULL) { t_error("t_alloc of t_bind structure failed"); exit(2); } bind->qlen = 1; /* * プロバイダのアドレスの形式を推測するため * このプログラムはトランスポートに依存する */ bind->addr.len = sizeof(int); *(int *) bind->addr.buf = SRV_ADDR; if (t_bind (listen_fd, bind, bind) < 0 ) { t_error("t_bind failed for listen_fd"); exit(3); } #if (!defined(_XOPEN_SOURCE) ||(_XOPEN_SOURCE_EXTENDED -0 != 1)) /* * 正しいアドレスがバインドされているかどうか * * XTI の場合このテストは不要 */ if (bind->addr.len != sizeof(int) || *(int *)bind->addr.buf != SRV_ADDR) { fprintf(stderr, "t_bind bound wrong address¥n"); exit(4); } #endif
クライアント同様、サーバーはまず選択したトランスポートプロバイダとトランスポートエンドポイントを確立するため t_open(3NSL) を呼び出します。エンドポイント listen_fd は接続要求待機を行うために使用されます。
次にサーバーはアドレスをエンドポイントへバインドします。アドレスは各クライアントがサーバーへアクセスする際に使用されます。2 つ目の引数はエンドポイントへバインドするアドレスを指定する t_bind 構造体を指します。t_bind 構造体は以下の形式です。
struct t_bind { struct netbuf addr; unsigned qlen; }
addr はバインドされたアドレスを示し、qlen は未処理の接続要求の最大件数を指定します。すべての XTI 構造および定数定義は xti.h を介しアプリケーションプログラムで使用可能になります。すべての TLI 構造体および定数定義は tiuser.h に格納されます。
アドレスは以下の形式で netbuf 構造体で指定されます。
struct netbuf { unsigned int maxlen; unsigned int len; char *buf; }
maxlen はバッファーの最大長をバイト単位で指定、len はバッファー内のデータのバイト長を指定、そして buf はデータを格納しているバッファーを指します。
t_bind 構造体では、データはトランスポートアドレスを識別します。qlen は待機可能な接続要求の最大数を指定します。qlen の値が正の場合、エンドポイントを接続要求の待機に使用することが可能となります。t_bind(3NSL) は、ただちにバイトされたアドレスに各接続要求の待機を行うようトランスポートプロバイダに指示します。サーバーは 1 つずつ接続要求の待機を解除し、受け付けまたは拒否を行う必要があります。次の接続要求を受信する前に 1 つの接続要求の処理および応答を行うサーバーの場合、qlen の値は 1 が適切です。応答を行う前に複数の接続要求の待機を解除するサーバーの場合は、より長い待ち行列を指定する必要があります。この例のサーバーでは、一度に 1 つの接続要求の処理しか行わないため、qlen は 1 に設定してあります。
t_alloc(3NSL) は t_bind 構造体を割り当てるために呼び出されます。t_alloc(3NSL) には 3 つの引数があります。トランスポートエンドポイントのファイル記述子、割り当てる構造体の識別子、そして割り当てる netbuf バッファーを指定するフラグです ( netbuf バッファーを使用する場合)。T_ALL はすべての netbuf バッファーの割り当てを指定し、この例では addr バッファーが割り当てられる要因となります。バッファーのサイズは自動的に決定され、maxlen に格納されます。
各トランスポートプロバイダは個別にアドレス空間を管理します。トランスポートプロバイダには複数のトランスポートエンドポイントに同じトランスポートアドレスをバインドするものと、各エンドポイントに固有のアドレスをバインドするものがあります。XTI と TLI のアドレスバインド方法には大きく異なる部分があります。
TLI のルールでは、プロバイダが要求されたアドレスのバインドが可能かを判定します。バインドが行えない場合、そのアドレス空間で別の有効アドレスを捜しトランスポートエンドポイントにバインドします。アプリケーションプログラムは、バインドされたアドレスが事前にクライアントに通知されたものと同一であることをチェックする必要があります。XTI ではプロバイダが要求されたアドレスのバインドを行えないと判定した場合、t_bind(3NSL) をエラーで終了します。
t_bind(3NSL) が成功した場合、プロバイダは接続要求の待機を開始し、通信の次のフェーズに移ります。
このフェーズでは XTI/TLI は異なる処理をクライアントおよびサーバーに要求します。クライアント t_connect(3NSL) を使用して指定されたサーバーに接続要求を行うことにより接続の確立を開始します。 サーバーはクライアントの要求を t_listen(3NSL) を呼び出して受信します。サーバーはクライアントの要求を受け付け、または拒否しなければなりません。接続を確立するため t_accept(3NSL) を呼び出すか、または t_snddis(3NSL) を呼び出し、要求を拒否します。クライアントは t_connect(3NSL) が返されることにより結果を認識をします。
TLI は接続の確立時に、すべてのトランスポートプロバイダにはサポートされていない可能性のある 2 つの機能をサポートしています。
接続の確立を行っている間のクライアントとサーバーのデータ転送。クライアントは接続要求時、サーバーへデータを送信できます。このデータは t_listen(3NSL) によってサーバーに渡されます。サーバーは接続要求の受け付け、または拒否を行う時にクライアントにデータを送信することが可能です。t_open(3NSL) により返される接続特性が 2 ユーザー間で (データの転送が可能な場合) 転送可能なデータのサイズを決定します。
プロトコルオプションのネゴシエーション。クライアントはトランスポートプロバイダおよび (または) リモートユーザーへ任意のプロトコルオプションを指定できます。XTI/TLI はローカルおよびリモート両方のオプションネゴシエーションをサポートします。オプションネゴシエーションはプロトコル固有の機能です。
これらの機能はプロトコルに依存するソフトウェアを作成します (「プロトコルに依存しない処理に関する指針」を参照)。
例 3-5 はクライアントの接続確立を行うためのコード例です。
if ((sndcall = (struct t_call *) t_alloc(fd, T_CALL, T_ADDR)) == (struct t_call *) NULL) { t_error("t_alloc failed"); exit(3); } /* * プロバイダのアドレスの形式を認知していると推測されるため、 * このプログラムはトランスポートに依存します。 */ sndcall->addr.len = sizeof(int); *(int *) sndcall->addr.buf = SRV_ADDR; if (t_connect( fd, sndcall, (struct t_call *) NULL) == -1 ) { t_error("t_connect failed for fd"); exit(4); }
t_connect(3NSL) 呼び出しはサーバに接続するために使用されます。t_connect(3NSL) の 1 つ目の引数は、クライアント側のエンドポイントを特定します。2 つ目の引数は、宛先サーバーを特定する t_call 構造体を指します。この構造体は以下の形式です。
struct t_call { struct netbuf addr; struct netbuf opt; struct netbuf udata; int sequence; }
addr はサーバーのアドレスを特定します。opt は接続へプロトコル固有オプションを指定します。udata はサーバーへの接続要求とともに送信可能なユーザーデータを特定します。sequence フィールドの t_connect(3NSL) における役割はありません。コーディング例ではサーバーのアドレスのみが渡されます。
t_alloc(3NSL) は t_call 構造体を動的に割り当てます。t_alloc(3NSL) の 3 つ目の引数、T_ADDR は netbuf バッファーの割り当てがシステムに必要であることを示します。サーバーのアドレスは buf にコピーされ、len は同様に適切な値に設定されます。
t_connect(3NSL) の 3 つ目の引数は、新たに確立された接続の情報を取り出すのに使用でき、サーバーによって接続要求の応答とともに送られたユーザーデータの返送が可能です。ここでは 3 つ目の引数はクライアントにより NULL に設定されています。接続は t_connect(3NSL) の応答が成功している場合に確立されます。サーバーが接続要求を拒否した場合、t_connect(3NSL) は t_errno を TLOOK に設定します。
TLOOK エラーには特殊な性質があります。XTI/TLI ルーチンにエンドポイント上の予測されていない非同期トランスポートイベントによる割り込みが発生した場合 TLOOK が設定されます。この場合 TLOOK は XTI/TLI ルーチンについてのエラーのレポートを行わず、また保留イベントのため通常のルーチン処理が行われません。表 3-7 は XTI/TLI で定義されているイベントの一覧です。
表 3-7 非同期エンドポイントイベント
名称 |
説明 |
---|---|
T_LISTEN |
接続要求がトランスポートエンドポイントに着信 |
T_CONNECT |
前の接続要求の確認着信 (サーバーが接続要求を受け付けた場合に生成) |
T_DATA |
ユーザーデータ着信 |
T_EXDATA |
優先ユーザーデータ着信 |
T_DISCONNECT |
接続の中止または接続要求の拒否の着信の通知 |
T_ORDERL |
接続の正常型解放要求の着信 |
T_UDERR |
最後に着信したデータグラム内のエラー通知 (「読み取り/書き込み用インタフェース」を参照) |
「状態遷移」内の状態テーブルでは、各状態で発生する可能性のあるイベントを表にしています。t_look(3NSL) は TLOOK エラーが発生した場合にユーザーによるイベントの判定を可能にします。例の中では、接続要求が拒否された場合、クライアントは終了します。
クライアントが t_connect(3NSL) を呼び出した場合、サーバーのトランスポートエンドポイントへ接続要求が送られます。サーバーは各クライアントの接続要求を受け付け、接続のサービスを提供するため処理生成を行います。
if ((call = (struct t_call *) t_alloc(listen_fd, T_CALL, T_ALL)) == (struct t_call *) NULL) { t_error("t_alloc of t_call structure failed"); exit(5); } while(1) { if (t_listen( listen_fd, call) == -1) { t_error("t_listen failed for listen_fd"); exit(6); } if ((conn_fd = accept_call(listen_fd, call)) != DISCONNECT) run_server(listen_fd); }
サーバーは t_call 構造を割り当て、閉ループを行います。ループは接続要求のため t_listen(3NSL) でブロックします。要求が着信した時点で、サーバーは接続要求を受け付けるため accept_call() を呼び出します。accept_call() は接続を代替トランスポートエンドポイントで (下記で説明されている方法で) 受け付け、エンドポイントのハンドルを返します (conn_fd はグローバル変数)。接続が代替エンドポイントによって受け付けられるため、サーバーは引き続き元のエンドポイントの待機を行うことが可能です。呼び出しがエラーなしに受け付けられた場合、run_server が接続のサービスを提供するために起動されます。
XTI/TLI ではこのような処理のブロックを防ぐルーチンのために非同期モードをサポートします (「拡張機能」を参照)。
接続要求が着信すると、サーバーは例 3-6 で示されるようにクライアントの要求を受け付けるため accept_call() を呼び出します。
このサーバーは一度に 1 つの接続要求の処理しか行う必要がないことを暗黙の前提としています。これは通常のサーバーではあまりない状況です。複数の接続要求の同時処理を行うために必要なコードは XTI/TLI のイベントメカニズムのため、複雑です (このようなサーバーの場合は、「高度なプログラム例」を参照)。
accept_call(listen_fd, call) int listen_fd; struct t_call *call; { int resfd; if ((resfd = t_open("/dev/exmp", O_RDWR, (struct t_info *) NULL)) == -1) { t_error("t_open for responding fd failed"); exit(7); } if (t_bind(resfd,(struct t_bind *) NULL, (struct t_bind *NULL)) == -1) { t_error("t_bind for responding fd failed"); exit(8); } if (t_accept(listen_fd, resfd, call) == -1) { if (t_errno == TLOOK) { /*切断である必要あり*/ if (t_rcvdis(listen_fd,(struct t_discon *) NULL) == -1) { t_error("t_rcvdis failed for listen_fd"); exit(9); } if (t_close(resfd) == -1) { t_error("t_close failed for responding fd"); exit(10); } /*上に戻り、他の呼び出しの待機*/ return(DISCONNECT); } t_error("t_accept failed"); exit(11); } return(resfd); }
accept_call() には 2 つの引数があります。
listen_fd | 接続要求が着信したトランスポートエンドポイントのファイルハンドル |
call | 接続要求に関するすべての情報が格納された t_call 構造体を指す |
サーバーはトランスポートプロバイダのクローンのデバイス特殊ファイルを開き、アドレスをバインドすることによって、最初に別のトランスポートエンドポイントをオープンします。NULL はプロバイダによってバインドされたアドレスを返さないよう指定します。新しいトランスポートエンドポイント、resfd がクライアントの接続要求を受け付けます。
t_accept(3NSL) の最初の 2 つの引数は待機を行うトランスポートエンドポイントおよび接続が受け付けられるエンドポイントをそれぞれ指定します。待機を行うエンドポイント上で接続を受け付けると、接続の間、他のクライアントはサーバーへアクセスすることができません。
t_accept(3NSL) の 3 つ目の引数は、接続要求を格納している t_call 構造体を指します。この構造体は呼び出しを行っているユーザーのアドレス、および t_listen(3NSL) によって戻されたシーケンス番号が格納されています。サーバーが複数の接続要求の待機を行った場合、シーケンス番号は重要です。「拡張機能」ではこの例を説明しています。t_call 構造体はプロトコルオプションおよびクライアントに渡すユーザーデータも識別します。このトランスポートプロバイダはプロトコルオプションまたは接続中のユーザーデータの転送をサポートしないため、t_listen(3NSL) によって戻された t_call 構造体は変更なしに t_accept(3NSL) へ渡されます。
この例は簡略化されています。サーバーは t_open(3NSL) または t_bind(3NSL) 呼び出しが失敗した時点で終了します。exit(2) は listen_fd のトランスポートエンドポイントを閉じ、クライアントへは切断要求が送信されます。クライアントの t_connect(3NSL) 呼び出しは失敗し、t_errno は TLOOK に設定されます。
t_accept(3NSL) は、待機しているエンドポイント上で接続が受け付けられる前に非同期イベントが発生し、t_errno が TLOOK に設定された場合失敗する可能性があります。この状態では待機を行っている接続要求が 1 つでも、切断要求のみが送信可能であることが表 3-8 で示されています。このイベントはクライアントが事前に行った接続の要求を取り消した場合に発生する可能性があります。切断要求が着信した場合、サーバーは t_rcvdis(3NSL) を呼び出し応答を行う必要があります。このルーチンの引数は、切断要求のデータを取り出すために使用される t_discon 構造体へのポインタです。この例ではサーバーは NULL を渡します。
切断要求を受け取った後、accept_call は応答を行うトランスポートエンドポイントを閉じ、 DISCONNECT を戻してサーバーに接続がクライアントによって切断されたことを伝えます。その後サーバーは他の接続要求の待機を行います。
図 3-4 はサーバーの接続確立の方法を示します。
トランスポートの接続は新しく応答を行うエンドポイント上で確立され、待機を行ったエンドポイントは他の接続要求の待機を行うため解放されます。
接続が確立された後、サーバーおよびクライアントは t_snd(3NSL) および t_rcv(3NSL) を使用してデータの転送が行えるようになります。XTI/TLI はこれ以降、クライアントおよびサーバーを区別なく扱います。どちらのユーザーもデータの送信、受信、および接続の解放が行えます。
トランスポート接続上のデータのクラスには以下の 2 種類があります。
普通データ (Normal data)
優先データ (Expedited data)
優先データは緊急度の高いデータに使用されます。優先データの正確な意味論はトランスポートプロバイダにより異なります。すべてのトランスポートプロトコルが優先データをサポートしているわけではありません (t_open(3NSL) を参照)。
ほとんどのコネクション型モードプロトコルはバイトストリームによってデータの転送を行います。「バイトストリーム」では接続上で送信されるデータにメッセージ境界を与えていません。トランスポートプロトコルによってはトランスポート接続上でメッセージ境界を持っているものもあります。このサービスは XTI/TLI によってサポートされていますが、プロトコルに依存しないソフトウェアで使用することは避けてください。
メッセージ境界は t_snd(3NSL) および t_rcv(3NSL) の T_MORE フラグによって発行されます。トランスポートサービスデータユニット (TSDU) と呼ばれるメッセージは、2 つのトランスポートユーザー間を個別のユニットとして転送を行うことが可能です。メッセージの最大サイズは基本のトランスポートプロトコルにより定義されます。メッセージサイズは t_open(3NSL) または t_getinfo(3NSL) で得られます。
メッセージは複数のユニットで送信できます。そのためには (複数のユニットの) 最後のメッセージ以外のすべての t_snd(3NSL) 呼び出しに T_MORE フラグを設定します。フラグは現在と次の t_snd(3NSL) 呼び出しは論理ユニットであることを指定します。最後のメッセージで T_MORE フラグを外すことにより論理ユニットの終わりを指定します。
同様に論理ユニットを複数のユニットで送信することも可能です。もし t_rcv(3NSL) が T_MORE フラグが設定された状態で戻された場合、ユーザーはメッセージの続きを受信するため再び t_rcv(3NSL) を呼び出す必要があります。ユニットの最後のメッセージは T_MORE が設定されていない t_rcv(3NSL) の呼び出しによって認識することが可能です。
T_MORE フラグは XTI/TLI によるデータのパッケージ方法および遠隔ユーザーへのデータの配信方法については指定しません。各トランスポートプロトコルおよび各プロトコルの実装方法によりデータのパッケージおよび配信を異なる方法で行えます。
たとえば、ユーザーが t_snd(3NSL) への 1 回の呼び出しで完全なメッセージを送った場合、トランスポートプロバイダが受信ユーザーへ 1 つのユニットでデータの配信を行う保証はありません。同様に、2 つのユニットにより送信されたメッセージは遠隔トランスポートユーザーへ 1 つのユニットとして配信される可能性があります。
トランスポートによりサポートされている場合、メッセージ境界は T_MORE の値を t_snd(3NSL) に設定し、t_rcv(3NSL) の後にテストを行うことでのみ保持されます。これにより受信ユーザーは送られたときと同一の内容とメッセージ境界のメッセージを見ることが保証されます。
例のサーバーはクライアントへトランスポート接続を使用しログファイルを転送しています。クライアントはそのデータを受信し、標準出力ファイルへ書き込みます。クライアントとサーバーではメッセージ境界を持たないバイトストリームインタフェースが使用されています。クライアントは以下によりデータを受信します。
while ((nbytes = t_rcv(fd, buf, nbytes, &flags))!= -1){ if (fwrite(buf, 1, nbytes, stdout) == -1) { fprintf(stderr, "fwrite failed¥n"); exit(5); } }
クライアントは受信データを受け取るため繰り返し t_rcv(3NSL) を呼び出します。t_rcv(3NSL) はデータが着信するまでブロックします。t_rcv(3NSL) はデータの量に合わせ nbytes のデータを buf に格納しバッファーに書き込んだバイト数を戻します。クライアントは標準出力ファイルへデータを書き込み、処理を継続します。データ転送ループは t_rcv(3NSL) が失敗した段階で終了します。t_rcv(3NSL) は正常型解放または切断要求が着信した時点で失敗します。何らかの理由で fwrite(3C) が失敗した場合、クライアントは終了し、トランスポートエンドポイントは閉じられます。トランスポートエンドポイントがデータ転送中に閉じられた場合 (exit(2) または t_close(3NSL) によって)、接続は中止され遠隔ユーザーは接続の切断要求を受信します。
サーバーは子プロセスを生成しクライアントへデータを転送することによりデータ転送を管理します。親プロセスは接続要求の待機を行うためループを継続します。例 3-7 で示すとおり、サーバーでは子プロセスの生成を行うため run_server が呼び出されます。
connrelease() { /*ここで必要なため conn_fd はグローバル*/ if (t_look(conn_fd) == T_DISCONNECT) { fprintf(stderr, "connection aborted¥n"); exit(12); } /*上記以外の場合、正常型解放を要求-通常終了*/ exit(0); } run_server(listen_fd) int listen_fd; { int nbytes; FILE *logfp; /*ログファイルへのファイルポインタ*/ char buf[1024]; switch(fork()) { case -1: perror("fork failed"); exit(20); default: /*親*/ /* conn_fd を閉じ、戻って再び待機する*/ if (t_close(conn_fd) == -1) { t_error("t_close failed for conn_fd"); exit(21); } return; case 0: /*子*/ /* listen_fd を閉じ、サービスを行う */ if (t_close(listen_fd) == -1) { t_error("t_close failed for listen_fd"); exit(22); } if ((logfp = fopen("logfile", "r")) == (FILE *) NULL) { perror("cannot open logfile"); exit(23); } signal(SIGPOLL, connrelease); if (ioctl(conn_fd, I_SETSIG, S_INPUT) == -1) { perror("ioctl I_SETSIG failed"); exit(24); } if (t_look(conn_fd) != 0){ /*切断するかどうか*/ fprintf(stderr, "t_look: unexpected event¥n"); exit(25); } while ((nbytes = fread(buf, 1, 1024, logfp)) > 0) if (t_snd(conn_fd, buf, nbytes, 0) == -1) { t_error("t_snd failed"); exit(26); }
フォーク後、親プロセスは待機のメインループへ戻ります。子プロセスは新たに確立されたトランスポート接続を管理します。フォークが失敗した場合、exit(2) が両方のトランスポートエンドポイントを閉じ、クライアントに接続の切断要求を送り、クライアントの t_connect(3NSL) 呼び出しは失敗します。
サーバーの処理はログファイルから一度に 1024 バイトを読み込み、クライアントに t_snd(3NSL) を使用して送ります。buf はデータバッファーの開始点を指し、nbytes は送信するデータのバイト数を指定します。4 つ目の引数には、0 または以下の 2 つのオプションフラグを指定することが可能です。
この例ではどちらのフラグもサーバーによって設定されていません。
ユーザーがトランスポートプロバイダをデータであふれさせた場合、トランスポートから十分なデータが取り除かれるまで t_snd(3NSL) がブロックを行います。
t_snd(3NSL) は接続の切断要求を捜しません (接続が切断したことを表示)。接続が中止された場合、データが失われる可能性があるためサーバーへ通知する必要があります。1 つの解決法は、各 t_snd(3NSL) 呼び出しの前、または t_snd(3NSL) 失敗の後に、着信するイベントのチェックを行うよう t_look(3NSL) を呼び出すことです。例ではより整理された手法を使用しています。ioctl(2) の I_SETSIG によって指定されているイベントが発生した場合にユーザー要求をシグナルにします。 streamio(7I) のマニュアルページを参照してください。S_INPUT によりエンドポイント conn_fd に入力が着信した場合にシグナルをユーザープロセスへ送信します。接続の切断要求が着信した場合、シグナルを検知するルーチン (connrelease) がエラーメッセージを出力し、終了します。
サーバーが t_snd(3NSL) および t_rcv(3NSL) 呼び出しを交互に行う場合、着信する接続の切断要求を認識するために、t_rcv(3NSL) を使用することが可能です。
データ転送中のいかなる場合でもいずれかのユーザーがトランスポート接続を解放し会話を終了させることが可能です。
最初の方法の放棄型解放は、ただちに接続を切断し、宛先ユーザーへ配信されていないデータを破棄します。
どちらのユーザーも放棄型解放を行うために t_snddis(3NSL) を呼び出すことが可能です。XTI/TLI で問題が発生した場合は、トランスポートプロバイダによる接続の中止も可能です。t_snddis(3NSL) は接続の中止時に、ユーザーから遠隔ユーザーへのデータの送信を可能としています。放棄型解放はすべてのトランスポートプロバイダによりサポートされています。接続の中止時にデータを送信する機能は、すべてのトランスポートプロバイダではサポートされていません。
遠隔ユーザーが接続中止を通知された段階で、切断要求を受けるためには t_rcvdis(3NSL) を呼び出します。呼び出しにより接続が切断された原因を識別するコードおよび、切断要求ととも送られたデータを戻します (切断要求が遠隔ユーザーによって行われた場合) 。原因コードが基本的なプロトコル固有のものであり、プロトコルに依存しないソフトウェアによる判断は行うべきではありません。
2 つ目の方法の正常型解放では、データが失われないよう接続を終了させます。すべてのプロバイダは放棄型解放をサポートしていなければなりませんが、正常型解放はすべてのコネクション型プロトコルにサポートされているオプションではありません。
正常型解放をサポートするトランスポートの選択については 「トランスポート選択」を参照してください。
この例は、トランスポートプロバイダが正常型解放をサポートしていることを前提としています。すべてのデータがサーバーによって送信された時点で接続は以下のように解放されます。
if (t_sndrel(conn_fd) == -1) { t_error("t_sndrel failed"); exit(27); } pause(); /*正常型解放の要求が着信するまで*/
正常型解放は各ユーザーによる 2 段階の処理を必要とします。サーバーは t_sndrel(3NSL) を呼び出すことが可能です。このルーチンは接続の切断要求を送ります。クライアントが要求を受信した場合、引き続きデータをサーバーに送ることが可能です。すべてのデータが送られた時点で、クライアントは t_sndrel(3NSL) を呼び出し接続の切断要求を送り返します。接続は両方のユーザーが切り離し要求を受け取ってから解放されます。
この例で、データはサーバーからクライアントへのみ送信されます。そのためサーバーが解放に着手したあとにクライアントからデータを受信する場合の決まりはありません。サーバーは解放に着手した後、pause(2) を呼び出します。
クライアントは正常型解放により応答し、connrelease() により検知されるシグナルを生成します。(例 3-7 で、サーバーは ioctl(2) の I_SETSIG を受信イベントに対しシグナルを生成するために使用)。この状態で発生する可能性のある XTI/TLI イベントは切断要求、または正常型解放のため、connrelease は正常型解放要求が着信した時点で通常通り終了します。connrelease の exit(2) がトランスポートエンドポイントを閉じ、バインドされたアドレスの解放を行います。終了を行わずにトランスポートエンドポイントを閉じる場合、t_close(3NSL) を呼び出します。
クライアントは、サーバーが接続を解放するのと同様に解放を行います。クライアントは t_rcv(3NSL) が失敗するまで受信データの処理を行います。サーバーが接続を解放した場合 (t_snddis(3NSL) または t_sndrel(3NSL) を使用した場合)、t_rcv(3NSL) は失敗し、 t_errno を TLOOK に設定します。その後クライアントは以下の方法で接続の解放を処理します。
if ((t_errno == TLOOK) && (t_look(fd) == T_ORDREL)) { if (t_rcvrel(fd) == -1) { t_error("t_rcvrel failed"); exit(6); } if (t_sndrel(fd) == -1) { t_error("t_sndrel failed"); exit(7); } exit(0); }
クライアントのトランスポートエンドポイント上の各イベントは正常型解放要求のチェックが行われます。正常型解放の要求が見つかった場合、クライアントは要求の処理を行うために t_rcvrel(3NSL) と、解放要求の応答を送るため t_sndrel(3NSL) を呼び出します。その後クライアントは終了し、トランスポートエンドポイントを閉じます。
トランスポートプロバイダが正常型解放をサポートしていない場合、t_snddis(3NSL) と t_rcvdis(3NSL) とともに放棄型解放を使用します。各ユーザーはデータの喪失を防ぐ手段をとる必要があります。たとえば、会話の終わりが判断できるようデータストリーム内で特殊なバイトパターンを使用します。