ネットワーク

68. TCP/IP接続試行でタイムアウトを設定するには?

TCP/IPアプリケーションが起動されているかどうかを
検査するために接続の試行を行う場合がある。
例えば IBM iから別のサーバーへFtp接続したい場合は
そのサーバーが PORT 21番で接続可能であるか
テストすればよい。
TCP/IPアプリケーションの種類によってPORT番号が
決まっているので PORT番号を指定して接続試行を
すれば、そのアプリが使用可能かどうかを
判断することができる。

さて接続試行は具体的な関数としてC言語では
connect関数を使って接続を試みることになる。
しかし首尾よく接続できればよいのだが
接続できない場合でも connect関数は数分のあいだ
サーバーからの応答を待機する。
一般的には 3~5分待機した後で失敗を告げるのだが
これは実用的にはあまりにも長い待ち時間である。
人間が待つ限界としてはせいぜい 5~10秒くらいであろう。

待機時間はTCP/IPサーバーの設定値であるが
IBM i から接続するときはクライアントとなるIBM i側で
setsockopt関数などで待機時間を設定することは
できない。
UNIXでは可能であるがTCP/IPの待ち時間を設定できる
唯一の関数は select関数であるとIBMの英文マニュアルには
記述されている。

そこでここでは connectselectを使った connect関数の
タイムアウト機能を紹介する。
IBM iで待ち時間のある接続試行はソフトウェア製品には
なくてはならないものである。

[ TESTSEL3: TCP/IPでのタイムアウト ]
ソースはこちらで

0001.00 #include                                                     
0002.00 #include                                                    
0003.00 #include                                                    
0004.00 #include                                                     
0005.00 #include                                                
0006.00 #include                                                 
0007.00 #include                                                
0008.00 #include                                               
0009.00 #include                                                 
0010.00 #include                                                  
0011.00 #include                                                     
0012.00                                                                       
0013.00 #define TRUE         0                                                
0014.00 #define FALSE       -1                                                
0015.00 #define TCP_LEN      1492    /* TCP/IP 送受信長 */                    
0016.00 int SMB_PORT = 445;                /* WINDOWS SMB サーバー */         
0017.00 int    m_timeout  = 10;      /* IE 切断後の再 READ のタイムアウト秒 */ 
0018.00 char IPADDR[16];                                                      
0019.00 typedef struct {                                                      
0020.00    int  BYTESPRO;                                                     
0021.00    int  BYTESAVL;                                                     
0022.00    char MSGID[7];                                                     
0023.00    char RESRVD;                                                       
0024.00    char EXCPDATA[100];                                                    
0025.00 } ERRSTRUCTURE;     /* Define the error return structure            */    
0026.00 ERRSTRUCTURE  errcode;/* Error Code Structure for RCVMSG      */          
0027.00                                                                           
0028.00 void main(void){                                                          
0029.00    int sockfd, on = 1, rc, port = 3010, n, usec = 10, flags, len;         
0030.00    struct sockaddr_in iaddr;                                              
0031.00    int    iaddrlen = sizeof(iaddr);                                       
0032.00    struct timeval timeout;                                                
0033.00    fd_set read_fd, write_fd;                                              
0034.00    char buff[TCP_LEN+1];                                                  
0035.00    fd_set rset, wset;                                                     
0036.00    struct timeval tval;                                                   
0037.00                                                                           
0038.00    printf("** TESTSEL3 : select のテスト・サンプル 3 **n");              
0039.00    getchar();                                                             
0040.00    strcpy(IPADDR, "192.176.233.93");                                      
0041.00  /*strcpy(IPADDR, "192.168.1.93");*/                                      
0042.00    m_timeout = 10; /* 10 秒のタイムアウト */                              
0043.00                                                                           
0044.00    sockfd = socket(AF_INET, SOCK_STREAM, 0);                              
0045.00    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)); 
0046.00    memset(&iaddr, 0, sizeof(struct sockaddr_in));                         
0047.00    iaddr.sin_port = htons(SMB_PORT); /* SMB ポートで接続を試みる */       
0048.00    iaddr.sin_family  = AF_INET;                                         
0049.00    iaddr.sin_addr.s_addr = inet_addr(IPADDR);                           
0050.00                                                                         
0051.00    /* nonblock に設定 */                                                
0052.00    flags = fcntl(sockfd, F_GETFL, 0);                                   
0053.00    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);                          
0054.00                                                                         
0055.00    printf("[%d] %s に接続します。 n", __LINE__, IPADDR);               
0056.00    getchar();                                                           
0057.00    rc = connect(sockfd, (struct sockaddr *)&iaddr, sizeof(iaddr));      
0058.00    printf("[%d] connect rc = %dn", __LINE__, rc);                      
0059.00    if(rc == 0) goto DONE;                                               
0060.00    FD_ZERO(&read_fd);                                                   
0061.00    FD_SET(sockfd, &read_fd);                                            
0062.00    write_fd = read_fd;                                                  
0063.00    timeout.tv_sec = m_timeout;                                          
0064.00    timeout.tv_usec = 0;                                                 
0065.00    printf("** (%s) select 待機中 **n", IPADDR);                        
0066.00    rc = select(sockfd+1, &read_fd, &write_fd, NULL, &timeout);          
0067.00    if(rc < 0){/* select 失敗 */                                         
0068.00      fprintf(stderr, "%d:SELECT 失敗 :%s", __LINE__, strerror(errno));  
0069.00      close(sockfd);                                                     
0070.00      getchar();                                                         
0071.00      exit(-1);                                                          
0072.00    }/* select 失敗 */                                                        
0073.00    if(FD_ISSET(sockfd, &read_fd) || FD_ISSET(sockfd, &write_fd)){            
0074.00    }                                                                         
0075.00    else{/* NODATA-TIMEOUT */                                                 
0076.00      printf("%d 秒の受信待機はタイムアウトで終了しました。 n", m_timeout);  
0077.00      close(sockfd);                                                          
0078.00      getchar();                                                              
0079.00      exit(-1);                                                               
0080.00    }/* NODATA-TIMEOUT */                                                     
0081.00 DONE:                                                                        
0082.00    fcntl(sockfd, F_SETFL, flags);                                            
0083.00    close(sockfd);                                                            
0084.00    printf("[%d] (%s) 接続に成功しました。 n", __LINE__, IPADDR);            
0085.00    getchar();                                                                
0086.00    exit(0);                                                                  
0087.00                                                                              
0088.00 }     


                                                                                

