ネットワークインタフェース

第 3 章 XTI と TLI を使用したプログラミング

X/Open トランスポートインタフェース (XTI) およびトランスポート層インタフェース(TLI) はネットワークプログラミングインタフェースを構成する関数のセットです。 XTI は SunOS 4 用の旧 TLI インタフェースを発展させたものです。両インタフェースはサポートされていますが、 XTI がインタフェースセットの将来の方向を表しています。

XTI/TLI はマルチスレッドに対して安全

この章で取り上げるインタフェースはマルチスレッドに対して安全です。これは XTI/TLI 関数呼び出しを含むアプリケーションはマルチスレッドアプリケーション内で自由に使用可能なことを意味します。アプリケーションの多重度は特定されていません。

XTI/TLI は非同期安全ではない

XTI/TLI インタフェースの非同期環境におけるふるまいは特定されていません。これらのインタフェースのシグナルハンドラルーチンからの使用は行わないことを推奨します。

XTI と TLI について

TLI は AT&T の System V Release 3 とともに 1986 年に導入されました。TLI は トランスポート層インタフェース API を規定しました。TLI インタフェースは ISO Transport Service Definition をモデルに設計され、 OSI トランスポート層とセッション層の間の API を提供します。その後 TLI インタフェースは AT&T の System V Release 4 の UNIX バージョンでさらに発展し、 SunOS 5.6 オペレーティングシステムインタフェースにも取り入れられました。

XTI インタフェースは TLI インタフェースを発展させたもので、このインタフェースの将来の方向を表しています。TLI を使用するアプリケーションとの互換性が保証されています。ただちに TLI のアプリケーションを XTI のアプリケーションに移行する必要性はありません。新しいアプリケーションは XTI インタフェースを使用し、古いアプリケーションは必要性が発生した時点で XTI に移行可能です。

TLI はアプリケーションがリンクするライブラリ (libns1) 内の関数呼び出しのセットとして実装されています。XTI アプリケーションは c89 フロントエンドを使用してコンパイルされ、 xnet ライブラリ (libxnet) とリンクされる必要があります。XTI におけるコンパイルに関する詳細は、standards(5) のマニュアルページを参照してください。


注 -

XTI インタフェースを使用するアプリケーションは xti.h ヘッダーファイルを使用するのに対し、TLI インタフェースを使用するアプリケーションは tiuser.h ヘッダーファイルを使用しています。


XLI/TLI において必要不可欠なのがトランスポートエンドポイント (transport endpoints) およびトランスポートプロバイダ (transport provider) の概念です。トランスポートエンドポイントは通信を行う 2 つのエンティティであり、トランスポートプロバイダはホスト上の基本的な通信サポートを提供するルーチンのセットです。XTI/TLI はトランスポートプロバイダへのインタフェースであり、プロバイダそのものではありません。図 3-1 を参照してください。

図 3-1 XLI/TLI の仕組み

Graphic

XLI/TLI コードは第 4 章で説明されている追加インタフェースやメカニズムとともに現在のトランスポートプロバイダとは独立した形で作成できます。SunOS 5 にはいくつかのトランスポートプロバイダ (たとえば、TCP) をオペレーティングシステムの一部として用意しています。トランスポートプロバイダはサービスを実行し、トランスポートユーザーはサービスを要求します。トランスポートユーザーがトランスポートプロバイダへサービス要求を発行します。たとえば、TCP や UDP 上のデータ転送要求があります。

また XTI/TLI はトランスポートに依存しないプログラミングにも使用できます。そのために XTI/TLI には 2 つのコンポーネントが用意されています。

XLI/TLI には 2 つのサービスモードが存在します。コネクションモードとコネクションレスモードです。以下のセクションではこれらの 2 つのモードの概要について説明します。

コネクションレスモード

コネクションレスモードはメッセージ指向のモードです。データはユニット間の関係を持たない自己内包型ユニットで転送されます。このサービスはデータの特性を判定する対等ユーザー間の確立された関連付けのみを必要とします。メッセージを送る上で必要な情報のすべては (たとえば、宛先アドレス) 送信されるデータとともに 1 回のサービス要求でトランスポートプロバイダに渡されます。各メッセージは完全に自己内包型です。以下のアプリケーションにはコネクションレスモードサービスが適しています。

コネクションレストランスポートは信頼性が低いことを理解してください。メッセージのシーケンスを維持しないため、メッセージが行方不明になる場合があります。

コネクションレスモードルーチン

コネクションレスモードトランスポートサービスには 2 つのフェーズ (処理段階) があります。ローカル管理とデータ転送です。ローカル管理フェーズはコネクションモードサービスにおけるのと同じローカル処理の定義を行います。

データ転送フェーズは指定された対等ユーザーへデータユニット (通常データグラムと呼ばれる) の転送を可能にします。各データユニットは宛先ユーザーの転送アドレスとともに転送されます。t_sndudata(3NSL) は送信を行い t_rcvudata(3NSL) はメッセージの受信を行います。表 3-1 はコネクションレスモードデータ転送のすべてのルーチンの一覧です。

表 3-1 コネクションレスモードデータ転送のルーチン

コマンド 

説明 

t_sndudata

トランスポートの他ユーザーへメッセージを送信する。 

t_rcvudata

トランスポートの他ユーザーからのメッセージを受信する。 

t_rcvuderr

最後に送ったメッセージのエラー情報を検出する。 

コネクションレスモードサービス

コネクションレスモードサービスはトランザクション処理アプリケーションなどの短期の要求および応答対話に適しています。データは自己内包型ユニットで転送され、複数のユニット間の論理的な関連は必要ありません。

エンドポイントの初期化

トランスポートのユーザーはデータ転送を行う前に XTI/TLI エンドポイントの初期化を行う必要があります。まず t_open(3NSL) を使用して適切なコネクションレスサービスプロバイダを選択し、t_bind(3NSL) を使用してその識別アドレスを確定します。

プロトコルオプションのネゴシエーションには t_optmgmt(3NSL) を使用します。コネクションモードサービス同様、各トランスポートプロバイダがサポートされているオプションを任意に指定します。オプションのネゴシエーションはプロトコル固有の処理です。例 3-1 はサーバーが着信クエリーの待ち状態から、その後、処理、応答を行う例です。例ではサーバーの定義および初期化シーケンスのコードも見ることができます。


例 3-1 CLTS サーバー

#include <stdio.h>
#include <fcntl.h>
#include <xti.h>	/* TLI アプリケーションは <tiuser.h> を使用*/
#define SRV_ADDR 2	/* サーバーの既知アドレス*/

