プログラミングインタフェース

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

この章では、トランスポートレベルインタフェース (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 インタフェース呼び出しを含むアプリケーションがマルチスレッド化されたアプリケーション内で自由に使用できることを意味します。これらのインタフェース呼び出しは再入可能ではないので、スケーラビリティーは直線的でありません。



注意 – 注意 –

XTI/TLI インタフェースの非同期環境における動作は仕様化されていません。これらのインタフェースはシグナルハンドラルーチンからは使用しないでください。


TLI は 1986 年に AT&T の System V, Release 3 によって導入されました。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 はライブラリ (libnsl) 内のインタフェース呼び出しセットとして実装され、それに対してアプリケーションがリンクします。XTI アプリケーションは c89 フロントエンドを使用してコンパイルし、xnet ライブラリ (libxnet) とリンクする必要があります。XTI を使用するコンパイルの詳細は、standards(5) のマニュアルページを参照してください。


注 –

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


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

XTI/TLI は次の 2 つの構成要素を利用することによっても、トランスポートに依存しないプログラミングが可能になります。

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

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


例 9–1 読み取りインタフェースと書き込みインタフェース

#include <stropts.h>

/* Same local management and connection establishment steps. */

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 モジュールをストリーム上にプッシュできるのは、トランスポート終端がデータ転送フェーズ中にある場合だけです。モジュールをプッシュしたあと、ユーザーは XTI/TLI ルーチンを呼び出すことはできません。ユーザーが XTI/TLI ルーチンを呼び出した場合、tirdwr はストリーム上に重大なプロトコルエラー EPROTO を発生させ、使用不可になったことを通知します。このとき、tirdwr モジュールをストリーム上からポップすると、トランスポートコネクションは中止されます。詳細は、streamio(7I) のマニュアルページの I_POP を参照してください。


データの書き込み

write(2) を使用してトランスポートコネクションにデータを送信したあと、tirdwr はトランスポートプロバイダを通じてデータを渡します。メカニズム上は許可されていますが、ゼロ長のデータパケットを送った場合、tirdwr はメッセージを破棄します。トランスポートコネクションが中止された場合、ハングアップ状態がストリーム上に生成され、それ以降の write(2) 呼び出しは失敗し、errnoENXIO に設定されます。この問題が発生するのは、たとえば、リモートユーザーが t_snddis(3NSL) を使用してコネクションを中止した場合などです。ハングアップ後も利用できるデータの取り出しは可能です。

データの読み取り

トランスポートコネクションに着信したデータを読み取るには、read(2) を使用します。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 を使用することによって各 XTI/TLI ルーチンに与える影響はそれぞれ異なります。特定のルーチンへの影響を知るには、O_NDELAYO_NONBLOCK の正確な意味論を認識する必要があります。

XTI/TLI の高度なプログラミング例

例 9–2 に、2 つの重要な概念を示します。1 つ目はサーバーにおける複数の未処理のコネクション要求に対する管理能力。2 つ目はイベント方式の XTI/TLI の使用法およびシステムコールインタフェースです。

XTI/TLI を使用すると、サーバーは複数の未処理のコネクション要求を管理できます。複数のコネクション要求を同時に受信する理由の 1 つは、クライアントを順位付けることです。複数のコネクション要求を受信した場合、サーバーはクライアントの優先順位に従ってコネクション要求を受け付けることが可能です。

複数の未処理コネクション要求を同時に処理する理由の 2 つ目、シングルスレッド処理の限界です。トランスポートプロバイダによっては、あるサーバーが 1 つのコネクション要求を処理する間、他のクライアントからはそのサーバーがビジーであるように見えます。複数のコネクション要求を同時に処理する場合、サーバーがビジーになるのは、サーバーを同時に呼び出そうとするクライアントの数が最大数を超える場合だけです。

次のサーバーの例はイベント方式です。 プロセスはトランスポート終端をポーリングして、XTI/TLI 受信イベントが発生しているかを確認し、受信したイベントに適切な処理を行います。複数のトランスポート終端をポーリングして、受信イベントが発生しているか確認する例を示します。


例 9–2 終端の確立 (複数コネクションへ変更可能)

#include <tiuser.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <stropts.h>
#include <signal.h>

#define NUM_FDS 1
#define MAX_CONN_IND 4
#define SRV_ADDR 1                 /* server's well known address */

int conn_fd;                       /* server connection here */
extern int t_errno;
/* holds connect requests */
struct t_call *calls[NUM_FDS][MAX_CONN_IND];

main()
{
   struct pollfd pollfds[NUM_FDS];
   struct t_bind *bind;
   int i;

   /*
    * Only opening and binding one transport endpoint, but more can
    * be supported
    */
   if ((pollfds[0].fd = t_open(“/dev/tivc”, O_RDWR,
         (struct t_info *) NULL)) == -1) {
      t_error(“t_open failed”);
      exit(1);
   }
   if ((bind = (struct t_bind *) t_alloc(pollfds[0].fd, T_BIND,
         T_ALL)) == (struct t_bind *) NULL) {
      t_error(“t_alloc of t_bind structure failed”);
      exit(2);
   }
   bind->qlen = MAX_CONN_IND;
   bind->addr.len = sizeof(int);
   *(int *) bind->addr.buf = SRV_ADDR;
   if (t_bind(pollfds[0].fd, bind, bind) == -1) {
      t_error(“t_bind failed”);
      exit(3);
   }
   /* Was the correct address bound? */
   if (bind->addr.len != sizeof(int) ||
      *(int *)bind->addr.buf != SRV_ADDR) {
      fprintf(stderr, “t_bind bound wrong address\n”);
      exit(4);
   }
}

t_open(3NSL) によって返されるファイル記述子は pollfd 構造体に格納され、トランスポート終端にデータの受信イベントが発生しているかを確認するポーリングを制御するときに使用されます。poll(2) のマニュアルページを参照してください。この例では 1 つのトランスポート終端だけが確立されます。ただし、例の残りの部分は複数のトランスポート終端を管理するために書かれています。例 9–2 を少し変更することにより複数のトランスポート終端をサポートできるようになります。

このサーバーは t_bind(3NSL) 用に qlen1 より大きな値に設定します。この値は、サーバーが複数の未処理のコネクション要求を待ち行列に入れる必要があるということを指定します。サーバーは現在のコネクション要求の受け付けを行なってから、別のコネクション要求を受け付けます。この例では、MAX_CONN_IND 個までのコネクション要求を待ち行列に入れることができます。MAX_CONN_IND 個の未処理のコネクション要求をサポートできない場合、トランスポートプロバイダはネゴシエーションを行なって qlen の値を小さくすることができます。

アドレスをバインドし、コネクション要求を処理できるようになったあと、サーバーは次の例に示すように動作します。


例 9–3 コネクション要求の処理

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 の値を確認し、新しいイベントが発生しているかを確認します。revents0 の場合、この終端上ではイベントが生成されていないので、サーバーは次の終端に進みます。reventsPOLLIN の場合は終端上にイベントがあるため、do_event を呼び出してイベントを処理します。revents がそれ以外の値の場合は、終端上のエラーを通知し、サーバーは終了します。終端が複数ある場合、サーバーはこのファイル記述子を閉じて、処理を継続します。

サーバーはループを繰り返すごとに service_conn_ind を呼び出して、未処理のコネクション要求を処理します。他のコネクション要求が保留状態の場合、service_conn_ind は新しいコネクション要求を保存し、あとでそれを処理します。

次に、サーバーが do_event を呼び出して受信イベントを処理する例を示します。


例 9–4 イベント処理ルーチン

do_event( slot, fd)
int slot;
int fd;
{
   struct t_discon *discon;
   int i;

   switch (t_look(fd)) {
   default:
      fprintf(stderr, "t_look: unexpected event\n");
      exit(7);
   case T_ERROR:
      fprintf(stderr, "t_look returned T_ERROR event\n");
      exit(8);
   case -1:
      t_error("t_look failed");
      exit(9);
   case 0:
      /* since POLLIN returned, this should not happen */
      fprintf(stderr,"t_look returned no event\n");
      exit(10);
   case T_LISTEN:
      /* find free element in calls array */
      for (i = 0; i < MAX_CONN_IND; i++) {
         if (calls[slot][i] == (struct t_call *) NULL)
            break;
      }
      if ((calls[slot][i] = (struct t_call *) t_alloc( fd, T_CALL,
               T_ALL)) == (struct t_call *) NULL) {
         t_error("t_alloc of t_call structure failed");
         exit(11);
      }
      if (t_listen(fd, calls[slot][i] ) == -1) {
         t_error("t_listen failed");
         exit(12);
      }
      break;
   case T_DISCONNECT:
      discon = (struct t_discon *) t_alloc(fd, T_DIS, T_ALL);
      if (discon == (struct t_discon *) NULL) {
         t_error("t_alloc of t_discon structure failed");
         exit(13)
      }
      if(t_rcvdis( fd, discon) == -1) {
         t_error("t_rcvdis failed");
         exit(14);
      }
      /* find call ind in array and delete it */
      for (i = 0; i < MAX_CONN_IND; i++) {
         if (discon->sequence == calls[slot][i]->sequence) {
            t_free(calls[slot][i], T_CALL);
            calls[slot][i] = (struct t_call *) NULL;
         }
      }
      t_free(discon, T_DIS);
      break;
   }
}

例 9–4 の引数は番号の slot とファイル記述子の fd です。slot は各トランスポート終端のエントリを持つグローバル配列 calls のインデックスです。各エントリは終端で受信されるコネクション要求を保持する t_call 構造体の配列です。

do_event モジュールは t_look(3NSL) を呼び出し、fd により指定された終端上の XTI/TLI イベントの識別を行います。イベントがコネクション要求 (T_LISTEN イベント) あるいは切断要求 (T_DISCONNECT イベント) する場合、イベントは処理されます。それ以外の場合、サーバーはエラーメッセージを出力して終了します。

コネクション要求の場合、do_event は最初の未使用エントリを検索するため未処理のコネクション要求配列を走査します。エントリには t_call 構造体が割り当てられ、コネクション要求は t_listen(3NSL) によって受信されます。配列は未処理コネクション要求の最大数を保持するのに十分な大きさを持っています。コネクション要求の処理は延期されます。

切断要求は事前に送られたコネクション要求と対応している必要があります。要求を受信するために、do_event モジュールは t_discon 構造体を割り当てます。この構造体には次のフィールドが存在します。

struct t_discon {
    struct    netbuf    udata;
    int       reason;
    int       sequence;
}

udata 構造体には、切断要求によって送信されたユーザーデータが含まれます。reason の値には、プロトコル固有の切断理由コードが含まれます。sequence の値は、切断要求に一致するコネクション要求を識別します。

サーバーは切断要求を受信するために、t_rcvdis(3NSL) を呼び出します。次に、コネクション要求の配列を走査して、切断要求の sequence 番号と一致するコネクション要求があるかどうかを走査します。一致するコネクション要求が見つかった場合、サーバーはその構造体を解放して、エントリを NULL に設定します。

トランスポート終端上にイベントが見つかった場合、サーバーは終端上の待ち行列に入っているすべてのコネクション要求を処理するために service_conn_ind を呼び出します。


例 9–5 すべてのコネクション要求の処理

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 はそれぞれのコネクション要求を順に受け取ることができます。

非同期ネットワーキング

この節では、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_errnoTFLOW に設定します。同様に、データが存在しない場合でも、t_rcvudata(3NSL) への呼び出しは -1 を返し、t_errnoTNODATA に設定します。

非同期ネットワーク転送

アプリケーションは 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);
	...     /* set up binding address */
	t_bind(fd, bind, bin

	/* make endpoint non-blocking */
	fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);

	/* establish signal handler for SIGPOLL */
	signal(SIGPOLL, sigpoll);

	/* request SIGPOLL signal when receive data is available */
	ioctl(fd, I_SETSIG, S_INPUT | S_HIPRI);

	...