[解説]

タイムアウトは


0042.00    m_timeout = 10; /* 10 秒のタイムアウト */   

として設定されている。

0051.00    /* nonblock に設定 */                                                
0052.00    flags = fcntl(sockfd, F_GETFL, 0);                                   
0053.00    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);

よって socket識別子をノンプロックに設定しておいてから

0047.00    iaddr.sin_port = htons(SMB_PORT); /* SMB ポートで接続を試みる */       
0048.00    iaddr.sin_family  = AF_INET;                                         
0049.00    iaddr.sin_addr.s_addr = inet_addr(IPADDR);

によって設定された ipaddr に対して

0057.00    rc = connect(sockfd, (struct sockaddr *)&iaddr, sizeof(iaddr));

で connectすると正常に接続された場合は rc = 0 が戻る。

0060.00    FD_ZERO(&read_fd);                                                   
0061.00    FD_SET(sockfd, &read_fd);                                            
0062.00    write_fd = read_fd;                                                  
0063.00    timeout.tv_sec = m_timeout;                                          
0064.00    timeout.tv_usec = 0;

によって selectに必要な socket識別子に対してタイムアウトも設定してから

0066.00    rc = select(sockfd+1, &read_fd, &write_fd, NULL, &timeout);

で、タイムアウトをともなう受信を行う。

この例では

0016.00 int SMB_PORT = 445;                /* WINDOWS SMB サーバー */

によって PORT番号 445番SMBサーバーが待機しているかどうかを
調べている。
SMBサーバーとは SMB:Server Message Block というプリンタ共有サーバーの
ことである。

よく「プリンタを共有にする」とかいう言葉を耳にすることがあるが
正しくは「プリンタを共有にするためのSMBサーバーを起動する」ということである。
あるプリンタ・ドライバが共有であるかどうかを検査するために
PORT 445番でそのTCP/IPサーバーにアクセスすればよいことになる。

この技術は the WINDOWSライターに使われていて共有サーバーに接続可能か
どうかを検査するために使用されている。
またSpoolライターVer5.0でのFTP送信にもこの技術が生かされている。