main()
{
   int fd;
   int flags;
   struct t_bind *bind;
   struct t_unitdata *ud;
   struct t_uderr *uderr;
   extern int t_errno;

	if ((fd = t_open("/dev/exmp", O_RDWR, (struct t_info *) NULL))
	        == -1) {
      t_error("unable to open /dev/exmp");
      exit(1);
   }
 	if ((bind = (struct t_bind *)t_alloc(fd, T_BIND, T_ADDR))
         == (struct t_bind *) NULL) {
      t_error("t_alloc of t_bind structure failed");
      exit(2);
   }
   bind->addr.len = sizeof(int);
   *(int *)bind->addr.buf = SRV_ADDR;
   bind->qlen = 0;
   if (t_bind(fd, bind, bind) == -1) {
      t_error("t_bind failed");
      exit(3);
   }
   /*
    * TLI インタフェースアプリケーションでは以下のコードが必要です。
    * XTI アプリケーションでは必要ありません。
    *------------------------------------------------------------
    * バインドされたアドレスの検査
    *
    * 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) を使用して、任意のトランスポートプロバイダとのトランスポートエンドポイントを確立します。各プロバイダには関連付けられたサービスタイプがあるため、ユーザーは適切なトランスポートプロバイダファイルを開くことにより特定のサービスを選択できます。このコネクションレスモードサーバーは 3 つ目の引数を NULL に設定すると t_open(3NSL) により返されるプロバイダの特性を無視します。トランザクションサーバーはトランスポートプロバイダが以下の特性を持っていると判断します。

コネクションレスサーバーはアクセスの可能性のあるすべてのクライアントがサーバーにアクセスできるようエンドポイントにトランスポートアドレスをバインドします。t_alloc(3NSL) により t_bind 構造体が割り当てられ、アドレスの buf および len フィールドが設定されます。

コネクションモードサーバーとコネクションレスモードサーバーの違いの 1 つは、コネクションレスモードサービスの場合は t_bind 構造体の qlen フィールドが 0 であるという点です。待機状態の接続要求が存在しないということです。

XLI/TLI インタフェースはコネクションモードサービス内でトランスポートコネクションの確立を行いながら 2 ユーザー間において固有のクライアントサーバー関係を定義します。このような関係はコネクションレスモードサービスでは存在しません。

TLI では t_bind(3NSL) より返されるバインドされたアドレスが指定されているアドレスと同一であることを保証するため、サーバーによるアドレスのチェックを必要としています。また要求されたアドレスがビジーの場合、t_bind(3NSL) はエンドポイントに、個別の使用されていないアドレスをバインドすることが可能です。

データ転送

ユーザがトランスポートエンドポイントにアドレスをバインドさせた後、データグラムはエンドポイントを介し送受信が可能になります。各送信メッセージは宛先ユーザーのアドレスとともに送信されます。また XTI/TLI はデータユニット転送へのプロトコルオプションの指定を可能にします (たとえば、transit delay )。各トランスポートプロバイダはオプションセットをデータグラム上で定義します。データグラムが宛先ユーザーに渡された時点で、関連付けられたプロトコルオプションも渡されます。

例 3-2 は、コネクションレスモードサーバーのデータ転送フェーズの例です。


例 3-2 データ転送ルーチン

	if ((ud = (struct t_unitdata *) t_alloc(fd, T_UNITDATA,T_ALL))
         == (struct t_unitdata *) NULL) {
      t_error("t_alloc of t_unitdata struct failed");
      exit(5);
   }
   if ((uderr = (struct t_uderr *) t_alloc(fd, T_UDERROR, T_ALL))
         == (struct t_uderr *) NULL) {
      t_error("t_alloc of t_uderr struct failed");
      exit(6);
   }
   while(1) {
      if (t_rcvudata(fd, ud, &flags) == -1) {
         if (t_errno == TLOOK) {
               /* 前に送られたデータグラムのエラ−*/
               if(t_rcvuderr(fd, uderr) == -1) {
                  exit(7);
               }
            fprintf(stderr, "bad datagram, error=%d¥n",
               uderr->error);
            continue;
         }
         t_error("t_rcvudata failed");
         exit(8);
      }
      /*
       * Query() が要求の処理を行い、応答を ud->udata.buf に格納、
       * ud->udata.len が設定される。
       */
      query(ud);
      if (t_sndudata(fd, ud) == -1) {
         t_error("t_sndudata failed");
         exit(9);
      }
   }
}

/* 引数の使用 */
void
query(ud)
struct t_unitdate *ud;
{
   /* 簡略化のため関数の切り口のみ */
}

データグラムのバッファー化を行うにはサーバーはまず以下の形式を持つ t_unitdata 構造体の割り当てを行う必要があります。

struct t_unitdata {
 	struct netbuf addr;
 	struct netbuf opt;
 	struct netbuf udata;
}

addr は入力されるデータグラムの送信元アドレスと、出力されるデータグラムの宛先アドレスを保持します。opt はデータグラムのプロトコルオプションを保持します (プロトコルオプションがある場合)。udata はデータを保持します。addropt、および udata フィールドはいかなる入力値にも対応できるよう、十分なバッファーを割り当てておく必要があります。 これを確実に行うため、t_alloc(3NSL)T_ALL 引数は各 netbuf 構造体の maxlen フィールドを必要に応じ設定します。この例ではプロバイダはプロトコルオプションをサポートしていないため、opt の netbuf 構造体の maxlen0 に設定されています。またサーバーはデータグラムエラー用に t_uderr 構造体の割り当てを行います。

トランザクションサーバーは無限ループ状態でクエリーの受信、処理およびクライアントへの応答を行います。最初に次のクエリーを受信するため t_rcvudata(3NSL) を呼び出します。t_rcvudata(3NSL) はデータグラムが入力されるまでブロックし、クエリーを戻します。

t_rcvudata(3NSL) の 2 番目の引数はデータグラムのバッファーを行うための t_unitdata 構造体を特定しています。

3 番目の引数 flags は整数型変数を指し、t_rcvudata(3NSL) からの戻り値にユーザーの udata バッファーがデータグラムを格納するだけの大きさが確保されていないことを示す T_MORE が設定されることが可能です。

上の事態が発生した場合、t_rcvudata(3NSL) の次の呼び出しは残りのデータグラムを取り戻します。t_alloc(3NSL) が最大サイズのデータグラムを格納するのに十分な大きさの udata バッファーを割り当てるため、このトランザクションサーバーは flags のチェックを行う必要がありません。これは t_rcvudata(3NSL) のみに当てはまり、他の受信プリミティブには適用されません。

データグラムが受信された場合、トランザクションサーバーは要求の処理を行うため、query ルーチンを呼び出します。このルーチンは ud が指す構造体に応答を格納し、ud->udata.len を応答のバイト数に合わせ設定します。t_rcvudata(3NSL) により戻される ud->addr の送信元アドレスは t_sndudata(3NSL) の宛先アドレスです。応答の準備が完了した時点で、t_sndudata(3NSL) が呼び出され、応答はクライアントに送信されます。

データグラムエラー

トランスポートプロバイダが t_sndudata(3NSL) によって送られたデータグラムの処理を行えない場合、ユーザーにユニットデータエラーイベント T_UDERR を戻します。このイベントにはエラーを識別するため、データグラムの宛先アドレスとオプション、プロトコル固有エラー値が含まれます。データグラムのエラーはプロトコル固有 (専用) のものです。


注 -

