接続タイムアウト設定

4703 ワード

socketプログラミングでは、クライアントがconnect()でサービス側に接続している場合、サーバやネットワークが忙しい場合、または電源が入っていないホストに接続している場合、クライアントが送信したSYNパケットが応答しない場合、connect関数は長い間返されません(ブロックモード).具体的なタイムアウト時間はシステムに関連し、75秒、120秒などに設定される場合があります.あるアプリケーションでは、ブロックが長すぎるのはプログラムが望んでいる行為ではありませんか?
接続タイムアウト時間と/proc/sys/net/ipv 4/tcp_syn_retriesの値に関係し,この値はSYNパケットの再送回数を決定する一方,SYNパケットの再送間隔時間系列は,1,2,4,8,16,32,64,120,120…この時間系列は固定されているのでtcp_syn_retriesの値が大きいほどconnectタイムアウト時間が長くなります.これはシステム設定であり、この値を変更することでconnectのタイムアウト時間を短縮するのは明らかに合理的ではありません.(sysctlを検索して変更方法を表示できます)
次にconnectタイムアウト時間を他の方法で変更する方法を見てみましょう
方法1:
ステップ
  • socket
  • を確立する
  • socketを非ブロックモード
  • に設定
  • connect
  • を呼び出す
  • selectを使用して、socket記述子が書き込み可能かどうかを確認する
  • .
  • select戻り結果からconnect結果
  • を判断する
  • socketをブロックモード
  • に復元する
    コードの例
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netdb.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <errno.h>
    
    
    int connect_timeout(int sockfd, struct sockaddr *serv_addr, int addrlen, int timeout);
    
    int open_clientfd(const char *server, int port)
    {
    	struct sockaddr_in serverAddr;
    	struct hostent *hostp;
    
    	int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    	if (clientfd < 0)
    	{
    		perror("socket: ");
    		return -1;
    	}
    	if ( (hostp = gethostbyname(server)) == NULL)
    	{
    		perror("gethostbyname: ");
    		close(clientfd);
    		return -1;
    	}
    
    	bzero((char *)&serverAddr, sizeof(serverAddr));
    	memcpy((void *)&(serverAddr.sin_addr), (void *)hostp->h_addr_list[0],hostp->h_length);
    
    	serverAddr.sin_family = AF_INET;
    	serverAddr.sin_port = ntohs(port);
    
    	int n = connect_timeout(clientfd, (sockaddr *)&serverAddr, sizeof(serverAddr), 10000);
    	if (n<0)
    	{
    		printf("error
    "); close(clientfd); return -1; } else if (n==0) { printf("time out
    "); close(clientfd); return -1; } else printf("connect sucessed
    "); return clientfd; } /* connect,timeout */ int connect_timeout(int sockfd, struct sockaddr *serv_addr, int addrlen, int timeout) { int flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); int n = connect(sockfd, serv_addr, sizeof(*serv_addr)); if(n < 0) { /* EINPROGRESS connect #define EWOULDBLOCK EAGAIN Operation would block */ if(errno != EINPROGRESS && errno != EWOULDBLOCK) return -1; struct timeval tv; tv.tv_sec = timeout/1000; tv.tv_usec = (timeout - tv.tv_sec*1000)*1000; fd_set wset; FD_ZERO(&wset); FD_SET(sockfd, &wset); n = select(sockfd+1, NULL, &wset, NULL, &tv); if(n < 0) { // select return -1; } else if (0 == n) { // return 0; } } fcntl(fd,F_SETFL,flags & ~O_NONBLOCK); // return 1; } int main(int argc, char **argv) { int sk = open_clientfd(argv[1], atoi(argv[2])); printf("socket: %d
    ", sk); if (sk>0) close(sk); return 0; }

    方法2(回転):
    linuxカーネルではconnectの実装に使用されるタイムアウトパラメータはsndtimeoである
    net/ipv4/af_inet.c
    	timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
    
    	if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
    		int writebias = (sk->sk_protocol == IPPROTO_TCP) &&
    				tcp_sk(sk)->fastopen_req &&
    				tcp_sk(sk)->fastopen_req->data ? 1 : 0;
    
    		/* Error code is set above */
    		if (!timeo || !inet_wait_for_connect(sk, timeo, writebias))
    			goto out;
    
    		err = sock_intr_errno(timeo);
    		if (signal_pending(current))
    			goto out;
    	}

    つまりLinuxプラットフォームではconnectの前にSO_を設定できるSNDTIMOは、接続のタイムアウトを制御する目的を達成します.
    サンプルコードは次のとおりです.
    #include <stdlib.h>
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netdb.h>
    #include <arpa/inet.h>
    #include <errno.h>
    
    int main(int argc, char *argv[])
    {
    	int fd;
    	struct sockaddr_in addr;
    	struct timeval timeo = {3, 0};
    	socklen_t len = sizeof(timeo);
    
    	fd = socket(AF_INET, SOCK_STREAM, 0);
    	if (argc == 4)
    		timeo.tv_sec = atoi(argv[3]);
    	setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeo, len);
    	addr.sin_family = AF_INET;
    	addr.sin_addr.s_addr = inet_addr(argv[1]);
    	addr.sin_port = htons(atoi(argv[2]));
    	if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
    		if (errno == EINPROGRESS) {
    			fprintf(stderr, "timeout
    "); return -1; } perror("connect"); return 0; } printf("connected
    "); return 0; }