客户端在连接服务器时,可能会出现问题,导致三次握手无法完成,持续重试,表现在客户端程序的行为就是卡在connect调用上无法返回,这样的客户端是非常不友好的。
下面是我以前做开发工程师的时候常常在项目中使用的可控制超时的connect源码,3秒超时,粘贴即可使用。
正确程序:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int fd, retval;
struct sockaddr_in addr;
struct timeval timeo = {3,0};
socklen_t len = sizeof(timeo);
fd_set set;
fd = socket(AF_INET, SOCK_STREAM, 0);
if (argc == 4) timeo.tv_sec = atoi(argv[3]);
int savefl = fcntl(fd,F_GETFL);
fcntl(fd, F_SETFL, savefl | O_NONBLOCK);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
printf( "%d\n ", time(NULL));
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0){
close(fd);
printf( "connected..1\n ");
return 0;
}
if (errno != EINPROGRESS){
close(fd);
perror( "connect..2 ");
return -1;
}
FD_ZERO(&set);
FD_SET(fd, &set);
retval = select(fd + 1, NULL, &set, NULL, &timeo);
if (retval == -1)
{
close(fd);
perror( "select ");
return -1;
}
else if(retval == 0)
{
close(fd);
fprintf(stderr, "timeout\n ");
printf( "%d\n ", time(NULL));
return 0;
}
if(FD_ISSET (fd,&set))
{
int error = 0;
socklen_t len = sizeof (error);
if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
{
printf ( "getsockopt fail,connected fail\n ");
return -1;
}
if (error == ETIMEDOUT)
{
printf ( "connected timeout\n ");
}
if(error == ECONNREFUSED)
{
printf( "No one listening on the remote address.\n ");
return -1;
}
}
printf ( "connected .. 3\n ");
fcntl(fd, F_SETFL, savefl);
close (fd);
return 0;
}
有问题的程序:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int fd, retval;
struct sockaddr_in addr;
struct timeval timeo = {3, 0};
socklen_t len = sizeof(timeo);
fd_set set;
fd = socket(AF_INET, SOCK_STREAM, 0);
if (argc == 4) timeo.tv_sec = atoi(argv[3]);
int savefl = fcntl(fd,F_GETFL);
fcntl(fd, F_SETFL, savefl | O_NONBLOCK);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
printf( "%d\n ", time(NULL));
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0)
{
close(fd);
printf( "connected..1\n ");
return 0;
}
if (errno != EINPROGRESS)
{
close(fd);
perror( "connect..2 ");
return -1;
}
FD_ZERO(&set);
FD_SET(fd, &set);
retval = select(fd + 1, NULL, &set, NULL, &timeo);
if (retval == -1)
{
close(fd);
perror( "select ");
return -1;
}
else if(retval == 0)
{
close(fd);
fprintf(stderr, "timeout\n ");
printf( "%d\n ", time(NULL));
return 0;
}
printf( "connected .. 3\n ");
fcntl(fd, F_SETFL,savefl);
close(fd);
return 0;
}
注意:
1.两个程序连远程主机都没问题
2.有问题的程序连本地任何端口都不会报错
3.差别在于好程序比有问题的程序多了一个SO_ERROR的检测,这个检测在man connect中被指定了要检测
EINPROGRESS
The socket is non-blocking and the connection cannot be completed immediately. It is possible to select(2) or poll(2) for completion by selecting the socket for writing. After select(2) indicates writability, use getsockopt(2) to read the SO_ERROR option at level SOL_SOCKET to determine whether connect() completed successfully (SO_ERROR is zero) or unsuccessfully (SO_ERROR is one of the usual error codes listed here, explaining the reason for the failure).