ユニットデータエラーイベントは指定された宛先へのデータグラム転送の成功または失敗を必ずしも示しません。コネクションレスサービスはデータの確実な転送を保証していないことを覚えておいてください。


別のデータグラムの受信を行おうとした場合トランザクションサーバーにはエラーが通知されます。この場合、t_rcvudata(3NSL) は失敗し、t_errnoTLOOK に設定されます。TLOOK が設定されている場合、発生の可能性のあるイベントは T_UDERR のみであることから、サーバーはイベントの取り出しを行うため t_rcvudata(3NSL) を呼び出します。t_rcvuderr(3NSL) の 2 番目の引数は事前に割り当てられている t_uderr 構造体です。この構造体は t_rcvuderr(3NSL) によって格納され、次の形式です。

struct t_uderr {
 	struct netbuf addr;
 	struct netbuf opt;
 	t_scalar_t error;
}

addr および opt は不正なデータグラムで指定された宛先アドレスおよびプロトコルオプションを特定し、error はプロトコル固有のエラーコードです。トランザクションサーバーはエラーコードを出力し、処理を継続します。

コネクションモード

コネクションモードは回路型のモードです。データは確立された接続を使用して順次送信されます。またこのモードはデータ転送フェーズ中のアドレス解決および転送を行わない識別処理を提供します。データストリーム型の通信を必要とするアプリケーションにこのサービスを使用してください。コネクションモードトランスポートサービスには 4 つのフェーズが存在します。

ローカル管理フェーズはトランスポートユーザーとトランスポートプロバイダ間のローカル操作の定義を行います (図 3-2 参照)。たとえば、ユーザーはトランスポートプロバイダと通信チャネルの確立を行う必要があります。トランスポートユーザーとトランスポートプロバイダ間の各チャネルは通信の固有エンドポイントであり、トランスポートエンドポイントと呼ばれます。t_open(3NSL) はコネクションモードサービスの供給およびトランスポートエンドポイントの確立を行うため、ユーザーによる特定のトランスポートプロバイダの選択が可能です。

図 3-2 トランスポートエンドポイント

Graphic

コネクションモードルーチン

各ユーザーは個別にトランスポートプロバイダに認識される必要があります。トランスポートアドレスが各トランスポートエンドポイントと関連付けられます。単一のユーザー処理によって複数のトランスポートエンドポイントの管理が可能です。コネクションモードサービスでは、アドレスを指定することにより、あるユーザーから別のユーザーへの接続の要求が行われます。トランスポートアドレスの構造はトランスポートプロバイダにより定義されます。アドレスは構造化されていない文字列 (たとえば、file_server) でも、ネットワーク上のデータの経路指定に必要な情報を含む符号化されたビットパターンのような形式でも指定可能です。各トランスポートプロバイダはユーザー識別のためのメカニズムを独自に定義します。アドレスは t_bind(3NSL) によりトランスポートのエンドポイントに割り当てることが可能です。

また、t_open(3NSL) および t_bind(3NSL) に加え、いくつかのルーチンがローカル操作をサポートしています。表 3-2 は XTI/TLI のローカル管理ルーチンの一覧表です。

表 3-2 XLI/TLI のエンドポイント上の操作ルーチン

コマンド 

説明 

t_alloc

XTI/TLI のデータ構造体を割り当てる 

t_bind

トランスポートアドレスをトランスポートエンドポイントへバインドする 

t_close

トランスポートエンドポイントを終了させる 

t_error

XTI/LTI エラーメッセージを出力する 

t_free

t_alloc(3NSL) により割り当てられた構造体を解放する

t_getinfo

特定のトランスポートプロバイダに関連付けられたパラメタセットを戻す 

t_getprotaddr

エンドポイントに関連付けられたローカルおよび/またはリモートのアドレスを戻す (XTI のみ) 

t_getstate

トランスポートエンドポイントの状態を戻す 

t_look

トランスポートエンドポイントの現在のイベントを戻す 

t_open

選択されたトランスポートプロバイダへ接続されたトランスポートエンドポイントを確立する 

t_optmgmt

トランスポートプロバイダを使用したプロトコル個有のオプションでネゴシエーションする 

t_sync

トランスポートエンドポイントとトランスポートプロバイダとの同期をとる 

t_unbind

トランスポートアドレスをトランスポートエンドポイントからアンバインドする 

コネクションフェーズは 2 ユーザー間で接続の確立または仮想回路の作成を可能にします。図 3-3 を参照してください。

図 3-3 トランスポート接続

Graphic

たとえば、サーバーがクライアントグループへサービスを通知すると接続フェーズが発生し、要求待ちを行うために t_listen(3NSL) によってブロックします。クライアントは t_connect(3NSL) の呼び出しにより通知されたアドレスでサーバーへの接続を行います。接続要求により t_listen(3NSL) はサーバーへ戻され、接続を完了するため t_accept(3NSL) の呼び出しを行います。

表 3-3 はトランスポート接続を確立するためのルーチンの一覧です。各ルーチンの仕様については、マニュアルページを参照してください。

表 3-3 トランスポート接続を確立するためのルーチン

コマンド 

説明 

t_accept

トランスポート接続の要求を受け入れる 

t_connect

指定された宛先のトランスポートユーザーとの接続を確立する 

t_listen

他のトランスポートユーザーからの接続要求を待機する 

t_rcvconnect

t_connect(3NSL) が非同期モードで呼び出されている場合、接続の確立を完了する (「拡張機能」を参照)

データ転送フェーズはユーザーによる接続内の双方向のデータ転送を可能にします。接続を介し t_snd(3NSL) がデータを送信し、t_rcv(3NSL) がデータを受信します。あるユーザーから他のユーザーへ送信されたデータはすべて送信された時の順序で受信されることを前提としています。表 3-4 はコネクションモードデータ転送ルーチンの一覧です。

表 3-4 コネクションモードデータ転送ルーチン

コマンド 

説明 

t_rcv(3NSL)

トランスポート接続を介し到着したデータの受信 

t_snd(3NSL)

トランスポート接続を介してデータを送信 

XTI/TLI の接続解放には 2 通りあります。放棄型解放はトランスポートプロバイダへただちに接続を解放するよう指示を与えます。他のユーザーに送られる予定で、まだ送信されていないデータはトランスポートプロバイダによる破棄が可能です。t_snddis(3NSL) は放棄型解放の初期化を行います。t_rcvdis(3NSL) が放棄型解放を受信します。トランスポートプロバイダは通常、いくつかの形式の放棄型解放操作をサポートしています。

トランスポートプロバイダの中にはデータの破棄を行わずに接続の終了を行う正常型解放をサポートしているものがあります。t_sndrel(3NSL) および t_rcvrel(3NSL) はこの機能を持っています。表 3-5 は接続解放ルーチンの一覧です。各ルーチンの仕様についてはマニュアルページを参照してください。

表 3-5 接続解放ルーチン

コマンド 

説明 

t_rcvdis(3NSL)

接続の切断および残っているユーザーデータに対する原因コードを返す 

t_rcvrel(3NSL)

正常型解放の接続要求の受信確認 

t_snddis(3NSL)

