2009年8月12日星期三

Socket中如何设置连接超时

connect函数默认是阻塞模式,而且默认超时时间随操作系统而已,各Linux版本之间也不尽相同,大多为几分钟。
要想对connect进行超时处理,就必须按如下步骤:
1. 采用fcntl设置非阻塞式连接以实现connect超时处理;
2. 采用select方法来设置socket connect超时;
3. 采用fcntl将socket设置回阻塞式;

如下是Linux下实现源码:
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
int main(int argc, char* argv[])
{
int fd;
int tos = 20; //timeout second
struct sockaddr_in sa;
time_t cur;
if(argc!=3){
printf("%s IP PORTn",argv[0]);
exit(-1);
}
fd=socket(AF_INET, SOCK_STREAM, 0);
if(fd<0){
perror("socket fail");
exit(-1);
}
//select 模型,即设置超时
int ret;
int err;
socklen_t len = sizeof(err);
int flags;
struct timeval timeout;
fd_set rset, wset, exset;
sa.sin_family=AF_INET;
sa.sin_addr.s_addr=inet_addr(argv[1]);
sa.sin_port=htons(atoi(argv[2]));
if(fcntl(fd, F_GETFL, 0) < 0)
perror("fcntl F_GETFL fail");
if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)
perror("fcntl O_NONBLOCK fail");
else
printf("fcntl O_NONBLOCK success.\n");
cur=time(NULL);
printf("before connect: %s", ctime(&cur));
//因为套接字设为NONBLOCK,通常情况下,连接在connect()返回之前是不会建立的,因此它会返回EINPROGRESS错误,如果返回任何其他错误,则要进行错误处理
//if ((ret = connect(fd, (struct sockaddr*)&sa, sizeof(sa))) < 0){
ret = connect(fd, (struct sockaddr*)&sa, sizeof(sa));
cur=time(NULL);
printf("after connect: %s", ctime(&cur));
printf("connect ret=%d, errno=%d.\n", ret, errno);
if(ret < 0){
if(errno != EINPROGRESS) {
printf("errno=%d, EINPROGRESS=%d.\n", errno, EINPROGRESS);
goto DONE;
}
} else
printf("connect success.\n");
//printf("select time=%ds\n", tos);
cur=time(NULL);
printf("before select: %s", ctime(&cur));
FD_ZERO(&rset);
FD_SET(fd, &rset);
wset = rset;
exset = rset;
timeout.tv_sec = tos; //连接超时(秒)
timeout.tv_usec =0;
//select返回值:num 就绪文件数,0:超时,-1:出错
//if((ret = select(fd+1, &rset, &wset, NULL, tos ? &timeout : NULL))
== 0){
//if((ret = select(fd+1, &rset, &wset, &exset, &timeout)) == 0){
ret = select(fd+1, &rset, &wset, &exset, &timeout);
cur=time(NULL);
printf("after select: %s", ctime(&cur));
printf("select ret=%d.\n",ret);
if(ret == 0){
perror("select timeout.\n");
goto DONE;
}else if(ret == -1) {
perror("select error");
goto DONE;
} else{
perror("select success");
//套接字已经准备好
if (FD_ISSET(fd, &rset) || FD_ISSET(fd, &wset)) {
//connect()失败,进行错处理
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
//getsockopt()失败,进行错处理
perror("getsockopt err.\n");
}
if(err){
printf("connect err=%d\n", err);
goto DONE;
} else
printf("connect success.\n");
} else {
//connect()失败,进行错处理
printf("select err: socket not set.\n");
}
}
DONE:
//到这里说明connect()正确返回
//下面恢复套接字阻塞状态
if (fcntl(fd, F_SETFL, flags) < 0)
perror("fcntl F_SETFL fail");
else
printf("fcntl O_BLOCK success.\n");
close(fd);
exit(0);
}

没有评论: