このフェーズでは 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 はサーバーの接続確立の方法を示します。
トランスポートの接続は新しく応答を行うエンドポイント上で確立され、待機を行ったエンドポイントは他の接続要求の待機を行うため解放されます。