接続の中止または接続要求の拒否 

t_sndrel(3NSL)

接続の正常型解放を要求 

コネクションモードサービス

コネクションモードサービスの基本概念をクライアントプログラムとそのサーバープログラムを使用して説明します。例ではセグメントを使用しています。

例の中ではクライアントがサーバー処理への接続を確立します。サーバーはクライアントへファイルの転送を行います。クライアントはそのファイルを受信し、標準出力へ書き出します。

エンドポイントの初期化

クライアントとサーバーの接続が行われる前に、それぞれが t_open(3NSL) を使用してトランスポートプロバイダ (トランスポートエンドポイント) へのローカル接続を行い、t_bind(3NSL) によりその識別 (またはアドレス) を確立する必要があります。

多くのプロトコルでは XTI/TLI で定義されているサービスのサブセットの実行が可能です。各トランスポートプロバイダは提供するサービスを決定したり、サービスを制限したりする特性を持っています。トランスポート特性を定義しているデータは t_info 構造体で t_open(3NSL) によって返されます。表 3-6t_info 構造体のフィールドの一覧です。

表 3-6 t_info 構造体

フィールド 

内容 

addr

トランスポートアドレスの最大サイズ 

options

トランスポートユーザーとトランスポートプロバイダ間を送信可能なプロトコル固有オプションの最大バイト数 

tsdu

コネクションモードまたはコネクションレスモードで送信可能な最大メッセージサイズ 

etsdu

トランスポート接続によって送信可能な優先データ最大メッセージサイズ 

connect

接続の確立時にユーザー間で送信可能なユーザーデータの最大バイト数 

discon

接続の放棄型解放時にユーザー間で送信可能なユーザーデータの最大バイト数 

servtype

トランスポートプロバイダによりサポートされているサービスのタイプ 

XTI/TLI により定義されている 3 つのサービスタイプは以下の通りです。

  1. T_COTS - トランスポートプロバイダはコネクションモードサービスをサポートしているが 、正常型解放機能を提供していない。接続の終了は放棄型解放によって行われ、送信されていないデータは破棄される。

  2. T_COTS_ORD - トランスポートプロバイダは正常型解放機能を持つコネクションモードサービスを提供する。

  3. T_CLTS - トランスポートプロバイダはコネクションレスモードサービスを提供する。

t_open(3NSL) により識別されるトランスポートプロバイダに関連付けが行えるのは 1 つのサービスのみです。

t_open(3NSL) はトランスポートエンドポイントのデフォルトプロバイダ特性を戻します。エンドポイントが開かれると変化する特性もあります。これはネゴシエーションを行ったオプションで発生します (オプションのネゴシエーションについてはこの章の以降のページで説明する)。t_getinfo(3NSL) はトランスポートエンドポイントの現在の特性を戻します。

ユーザーが選択したトランスポートプロバイダとエンドポイントを確立した後に、クライアントおよびサーバーは識別の確定を行う必要があります。これを行うのがトランスポートエンドポイントへトランスポートアドレスのバインドを行う t_bind(3NSL) です。サーバーの場合、このルーチンが接続要求待機にエンドポイントが使用されていることをトランスポートプロバイダへ通知します。

t_optmgmt(3NSL) はローカル管理フェーズ中に使用できます。ユーザーによるトランスポートプロバイダとのプロトコルオプション値のネゴシエーションを可能にします。各トランスポートプロトコルは quality-of-service パラメータなど独自のネゴシエーション可能なプロトコルオプションを定義します。オプションがプロトコル固有のものであるため、特定のプロトコル用に作成されたプログラムだけがこの機能を使用できます。

クライアント

これらの機能の詳細を説明するためにクライアントおよびサーバーのローカル管理の必要条件を例に示します。例 3-3 ではクライアントプログラムで必要とされる定義を行ってから必要なローカル管理を行うステップを示しています。


例 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_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 では定義とローカル管理を行うステップを例で示しています。


例 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 つの接続要求の処理しか行わないため、qlen1 に設定してあります。

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 つの機能をサポートしています。

これらの機能はプロトコルに依存するソフトウェアを作成します (「プロトコルに依存しない処理に関する指針」を参照)。

クライアント

例 3-5 はクライアントの接続確立を行うためのコード例です。


例 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_ADDRnetbuf バッファーの割り当てがシステムに必要であることを示します。サーバーのアドレスは buf にコピーされ、len は同様に適切な値に設定されます。

t_connect(3NSL) の 3 つ目の引数は、新たに確立された接続の情報を取り出すのに使用でき、サーバーによって接続要求の応答とともに送られたユーザーデータの返送が可能です。ここでは 3 つ目の引数はクライアントにより NULL に設定されています。接続は t_connect(3NSL) の応答が成功している場合に確立されます。サーバーが接続要求を拒否した場合、t_connect(3NSL)t_errnoTLOOK に設定します。

イベント処理

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 のイベントメカニズムのため、複雑です (このようなサーバーの場合は、「高度なプログラム例」を参照)。



例 3-6 accept_call 関数

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_errnoTLOOK に設定されます。

t_accept(3NSL) は、待機しているエンドポイント上で接続が受け付けられる前に非同期イベントが発生し、t_errnoTLOOK に設定された場合失敗する可能性があります。この状態では待機を行っている接続要求が 1 つでも、切断要求のみが送信可能であることが表 3-8 で示されています。このイベントはクライアントが事前に行った接続の要求を取り消した場合に発生する可能性があります。切断要求が着信した場合、サーバーは t_rcvdis(3NSL) を呼び出し応答を行う必要があります。このルーチンの引数は、切断要求のデータを取り出すために使用される t_discon 構造体へのポインタです。この例ではサーバーは NULL を渡します。

切断要求を受け取った後、accept_call は応答を行うトランスポートエンドポイントを閉じ、 DISCONNECT を戻してサーバーに接続がクライアントによって切断されたことを伝えます。その後サーバーは他の接続要求の待機を行います。

図 3-4 はサーバーの接続確立の方法を示します。

図 3-4 トランスポートエンドポイントの待機と応答

Graphic

トランスポートの接続は新しく応答を行うエンドポイント上で確立され、待機を行ったエンドポイントは他の接続要求の待機を行うため解放されます。

データ転送

接続が確立された後、サーバーおよびクライアントは t_snd(3NSL) および t_rcv(3NSL) を使用してデータの転送が行えるようになります。XTI/TLI はこれ以降、クライアントおよびサーバーを区別なく扱います。どちらのユーザーもデータの送信、受信、および接続の解放が行えます。

トランスポート接続上のデータのクラスには以下の 2 種類があります。

  1. 普通データ (Normal data)

  2. 優先データ (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 が呼び出されます。


例 3-7 ループバックおよび待機を行うための子プロセスの生成

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) を使用することが可能です。

接続の解放

データ転送中のいかなる場合でもいずれかのユーザーがトランスポート接続を解放し会話を終了させることが可能です。

正常型解放をサポートするトランスポートの選択については 「トランスポート選択」を参照してください。

サーバー

