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

非同期ネットワーキング

この節では、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);
}