ネットワークアプリケーションを書くときには、ノンブロッキングモードとブロッキングモードを使うことができます。ノンブロッキングモードは、アプリケーションが複数のソケットにサービスを提供するなど、複数のことをしなければならない場合に、より柔軟性があり、必要とされます。しかし、アプリケーションがソケットからの読み込みとファイルやキューへの書き込みなど、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 10240 command line was: timeout_recv 5647 10240 timeout_recv: 12:14:27 calling accept timeout_recv: 12:14:37 accept returned a timeout timeout_recv: 12:14:37 calling accept timeout_recv: 12:14:47 accept returned a timeout timeout_recv: 12:14:47 calling accept timeout_recv: 12:14:48 calling recv timeout_recv: 12:14:58 recv returned a timeout timeout_recv: 12:14:58 calling recv timeout_recv: 12:15:00 recv return 1 characters timeout_recv: 12:15:00 calling recv timeout_recv: 12:15:10 recv returned a timeout timeout_recv: 12:15:10 calling recv timeout_recv: 12:15:16 recv return 1 characters timeout_recv: 12:15:16 calling recv timeout_recv: 12:15:23 recv return 1 characters timeout_recv: 12:15:23 calling recv timeout_recv: 12:15:30 recv return 1 characters timeout_recv: 12:15:30 calling recv timeout_recv: 12:15:38 recv return 1 characters timeout_recv: 12:15:38 calling recv timeout_recv: 12:15:46 recv return 1 characters timeout_recv: 12:15:46 calling recv timeout_recv: 12:15:56 recv returned a timeout timeout_recv: 12:15:56 calling recv timeout_recv: client terminated connection at 12:16:00 ready 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 calls and 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