この例は、トランスポートプロバイダが正常型解放をサポートしていることを前提としています。すべてのデータがサーバーによって送信された時点で接続は以下のように解放されます。

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 は正常型解放要求が着信した時点で通常通り終了します。connreleaseexit(2) がトランスポートエンドポイントを閉じ、バインドされたアドレスの解放を行います。終了を行わずにトランスポートエンドポイントを閉じる場合、t_close(3NSL) を呼び出します。

クライアント

クライアントは、サーバーが接続を解放するのと同様に解放を行います。クライアントは t_rcv(3NSL) が失敗するまで受信データの処理を行います。サーバーが接続を解放した場合 (t_snddis(3NSL) または t_sndrel(3NSL) を使用した場合)、t_rcv(3NSL) は失敗し、 t_errnoTLOOK に設定します。その後クライアントは以下の方法で接続の解放を処理します。

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) とともに放棄型解放を使用します。各ユーザーはデータの喪失を防ぐ手段をとる必要があります。たとえば、会話の終わりが判断できるようデータストリーム内で特殊なバイトパターンを使用します。

読み取り/書き込み用インタフェース

ユーザーが接続中に受信したデータの処理を行うために、既存のプログラム上 (たとえば /usr/bin/cat など) で exec(2) を使用してトランスポート接続の確立を行いたい場合があります。既存のプログラムは read(2) および write(2) を使用します。XTI/TLI は直接トランスポートプロバイダへの読み取り/書き込みインタフェースをサポートしていないものの、これを行うことが可能です。インタフェースはトランスポート接続上のデータ転送フェーズ内で read(2) および write(2) 呼び出しを可能としています。このセクションでは XTI/TLI のコネクションモードサービスへの読み取り/書き込みインタフェースについて説明しています。このインタフェースはコネクションレスモードサービスでは使用できません。

読み取り/書き込みインタフェースは 「コネクションモードサービス」のクライアント例を (変更を加えた形で) 使い説明しています。クライアントはデータ転送フェーズの段階までは同一の処理を行います。そこからクライアントは読み取り/書き込みインタフェースと cat(1) を受信データの処理に使用します。cat(1) はトランスポート接続上で変更なしに実行されます。例 3-3 のクライアントとこのクライアントの相違点のみを例 3-8 に示します。


例 3-8 読み取り/書き込みインタフェース

#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) の意味論が使用されなければならず、メッセージ境界の保持は行われなくなります。tirdwr は XTI/TLI 意味論を復元するためにトランスポートプロバイダからポップすることが可能です (streamio(7I)I_POP を参照)。


注意 - 注意 -

トランスポートエンドポイントがデータ転送フェーズ中にある場合のみ tirdwr モジュールをストリーム上にプッシュすることが可能です。モジュールがプッシュされたあとは、ユーザーは XLI/TLI のルーチンを呼び出すことはできません。XTI/TLI ルーチンが呼び出された場合、tirdwr はストリーム上に重大なプロトコルエラー EPROTO を生成し、使用不可であることを通知します。さらに tirdwr モジュールをストリーム上からポップした場合、トランスポート接続は中止されます (streamio(7I)I_POP を参照)。


書き込み

write(2) を使用しトランスポート接続へデータを送信します。tirdwr はデータをトランスポートプロバイダへ通過させます。メカニズム上は許可されているゼロ長データパケットを送った場合、tirdwr はメッセージを破棄します。トランスポート接続が、たとえば、遠隔ユーザーが接続を t_snddis(3NSL) を使用したため中止された場合、ストリーム上にハングアップ状態が生成され、それ以降の write(2) 呼び出しは失敗し、errnoENXIO に設定されます。ハングアップ後も入手可能なデータの取り出しは可能です。

読み取り

read(2) を使用し、トランスポート接続にてデータを受信します。tirdwr はデータをトランスポートプロバイダから通過させます。プロバイダからユーザーへ渡されるその他のイベント、または要求は tirdwr によって以下の通り処理されます。

閉じる

ストリーム上に tirdwr が存在する場合、接続が行われている間、トランスポート接続上でデータの送受信が可能です。どちらのユーザーも、トランスポートエンドポイントに関連付けられたファイル記述子を閉じることにより、またはストリーム上から tirdwr モジュールをポップさせることにより接続を終了させることが可能です。いずれの場合においても tirdwr は以下の処理を行います。

プロセスは tirdwr がストリーム上にプッシュされてから正常型解放を行うことはできません。tirdwr はトランスポート接続の反対側のユーザーによって正常型解放が発行された場合のみ解放を受け付けます。このセクションのクライアントが 「コネクションモードサービス」のサーバープログラムと交信を行った場合、サーバーは正常型解放の要求によりデータの転送を終了します。その後サーバーはクライアントからの次の要求を待ちます。その時点でクライアントは終了し、トランスポートエンドポイントは閉じられます。ファイル記述子が閉じられると、tirdwr は接続のクライアント側からの正常解放型要求を発行します。これによりサーバーがブロックされている要求を生成します。

TCP など、プロトコルによってはデータが完全形で配信されるよう、この正常型解放を必要とするものがあります。

拡張機能

このセクションでは一歩踏み込んだ XTI/TLI の概念を説明します。

非同期実行モード

多くの 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_NDELAYO_NONBLOCK は各 XLI/TLI ルーチンへ異なる影響を与えます。特定のルーチンへの影響は O_NDELAYO_NONBLOCK の正確な意味論を判断する必要があります。

高度なプログラム例

以下の例は重要な 2 つの概念を示しています。1 つ目はサーバーにおける複数の未処理接続要求への管理能力を示しています。2 つ目はイベント方式の XLI/TLI の使用法およびシステムコールインタフェースです。

例 3-4 のサーバーは 1 つの未処理接続要求しかサポートしませんが、XTI/TLI ではサーバーによる複数の未処理接続要求の管理が可能です。複数の接続要求を同時に受信する理由の 1 つは、クライアントを順位付けるからです。複数の接続要求を受信した場合、サーバーはクライアントの優先順位により接続要求を受け付けることが可能です。

複数の未処理接続要求を同時に処理する 2 つ目の理由はシングルスレッド処理の限界です。トランスポートプロバイダによっては、サーバーが 1 つの接続要求の処理を行っている間、他のクライアントはビジー状態の応答を受けます。同時に複数の接続要求の処理が可能な場合、クライアント側にビジー応答が渡されるのは、サーバーで同時に処理可能なクライアントの数を超過した場合のみです。

サーバーの例はイベント方式です。プロセスはトランスポートエンドポイントに受信する XTI/TLI イベントのポーリングを行い、受信したそれぞれのイベントに対し適切な処置を行います。例は受信イベントに対し複数のトランスポートエンドポイントのポーリングを行う能力を示しています。

例 3-9 の定義およびエンドポイント確立機能は、サーバーの例 3-4 と同じものです。


例 3-9 エンドポイント確立 (複数接続へ変更可能)

