connect超时设置

来源:互联网 发布:金牛星网络是做什么的 编辑:程序博客网 时间:2024/05/16 15:06
socket编程中,当客户端用connect()去连接一个服务端是,如果服务器或网络繁忙,或连接了一个未开机的主机时,客户端发送的SYN包得不到响应,connect函数会很久不返回(阻塞模式下)。具体的超时时间与系统相关,有的可能设置为75秒,有的可能设置为120秒等等。在有的应用中,阻塞太久不是程序想要的行为?

connect超时时间与/proc/sys/net/ipv4/tcp_syn_retries的值有关,这个值决定了重发SYN包的次数,另一方面重发SYN包的间隔时间系列是:1, 2, 4, 8, 16, 32, 64, 120, 120... ...这个时间系列是固定的,所以tcp_syn_retries的值越大,connect超时时间就越长。这是一个系统设置,我们通过改这个值来缩短connect的超时时间显然不太合理。(可以搜索sysctl查看如何修改)

下面我们看看如何用其它方式来修改connect超时时间
方法一:
步骤
  1. 建立socket
  2. 把socket设置为非阻塞模式
  3. 调用connect
  4. 使用select检查该socket描述符是否可写(注意,是可写)
  5. 根据select返回结果判断connect结果
  6. 将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\n");close(clientfd);return -1;}else if (n==0){printf("time out\n");close(clientfd);return -1;}elseprintf("connect sucessed\n");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\n", sk);if (sk>0)close(sk);return 0;}

方法二(转):
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\n");return -1;}perror("connect");return 0;}printf("connected\n");return 0;}



0 0