この節では、実時間アプリケーションでトランスポートレベルインタフェース (TLI) を使用して非同期ネットワーク通信を行う方法を説明します。SunOS では、STREAMS の非同期機能と TLI ライブラリルーチンの非ブロッキングモードの組み合わせを使用した TLI イベントの非同期ネットワーク処理をサポートしています。
トランスポートレベルインタフェースの詳細は、『Transport Interfaces Programming Guide』と『man Pages(3): Library Routines』を参照してください。
トランスポートレベルインタフェースでは、「接続モード」と「接続なしモード」という 2 つのモードサービスが用意されています。
「接続モード」は回線中心で、確立された接続上を信頼できるシーケンスでデータを伝送します。データ伝送フェーズでのアドレスの解決と伝送のオーバヘッドを避けるための識別手続きも用意されています。このサービスは、比較的長い時間持続するデータストリーム中心の対話を必要とするアプリケーションに適しています。
「接続なしモード」はメッセージ中心で、複数のユニット間の論理的な関係を要求されない独立した単位でのデータ伝送をサポートします。宛先を含むデータのユニットを配信するために必要なすべての情報は、データと合わせて単一のサービス要求として送信側からトランスポートプロバイダに渡されます。接続なしモードサービスは、短い時間の要求と応答の対話を行い、データの配信の保証やシーケンスを必要としないアプリケーションに適しています。接続なしモードによる伝送は、概して信頼性が低いと言えます。
ファイルやデバイスの入出力と同様に、ネットワーク転送はプロセスサービス要求によって同期または非同期に実行できます。
同期ネットワーキングは、同期したファイルやデバイスの入出力と同様に進行します。write(2) 関数と同様に、送信要求はメッセージをバッファリングして戻りますが、バッファ空間がすぐ使用できない場合は、呼び出しプロセスを一時停止させることもあります。read(2) 関数と同様に、受信要求は要求を満たすデータが到着するまで、呼び出しプロセスの実行を一時停止させます。SunOS 5 では、転送サービスについて限界を保証していないので、同期ネットワーキングは他のデバイスに対して実時間特性が必要なプロセスには適していません。
非同期ネットワーキングは、非ブロッキングサービス要求によって用意されます。また、アプリケーションは接続が確立またはデータが送受信されたときに非同期通知を要求することもできます。
非同期接続なしモードのネットワーキングは、非ブロッキングサービスのエンドポイントを設定して、データ転送についての非同期通知をポーリングまたは受信することによって行われます。非同期通知を使用する場合は、実際のデータ受信はシグナルハンドラ内で行われます。
t_open(3N) によってエンドポイントを確立して、t_bind(3N) によって識別情報を確立したら、エンドポイントを非同期サービス用に設定できます。これは、fcntl(2) 関数によってエンドポイントに O_NONBLOCK フラグを設定して行います。これ以降は、 t_sndudata(3N) を呼び出すと、バッファ空間がすぐに利用できない場合は -1 が戻され、t_errno が TFLOW に設定されます。同様に t_rcvudata(3N) を呼び出すと、データがない場合は -1 が戻され、t_errno が TNODATA に設定されます。
アプリケーションは、poll(2) 関数を使用してエンドポイントでデータの受信を定期的に調べるか、データの受信を待つことができますが、データが到着したことを知らせる非同期通知を受信する必要がある場合もあります。この場合は、ioctl(2) 関数に I_SETSIG コマンドを付けて実行し、エンドポイントにデータが到着したら SIGPOLL シグナルをプロセスに送信するように要求します。アプリケーションは、複数のメッセージによって 1 つのシグナルが生成される場合についても対処しなければなりません。
次の例で、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(3N) 関数を使用して転送の設定を開始します。最後に、t_rcvconnect(3N) 関数を使用して接続が確立されたことを確認します。
接続を非同期に待つには、プロセスはまずサービスアドレスに結合した非ブロッキングエンドポイントを確立します。poll(2) の結果または非同期通知によって接続要求が到着したことがわかったら、プロセスは t_listen(3N) 関数を使用して接続要求を取得できます。接続を受け入れるには、プロセスは t_accept(3N) 関数を使用します。応答するエンドポイントは、非同期データ転送用に別個に設定しなければなりません。
#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 5 では、2 番目のプロセスが実際にファイルを開き、そのファイル記述子を実時間プロセスに渡すことによって解決できます。
SunOS 5 の STREAMS インタフェースは、開いているファイルの記述子をプロセスからプロセスへ渡す機構を提供しています。開いているファイルの記述子を持つプロセスは、ioctl(2) 関数にコマンド引数 I_SENDFD を付けて使用します。2 番目のプロセスは、ioctl() 関数にコマンド引数 I_RECVFD を付けて呼び出し、ファイル記述子を取得します。
例 9-1 では、親プロセスがまずテストファイルについての情報を表示し、その後パイプを作成します。次に、親プロセスは子プロセスを作成し、子プロセスはテストファイルを開いて、そのファイル記述子をパイプを通じて親に戻します。親プロセスは、新しいファイル記述子についての状態情報を表示します。
#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); }