#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) 用に qlen1 より大きい値に設定します。これは、サーバーが複数の未処理接続要求の待機を行うことを指定します。サーバーは現在の接続要求の受け付けを、他の接続要求を受け付ける前に行います。この例では、一度に MAX_CONN_IND (の設定値) までの接続要求の待機が行えます。MAX_CONN_IND で指定されている未処理接続要求のサポートが行えない場合、トランスポートプロバイダは qlen の値を小さくするためにネゴシエーションを行うことが可能です。

サーバーはアドレスのバインドが完了し、接続要求処理の準備が整ったら、例 3-10 で示す動作を行います。


例 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 つ) は新しいイベントのチェックを受けます。revents0 の場合、エンドポイントにイベントの発生はなく、サーバーは次のエンドポイントへ処理を移します。reventsPOLLIN の場合はエンドポイント上にイベントがあります。この場合、do_event が呼び出され、イベントの処理が行われます。revents がそれ以外の値の場合は、エンドポイント上のエラーを通知し、サーバーは終了します。複数のエンドポイントの場合は、サーバーはこの記述子を閉じて継続処理を行うことが処理上適しています。

ループの各繰り返しに対し、service_conn_ind が未処理接続要求の処理を行うために呼び出されます。他の接続要求が保留状態の場合、service_conn_ind は新しい接続要求を保存し、あとで応答します。

例 3-11do_event は受信するイベントを処理するために呼び出されます。


例 3-11 イベント処理ルーチン

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_eventt_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 を参照してください。


例 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_errnoTLOOK を設定します (保留状態の接続要求イベントまたは切断要求イベントがトランスポートエンドポイントにある場合は、未処理の接続要求を受け取ることはできません)。

このエラーが発生したときは、応答するトランスポートエンドポイントは閉じられ、service_conn_ind はただちに戻ります (現在の接続要求は後で処理するために保存されます)。これにより、サーバーのメイン処理ループに入ることができ、新しいイベントが次の poll(2) への呼び出しによって発見されます。この方法で、ユーザーは複数の接続要求を順に処理できます。

結果的にすべてのイベントが処理され、service_conn_ind はそれぞれの接続要求を順に受け取ることができます。接続が確立してからは、例 3-5 でサーバーに使用された run_server ルーチンが呼び出され、データの転送を管理します。

非同期ネットワーク通信

この項ではリアルタイムアプリケーション用の XTI/TLI を使用した非同期ネットワーク通信の方法を説明しています。SunOS は非同期ネットワークの XTI/TLI イベントの処理を、STREAMS 非同期機能と XTI/TLI ライブラリルーチンの非ブロッキングモードの組み合わせによりサポートしています。

ネットワークプログラミングモデル

ファイルおよびデバイス I/O と同様にネットワーク転送は、プロセスサービス要求による同期または非同期の実行が可能です。

同期ネットワーキング

同期ネットワーキングはファイルおよびデバイス I/O 同様に進行します。write(2) 関数と同様、送信要求はメッセージのバッファー化が行われてから戻されますが、バッファー領域がすぐに確保できない場合は、呼び出し処理を中断する可能性もあります。read(2) 関数と同様、受信要求は必要なデータが到着するまで呼び出し処理の実行を中断させます。 SunOS ではトランスポートサービスの範囲に関する保証は存在しないため、他のデバイスに関連する形でのリアルタイム処理を目的とした使用においての同期ネットワーキングの使用は不適切であるといえます。

非同期ネットワーキング

非同期ネットワーキングは非ブロッキングサービス要求により提供されます。また、データが送信または受信される接続の確立時にアプリケーションが非同期通知を要求することも可能です。

非同期コネクションレスモードサービス

非同期コネクションレスモードネットワーキングはエンドポイントに非ブロッキングサービスを構成し、データの転送時期をポーリングまたは非同期通知によって受信することにより行われます。非同期通知が使用された場合、実際のデータの受信は通常シグナルハンドラ内で行われます。

エンドポイントの非同期化

エンドポイントの確立が t_open(3NSL) により行われ、t_bind(3NSL) により識別が確立された後、エンドポイントを非同期サービスで使用するために構成することが可能です。これは fcntl(2) 関数を使用し、エンドポイント上に O_NONBLOCK フラグを設定することにより可能です。これにより、使用可能なバッファー領域がすぐに確保できない場合、t_sndudata(3NSL) への呼び出しは -1 を戻し、t_errnoTFLOW に設定します。同様に、データが存在しない場合、 t_rcvudata(3NSL) への呼び出しは -1 を戻し、t_errnoTNODATA に設定します。

非同期ネットワーク転送

データの着信、またはエンドポイント上でのデータ受信待機のチェックを定期的に行うためにアプリケーションが poll(2) 関数を使用することは可能ですが、データが着信した場合に非同期通知の受信が必要な場合があります。ioctl(2) 関数の I_SETSIG を使用することにより、エンドポイント上にデータが着信した場合、プロセスに 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 ではこの問題を解決するために、第 2 のプロセスに実際のファイルオープン作業を行わせ、ファイル記述子をリアルタイム処理に渡す機能が提供されています。

ファイル記述子の転送

SunOS の STREAMS インタフェースでは 1 つのプロセスから別のプロセスへオープンファイル記述子を渡すメカニズムを装備しています。オープンファイル記述子を持つプロセス ioctl(2) 関数の引数 I_SENDFD を使用します。もう 1 つのプロセスは ioctl(2) の引数 I_RECVFD を使用してファイル記述子を取得します。

この例では、親プロセスはテストファイルに関する情報を出力し、パイプを作成します。次に親は、テストファイルを開き、パイプを使用してオープンファイル記述子を親へ戻す子プロセスを作成します。その後、親プロセスは新しいファイル記述子の状態情報を表示します。


例 3-13 ファイル記述子転送

#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 状態

表 3-8 に、XTI/TLI の状態遷移で経過する状態およびサービスタイプを定義します。

表 3-8 XTI/TLI 状態遷移とサービスタイプ

状態 

説明 

サービスタイプ 

T_UNINIT

初期化未完 - インタフェースの初期状態と終了状態 

T_COTST_COTS_ORDT_CLTS

T_UNBND

初期化されているが、バインドされていない 

T_COTST_COTS_ORDT_CLTS

T_IDLE

接続が確立していない 

T_COTST_COTS_ORDT_CLTS

T_OUTCON

クライアントに対する送信接続が保留中 

T_COTS T_COTS_ORD

T_INCON

サーバーに対する受信接続が保留中 

T_COTS T_COTS_ORD

T_DATAXFER

データ転送 

T_COTST_COTS_ORD

T_OUTREL

送信正常型解放 (正常型解放要求待ち) 

T_COTS_ORD

T_INREL

受信正常型解放 (正常型解放要求の送信待ち) 

T_COTS_ORD

送信イベント

表 3-9 に記載する送信イベントは、指定のトランスポートルーチンがトランスポートプロバイダに要求または応答を送信したときに返される状態に対応しています。「accept」など、この表に記載する一部のイベントは、それが発生したコンテキストによって意味が変わります。これらのコンテキストは、次の変数の値に基づきます。

表 3-9 送信イベント

イベント 

説明 

サービスタイプ 