void sigpoll(int sig)
{
	int                  flags;
	struct t_unitdata    ud;

	for (;;) {
		... /* initialize ud */
		if (t_rcvudata(fd, &ud, &flags) < 0) {
			if (t_errno == TNODATA)
				break;  /* no more messages */
			... /* process other error conditions */
	}
	... /* process message in 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 = /* establish a non-blocking endpoint */

call = (struct t_call *) t_alloc(fd, T_CALL, T_ADDR);
/* initialize call structure */
t_connect(fd, call, call);

/* connection request is now proceeding asynchronously */

/* receive indication that connection has been accepted */
t_rcvconnect(fd, &call);

次の例に、非同期的にコネクションを待機する方法を示します。

#include <tiuser.h>
int             fd, res_fd;
struct t_call   call;

fd = /* establish non-blocking endpoint */

/*receive indication that connection request has arrived */
call = (struct t_call *) t_alloc(fd, T_CALL, T_ALL);
t_listen(fd, &call);

/* determine whether or not to accept connection */
res_fd = /* establish non-blocking endpoint for response */
t_accept(fd, res_fd, call);

非同期的に開く

アプリケーションは、リモートホストからマウントされたファイルシステムや初期化に時間がかかっているデバイスにある通常ファイルを動的に開く必要のある場合があります。しかし、このようなファイルを開く要求を処理している間、アプリケーションは他のイベントにリアルタイムで応答できません。この問題を解決するために、SunOS ソフトウェアはファイルを実際に開く作業を別のプロセスに任せて、ファイル記述子をリアルタイムプロセスに渡します。

ファイル記述子の転送

SunOS プラットフォームが提供する STREAMS インタフェースには、開いたファイル記述子をあるプロセスから別のプロセスに渡すメカニズムが用意されています。開いたファイル記述子を渡したいプロセスは、コマンド引数 I_SENDFD を指定して ioctl(2) を使用します。ファイル記述子を取得したいプロセスは、コマンド引数 I_RECVFD を指定して ioctl(2) を使用します。

次の例では、親プロセスはまず、テストファイルについての情報を出力し、パイプを作成します。親プロセスは次に、テストファイルを開いて、開いたファイル記述子をパイプ経由で親プロセスに返すような子プロセスを作成します。そのあと、親プロセスは新しいファイル記述子の状態情報を表示します。


例 9–6 ファイル記述子の転送

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

次の表に、XTI/TLI の状態遷移で使用される状態とサービスタイプを定義します。

表 9–1 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_COTST_COTS_ORD

T_INCON

サーバーに対する受信コネクションが保留中 

T_COTST_COTS_ORD

T_DATAXFER

データ転送 

T_COTST_COTS_ORD

T_OUTREL

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

T_COTS_ORD

T_INREL

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

T_COTS_ORD

送信イベント

次の表に示す送信イベントは、指定されたトランスポートルーチンから返される状態に対応しており、このようなイベントにおいて、これらのルーチンはトランスポートプロバイダに要求または応答を送信します。この表で示すイベントの一部 (accept など) は、発生した時点におけるコンテキストによって意味が変わります。これらのコンテキストは、次の変数の値に基づきます。

表 9–2 送信イベント

イベント 

説明 

サービスタイプ 

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 == resfd を指定した t_accept(3NSL) が正常に終了した

T_COTST_COTS_ORD

accept2

ocnt == 1fd != resfd を指定した t_accept(3NSL) が正常に終了した

T_COTST_COTS_ORD

accept3

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

T_COTST_COTS_ORD

snd

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

T_COTST_COTS_ORD

snddis1

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

T_COTST_COTS_ORD

snddis2

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

T_COTST_COTS_ORD

sndrel

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

T_COTS_ORD

sndudata

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

T_CLTS

受信イベント

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

次の表では、rcvdis イベントは、終端上の未処理のコネクション要求の数を示す ocnt の値によって区別されます。

表 9–3 受信イベント

イベント 

説明 

サービスタイプ 

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 <= 0 を指定した t_rcvdis(3NSL) が正常に終了した()

T_COTST_COTS_ORD

rcvdis2

ocnt == 1 を指定した t_rcvdis(3NSL) が正常に終了した

T_COTST_COTS_ORD

rcvdis3

ocnt > 1 を指定した t_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 の状態遷移を示します。状態テーブルの列には現在の状態を、行には現在のイベントを、行と列の交差する部分では次に発生する状態を示しています。次に発生する状態が空の場合は、状態とイベントの組み合わせが無効であることを意味します。また次に発生する状態には、動作一覧が示されている場合もあります。動作は、指定された順序で実行する必要があります。

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

次の表の状態遷移には、トランスポートユーザーが行う必要がある動作が記載されているものもあります。各動作は、次のリストから求められた数字によって表現されます。

次の表に、終端の確立の状態を示します。

表 9–4 コネクション確立時における状態

イベント/状態 

T_UNINIT

T_UNBND

T_IDLE

opened

T_UNBND

 

 

bind

 

T_IDLE[1]

 

optmgmt (TLI のみ)

 

 

T_IDLE

unbind

 

 

T_UNBND

closed

 

T_UNINIT

 

次の表に、コネクションモードにおけるデータの転送の状態を示します。

表 9–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

 

 

 

optmgmt

T_IDLE

T_OUTCON

T_INCON

T_DATAXFER

closed

T_UNINIT

T_UNINIT

T_UNINIT

T_UNINIT

次の表に、コネクションモードにおけるコネクションの確立、コネクションの解放、およびデータの転送の状態を示します。

表 9–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

 

 

T_DATAXFER

optmgmt

T_OUTREL

T_INREL

T_UNBND

closed

T_UNINIT

T_UNINIT

 

次の表に、コネクションレスモードにおける状態を示します。

表 9–7 コネクションレスモードにおける状態

イベント/状態 

T_IDLE

snudata

T_IDLE

rcvdata

T_IDLE

rcvuderr

T_IDLE

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

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

次に示すサービスはすべてのトランスポートプロトコルに共通とは限らないので、注意してください。

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

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

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

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

次の表に、XTI/TLI インタフェースとソケットインタフェースのおおまかな対応関係を示します。コメント列には、相違点を示します。コメントがない場合、インタフェースがほとんど同じであるか、または一方のインタフェースに相当する関数が存在しないことを意味します。

表 9–8 TLI 関数とソケット関数の対応表

TLI インタフェース 

ソケットインタフェース 

Comments 

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_look() と同じ種類のエラー情報を返す

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) および sendmsg(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 (UNIX03) 標準に新規の 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) と併用することはできません。