ネットワークアプリケーションを作成する際には、ノンブロッキングモードまたはブロッキングモードを使用できます。 ノンブロッキングモードは柔軟性が高く、複数のソケットを処理するなど、アプリケーションが複数の処理を同時に行う必要がある場合に必要です。一方、アプリケーションが1つの処理のみ(例えばソケットからの読み取りとファイルまたはキューへの書き込み)を行う場合、ブロッキングモードを使用することでアプリケーションの複雑さを大幅に削減できます。ただしブロッキングモードには小さな問題があります。接続に問題が発生した場合、アプリケーションはそれを認識できず、永遠に届かないデータを待ち続ける可能性があるのです。
どうしてそんなことが起こるのか?アプリケーションがrecvを呼び出し、クライアントからのデータ待ち状態にあると想像してください。クライアントのネットワーク接続は不安定で、クライアントアプリケーションは再送を繰り返し行っています。ある時点でクライアントのTCPスタックは接続を切断すべきと判断し、クライアントアプリケーションに通知してソケットをクリーンアップします。 クライアント側では既に閉じられた接続に対して、あなたのアプリケーションはデータ待ちの状態のまま放置されます。この問題を解消する唯一の方法は、アプリケーションを手動で終了させて再起動することです。アプリケーションを終了させると、接続されていたソケットは「TIME_WAIT」状態になります。REUSEADDRソケットオプションを設定していない限り、この状態ではアプリケーションを即座に再起動してリスニングソケットにバインドさせることができません。
ただし、OpenVOSに対してrecv呼び出し中のデータ待機時間に制限を設けるよう指示できます。その結果、制限時間が経過すると呼び出しは-1を返し、errnoはe$timeout(1081)に設定されます。 アプリケーションはその後、接続の終了、クライアントへの再試行の機会提供、接続テストのためのデータ送信など、状況に応じた適切な対応を決定できます。重要な点は、状況の診断と対応がアプリケーションの制御下にあることです。
図2のプログラムtimeout_recv.cはこの方法の例です。2つの引数を取ります:リスンするポート番号と、1/1024秒単位のタイムアウト時間です。図1は実行例を示しており、コマンドライン(太字 かつ下線付き)はエコーされます。インタラクティブプログラムは全ての引数をエコーすべきだと確信しているためです。 注:ここでは10秒(10240 / 1024)のタイムアウトを設定しています。プログラムはaccept呼び出し時と、acceptがタイムアウトを返した場合に報告します。前段落では触れなかったが、このタイムアウトはaccept呼び出し時にも機能し、プログラムはその動作を示しています。接続確立後は、recv呼び出し時と、recvがタイムアウトまたは文字列を返した時刻を報告します。 タイムアウトに関するメッセージは全て強調表示しています。
注意:時間制限を設定する呼び出しはOpenVOS(およびVOS)固有のものであり、この手法は他のオペレーティングシステムでは動作しません。
timeout_recv 5647 10240command line was: timeout_recv 5647 10240 timeout_recv: 12:14:27 calling accepttimeout_recv: 12:14:37 accept returned a timeouttimeout_recv: 12:14:37 calling accepttimeout_recv: 12:14:47 accept returned a timeouttimeout_recv: 12:14:47 calling accepttimeout_recv: 12:14:48 calling recvtimeout_recv: 12:14:58 recv returned a timeouttimeout_recv: 12:14:58 calling recvtimeout_recv: 12:15:00 recv return 1 characterstimeout_recv: 12:15:00 calling recvtimeout_recv: 12:15:10 recv returned a timeouttimeout_recv: 12:15:10 calling recvtimeout_recv: 12:15:16 recv return 1 characterstimeout_recv: 12:15:16 calling recvtimeout_recv: 12:15:23 recv return 1 characterstimeout_recv: 12:15:23 calling recvtimeout_recv: 12:15:30 recv return 1 characterstimeout_recv: 12:15:30 calling recvtimeout_recv: 12:15:38 recv return 1 characterstimeout_recv: 12:15:38 calling recvtimeout_recv: 12:15:46 recv return 1 characterstimeout_recv: 12:15:46 calling recvtimeout_recv: 12:15:56 recv returned a timeouttimeout_recv: 12:15:56 calling recvtimeout_recv: client terminated connection at 12:16:00ready 12:16:00 |
図1 – timeout_recvの実行例
/* ***************************************************************** timeout_recv written by NSDavids Stratus CAC 10-04-23 version 1.0 initial release This is a demonstration of timing out blocking accept and recv callsand returning control to the program. ***************************************************************** */ #include <sys/socket.h>#include <arpa/inet.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <time.h> #define bzero(s, len) memset((char *)(s), 0, len) int errno; void s$set_io_time_limit(short int *, long int *, short int *);int s$c_get_portid_from_fildes(int file_des); /* this routine returns the current time as a string */ char * GetCurrentTime (char * szT){time_t tTime;struct tm *tmLT; tTime = time ((time_t *) 0);tmLT = localtime (&tTime); sprintf (szT, "%02ld:%02ld:%02ld", tmLT -> tm_hour, tmLT -> tm_min, tmLT -> tm_sec); return (szT);} /* this routine sets the time out value on the port associated with the socket. These calls are VOS specific, this program is not portable to other environments. i.e. Windows or Linux */ int SetTimeOut (int iSocket, int iTimeLimit, char * szType){long lPortID;short sPortID;long lTimeLimit;short sError; if ((lPortID = s$c_get_portid_from_fildes(iSocket)) == -1) { printf ("timeout_recv: Error getting port ID of %s socketn", szType); exit (-1); } sPortID = (short)lPortID; if (iTimeLimit > 0) { lTimeLimit = iTimeLimit; s$set_io_time_limit (&sPortID, &lTimeLimit, &sError); if (sError != 0) { printf ("timeout_recv: Error %d setting time out of %s socketn", szType, sError); exit (sError); } }return (0);} main (argc, argv)int argc;char *argv []; { int iCliLen; struct sockaddr_in sockCliAddr, sockServAddr; short sPortNo; int iBytes; int iTimeLimit; char szT [32]; int iListenSock, iAcceptedSock; #define BUFFERLEN 100 char szBuffer [BUFFERLEN]; /* process the arguments */ if (argc == 3) { sPortNo = atoi (argv [1]); iTimeLimit = atoi (argv [2]); printf ("command line was: timeout_recv %d %dnn", sPortNo, iTimeLimit); } else { printf ("nUsage: timeout_recv <port_number> <time_limit in 1/1024s of a sec)n"); exit (-1); } /* set up a listening socket */ if ((iListenSock = socket (AF_INET, SOCK_STREAM, 0)) < 0) printf ("timeout_recv: Error %d can't open stream socket", errno); bzero ( (char *) &sockServAddr, sizeof (sockServAddr)); sockServAddr.sin_family = AF_INET; sockServAddr.sin_addr.s_addr = htonl (INADDR_ANY); sockServAddr.sin_port = htons (sPortNo); if (bind (iListenSock, (struct sockaddr *) &sockServAddr, sizeof (sockServAddr)) < 0) { printf ("timeout_recv: Error %d can't bind local addressn", errno); exit (errno); } listen (iListenSock, 5); SetTimeOut (iListenSock, iTimeLimit, "Listening"); /* In most cases I expect that an application will just block waiting for a connection. However, I wanted to show that you can also timeout the call to accept */ iAcceptedSock = 0; while (iAcceptedSock < 1) { printf ("timeout_recv: %s calling acceptn", GetCurrentTime (szT)); iAcceptedSock = accept (iListenSock, (struct sockaddr *) &sockCliAddr, &iCliLen); if (iAcceptedSock < 0) { if (errno == 1081) printf ("timeout_recv: %s accept returned a timeoutn", GetCurrentTime (szT)); else { printf ("timeout_recv: %s accept returned the unexpected error %dn", GetCurrentTime (szT), errno); exit (errno); } iAcceptedSock = 0; } } /* set the timeout on the newly accepted socket */ SetTimeOut (iAcceptedSock, iTimeLimit, "Accepted"); /* main loop, call recv and process the return value. If the return value is -1 then it is an error. An errno of 1081 (e$timeout) is expected, report it and continue. Any other error is unexpected and fatal. If the return value is 0 it means the client terminated the connection, report it and exit. If the return value is > 0 it indicates the number of characters received, report it and continue the loop. */ while (1) { printf ("timeout_recv: %s calling recvn", GetCurrentTime (szT)); iBytes = recv (iAcceptedSock, szBuffer, BUFFERLEN, 0); if (iBytes == -1) { if (errno == 1081) printf ("timeout_recv: %s recv returned a timeoutn", GetCurrentTime (szT)); else { printf ("timeout_recv: %s recv returned the unexpected error %dn", GetCurrentTime (szT), errno); exit (errno); } } else if (iBytes == 0) { printf ("timeout_recv: client terminated connection at %sn", GetCurrentTime (szT)); exit (0); } else printf ("timeout_recv: %s recv return %d charactersn", GetCurrentTime (szT), iBytes); }} |
図2 – timeout_recv.c