opened

正常に t_open(3NSL) が終了した

T_COTST_COTS_ORDT_CLTS

bind

正常に t_bind(3NSL) が終了した

T_COTST_COTS_ORDT_CLTS

optmgmt

正常に t_optmgmt(3NSL) が終了した

T_COTST_COTS_ORDT_CLTS

unbind

正常に t_unbind(3NSL) が終了した

T_COTST_COTS_ORDT_CLTS

closed

正常に t_close(3NSL) が終了した

T_COTST_COTS_ORDT_CLT

connect1

同期モードの t_connect(3NSL) が正常に終了した

T_COTST_COTS_ORD

connect2

非同期モードの t_connect(3NSL)TNODATA エラーが発生したか、または切断要求がトランスポートエンドポイントに到着したことにより TLOOK エラーが発生した

T_COTST_COTS_ORD

accept1

ocnt == 1fd == resfdt_accept(3NSL) が正常に終了した

T_COTST_COTS_ORD

accept2

ocnt == 1fd != resfdt_accept(3NSL) が正常に終了した

T_COTST_COTS_ORD

accept3

ocnt > 1t_accept(3NSL) が正常に終了した

T_COTST_COTS_ORD

snd

正常に t_snd(3NSL) が終了した

T_COTST_COTS_ORD

snddis1

ocnt <= 1t_snddis(3NSL) が正常に終了した

T_COTST_COTS_ORD

snddis2

ocnt > 1t_snddis(3NSL) が正常に終了した

T_COTST_COTS_ORD

sndrel

正常に t_sndrel(3NSL) が終了した

T_COTS_ORD

sndudata

正常に t_sndudata(3NSL) が終了した

T_CLTS

受信イベント

受信イベントは、指定のルーチンが正常に終了したときに発生します。これらのルーチンは、トランスポートプロバイダからのデータやイベント情報を返します。ルーチンからの戻り値に直接関連付けられていない入力イベントは、pass_conn だけで、接続が他のエンドポイントに移行するときに発生します。エンドポイントで XTI/TLI ルーチンを呼び出さなくても、接続を渡しているエンドポイントではこのイベントが発生します。

表 3-10 に示す rcvdis イベントは、それぞれ ocnt の値が異なります。ocnt とは、エンドポイントでの未処理接続要求の数です。

表 3-10 受信イベント

イベント 

接続 

サービスタイプ 

listen

正常に t_listen(3NSL) が終了した

T_COTST_COTS_ORD

rcvconnect

正常に t_rcvconnect(3NSL) が終了した

T_COTST_COTS_ORD

rcv

正常に t_rcv(3NSL) が終了した

T_COTST_COTS_ORD

rcvdis1

onct <= 0t_rcvdis(3NSL) が正常に終了した

T_COTST_COTS_ORD

rcvdis2

onct == 1t_rcvdis(3NSL) が正常に終了した

T_COTST_COTS_ORD

rcvdis3

onct > 1t_rcvdis(3NSL) が正常に終了した

T_COTST_COTS_ORD

rcvrel

正常に t_rcvrel(3NSL) が終了した

T_COTS_ORD

rcvudata

正常に t_rcvudata(3NSL) が終了した

T_CLTS

rcvuderr

正常に t_rcvuderr(3NSL) が終了した

T_CLTS

pass_conn

渡された接続を受け取った 

T_COTST_COTS_ORD

トランスポートユーザーの動作

次に示す一部の状態遷移は、トランスポートユーザーが行うべき動作の一覧を示しています。各動作は、次の一覧に示す値で表現します。

状態テーブル

状態テーブルは、XTI/TLI の状態遷移を示します。状態テーブルの列には現在の状態を、行には現在のイベントを、行と列の交差する部分では次に発生する状態を示しています。次に発生する状態が空の場合は、状態とイベントの組み合わせが無効であることを意味します。また次に発生する状態には、動作一覧が示されている場合もあります。動作は、指定された順序で実行しなければなりません。

状態テーブルを見る場合は、次の点に注意してください。

表 3-11表 3-12表 3-13 および表 3-14 では、エンドポイントの確立、コネクションレスモードでのデータの転送、コネクションモードでの接続確立/接続解放/データ転送を示します。

表 3-11 接続確立時における状態

イベント/状態 

T_UNINIT 

T_UNBND 

T_IDLE 

opened

T_UNBND 

 

 

bind

 

T_IDLE[1] 

 

optmgmt (TLI のみ)

 

 

T_IDLE 

unbind

 

 

T_UNBND 

closed

 

T_UNINIT 

 

表 3-12 コネクションモードにおける状態 - その 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 

 

 

 

optmgmt

T_IDLE 

T_OUTCON 

T_INCON 

T_DATAXFER 

closed

T_UNINIT 

T_UNINIT 

T_UNINIT 

T_UNINIT 

表 3-13 コネクションモードにおける状態 - その 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

 

 

T_DATAXFER

optmgmt

T_OUTREL 

T_INREL 

T_UNBND 

closed

T_UNINIT 

T_UNINIT 

 

表 3-14 コネクションレスモードにおける状態

イベント/状態 

T_IDLE 

snudata

T_IDLE 

rcvdata

T_IDLE 

rcvuderr

T_IDLE 

プロトコルに依存しない処理に関する指針

XTI/TLI が提供する一連のサービスは、多くのトランスポートプロトコルに共通であり、XTI/TLI を使用すると、アプリケーションはプロトコルに依存しない処理が可能になります。しかし、すべてのトランスポートプロトコルが XTI/TLI をサポートしているわけではありません。ソフトウェアをさまざまなプロトコル環境で実行する必要がある場合は、共通のサービスだけを使用してください。なお、次に示すサービスはすべてのトランスポートプロトコルに共通とは限らないので、注意が必要です。

  1. コネクションモードのサービスでは、すべてのトランスポートプロバイダで転送サービスデータ単位 (TSDU) がサポートされるとは限りません。接続の際に論理的なデータ境界が保たれることを前提としてはなりません。

  2. プロトコルおよび実装に固有のサービス範囲は、t_open(3NSL) および t_getinfo(3NSL) の各ルーチンによって返されます。これらの範囲に基づいて、バッファーを割り当て、プロトコルに固有のトランスポートアドレスおよびオプションを格納します。

  3. ユーザーデータは、t_connect(3NSL) および t_snddis(3NSL) などの接続要求や切断要求を使用して送信してはなりません。すべてのトランスポートプロトコルがこのような方法で機能するとは限りません。

  4. t_listen(3NSL) に使用する t_call 構造体に含まれるバッファーには、接続確立時にクライントが送信するデータを格納できるだけの大きさが必要です。t_alloc(3NSL)T_ALL 引数を使用して、最大バッファーサイズを設定し、現在のトランスポートプロバイダのアドレス、オプションおよびユーザーデータを格納します。

  5. t_bind(3NSL) に指定するプロトコルアドレスは、クライアント側のエンドポイントで指定してはなりません。トランスポートエンドポイントへの適切なアドレスの割り当ては、トランスポートプロバイダに任せます。サーバーは、トランスポートプロバイダの名前空間を知らなくても、t_bind(3NSL) のプロトコルアドレスを取り込むことができなければなりません。

  6. トランスポートアドレスの形式を仮定してはなりません。また、トランスポートアドレスをプログラム内で定数としてはなりません。この詳細については、第 4 章「トランスポート選択と名前からアドレスへのマッピング」を参照してください。

  7. t_rcvdis(3NSL) に関連付けられた理由コードは、プロトコルに依存します。プロトコルからの独立性が重要な問題となる場合は、この情報を使用してはなりません。

  8. t_rcvuderr(3NSL) のエラーコードは、プロトコルに依存します。プロトコルからの独立性が問題となる場合は、この情報を使用してはなりません。

  9. プログラム内にデバイス名をコーディングしてはなりません。デバイスノードは、特定のトランスポートプロバイダを指定し、プロトコルに依存します。詳細については、第 4 章「トランスポート選択と名前からアドレスへのマッピング」を参照してください。

  10. 複数のプロトコル環境で実行されるプログラムでは、t_sndrel(3NSL) および t_rcvrel(3NSL) のコネクションモードサービスで提供される、オプションの正常型解放機能を使用してはなりません。正常型解放機能は、すべてのコネクション型トランスポートプロトコルでサポートされているわけではありません。この機能を使用すると、解放型システムと正常に通信できなくなることがあります。

XTI/TLI とソケットインタフェース

XTI/TLI とソケットとでは、同じタスクの処理方法が異なります。たいていの場合、両者は、機能的には同様な機構とサービスとを提供しますが、ルーチンや低レベルのサービスで 1 対 1 の互換性があるわけではありません。アプリケーションを移植しようとする場合は、XTI/TLI インタフェースとソケットベースのインタフェースとの間の類似点や相違点をよく知る必要があります。

トランスポートの独立性に関しては、次の問題があります。これらの問題は、RPC アプリケーションにも関係があります。

ソケット関数と XTI/TLI 関数との対応関係

表 3-15 に、XTI/TLI 関数とソケット関数との対応関係をおおまかに示します。コメント欄には、両者の相違点を示します。コメントがない場合は、関数が同じであるか、または一方のインタフェースに等価の関数がないことを意味します。

表 3-15 TLI 関数とソケット関数の対応表

TLI 関数 

ソケット関数 

コメント 

t_open(3NSL)

socket(3SOCKET)

 

-

socketpair(3SOCKET)

 

t_bind(3NSL)

bind(3SOCKET)

t_bind(3NSL) は、受信ソケットの待ち行列の深さを設定するが、bind(3SOCKET) は設定しない。ソケットでは、listen(3SOCKET) の呼び出しで待ち行列の長さを指定する

t_optmgmt(3NSL)

getsockopt(3SOCKET)

setsockopt(3SOCKET)

t_optmgmt(3NSL) では、トランスポートオプションだけを管理する。getsockopt(3SOCKET)setsockopt(3SOCKET) では、トランスポート層のオプションだけでなく、ソケット層および任意のプロトコル層でのオプションも管理する

t_unbind(3NSL)

 

-

t_close(3NSL)

close(2)

 

t_getinfo(3NSL)

getsockopt(3SOCKET)

t_getinfo(3NSL) は、トランスポートに関する情報を返す。getsockopt(3SOCKET) は、トランスポートおよびソケットに関する情報を返すことができる

t_getstate(3NSL)

-

 

t_sync(3NSL)

-

 

t_alloc(3NSL)

-

 

t_free(3NSL)

-

 

t_look(3NSL)

-

SO_ERROR オプションを指定した getsockopt(3SOCKET) は、t_look(3NSL) と同じエラー情報を返す

t_error(3NSL)

perror(3C)

 

t_connect(3NSL)

connect(3SOCKET)

connect(3SOCKET) は、最初にローカルエンドポイントへのバインドを実行しなくても実行できる。エンドポイントは、t_connect(3NSL) 呼び出しの前にバインドされていなればならない。connect(3SOCKET) は、コネクションレスエンドポイントで実行すると、データグラムのデフォルト宛先アドレスを設定できる。データは、connect(3SOCKET) で送信できる

t_rcvconnect(3NSL)

-

 

t_listen(3NSL)

listen(3SOCKET)

t_listen(3NSL) は、接続指示を待つ。listen(3SOCKET) は、待ち行列の深さを設定する

t_accept(3NSL)

accept(3SOCKET)

 

t_snd(3NSL)

send(3SOCKET)

 

 

sendto(3SOCKET)

 

 

sendmsg(3SOCKET)

sendto(3SOCKET)sendsmg(3SOCKET) は、データグラムモードでもコネクションモードでも機能する

t_rcv(3NSL)

recv(3SOCKET)

 

 

recvfrom(3SOCKET)

 

 

recvmsg(3SOCKET)

recvfrom(3SOCKET) および recvmsg(3SOCKET) は、データグラムモードでもコネクションモードでも機能する

t_snddis(3NSL)

-

 

t_rcvdis(3NSL)

-

 

t_sndrel(3NSL)

shutdown(3SOCKET)

 

t_rcvrel(3NSL)

-

 

t_sndudata(3NSL)

sendto(3SOCKET)

 

 

recvmsg(3SOCKET)

 

t_rcvuderr(3NSL)

-

 

read(2)write(2)

read(2)write(2)

XTI/TLI では、read(2) または write(2) を呼び出す前に tirdwr(7M) モジュールをプッシュしておく必要がある。ソケットでは、単に read(2) または write(2) を呼び出すだけでよい

XTI インタフェースへの追加

XNS 5 (Unix98) 標準に新規の XTI インタフェースが導入されました。これらの XTI インタフェースについて、以下に簡単に説明します。詳細については、関連するマニュアルページを参照してください。TLI ユーザーはこれらのインタフェースを使用できません。

データの配布および収集転送インタフェース

t_sndvudata(3NSL)

1 つまたは複数の非連続バッファー上のデータ単位を送信する

t_rcvvudata(3NSL)

1 つまたは複数の非連続バッファーにデータ単位を受信する

t_sndv(3NSL)

接続時に、1 つまたは複数の非連続バッファー上のデータまたは優先データを送信する

t_rcvv(3NSL)

接続を経由して受信したデータまたは優先データを、1 つまたは複数の非連続バッファーに格納する

XTI ユーティリティ関数

t_sysconf(3NSL)

構成可能な XTI 変数を取得する

追加の接続解放インタフェース

t_sndreldata(3NSL)

ユーザーデータを使用して正常型解放を発行したり、応答したりする

t_rcvreldata(3NSL)

正常型解放指示やユーザーデータが含まれる確認を受け取る


注 -

追加のインタフェースである t_sndreldata(3NSL)t_rcvreldata(3NSL) は、"最小 OSI"と呼ばれる固有のトランスポートでだけ使用されます。最小 OSI は、Solaris プラットフォームではサポートされていません。これらのインタフェースは、インターネットトランスポート (TCP または UDP) と併用することはできません。