Linux 下socket超時(connect超時/recv超時)
connect超時:
目前各平臺通用的設定socket connect超時的辦法是通過select(),具體方法如下:
1.建立socket;
2.將該socket設定為非阻塞模式;
3.呼叫connect();
4.使用select()檢查該socket描述符是否可寫;
5.根據select()返回的結果判斷connect()結果;
6.將socket設回阻塞模式。
下面給出的是我寫的client程式(已經編譯通過):
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <unistd.h>
#include <asm/ioctls.h>
#define MAXDATASIZE 4926
int detect_imap(const char *server ,char *protocol,unsigned short port)
{
int sockfd,numbytes;
char buf[MAXDATASIZE];
struct hostent *he;
struct sockaddr_in their_addr;
if ((he = gethostbyname(server)) == NULL)
{
herror("gethostbyname");
return 0;
}
if ((sockfd=socket(AF_INET,SOCK_STREAM,0)) == -1)
{
perror("socket");
return 0;
}
unsigned long ul=1;
int rm=ioctl(sockfd,FIONBIO,&ul);
if(rm==-1)
{
close(sockfd);
return 0;
}
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(port);
their_addr.sin_addr = *((struct in_addr *)he->h_addr);
bzero(&(their_addr.sin_zero), 8);
if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == 0)
{
printf("connected/n");
}
if(errno!=EINPROGRESS)
{
perror("connect");
printf("cannot connect:%s/n",server);
return 0;
}
struct timeval timeout;
fd_set r;
FD_ZERO(&r);
FD_SET(sockfd,&r);
timeout.tv_sec=0;
timeout.tv_usec=100;
int retval = select(sockfd+1,NULL,&r,NULL,&timeout);
if(retval==-1)
{
perror("select");
return 0;
}
else if(retval == 0)
{
fprintf(stderr,"timeout/n");
return 0;
}
printf("%sconnected/n",server);
unsigned long ul1=0;
rm=ioctl(sockfd,FIONBIO,(unsigned long*)&ul1);
if(rm==-1)
{
close(sockfd);
return 0;
}
if ((numbytes=recv(sockfd,buf,MAXDATASIZE,0)) == -1)
{
perror("recv");
return 0;
}
buf[numbytes] = '/0';
if (0 == strncmp(buf,"* OK",4))
{
printf("Received: %s",buf);
close(sockfd);
return 1;
}
else
{
printf("Error protocol!");
close(sockfd);
return 0;
}
}
int
main(int argc,char *argv[])
{
int i;
if (argc!=2)
{
fprintf(stderr,"usage:client hostname/n");
exit(1);
}
i = detect_imap(argv[1] ,argv[2],143);
printf ("%d",i);
}
以上程式碼工作的很好,並且也可以通過getsockopt()獲得連線發生錯誤的確切資訊,但這總方法難免覺得有些複雜,因為要涉及到阻塞狀態的解除和回置。
這裡有個簡單的操作方法,同樣可以設定連線超時:即通過SO_SNDTIMO套節字引數讓超時操作跳過select。
原因是:Linux核心原始碼中connect的超時引數和SO_SNDTIMO操作的引數一致。
因此,在linux平臺下,可以通過connect之前設定SO_SNDTIMO來達到控制連線超時的目的。
部分修正程式碼如下:
struct timeval timeo;
socklen_t len = sizeof(timeo);
timeo.tv_sec = overtime;
if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeo, len) == -1)
{
strcpy(reason,strerror(errno));
perror("setsockopt");
return 0;
}
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(serverStruct->port);
their_addr.sin_addr = *((struct in_addr *)he->h_addr);
bzero(&(their_addr.sin_zero), 8);
if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1)
{
if (errno == EINPROGRESS)
{
strcpy(reason,"timeout");
return 0;
}
strcpy(reason,strerror(errno));
perror("connect");
return 0;
}
編譯執行:gcc client_select.c -o client_select
./client_select imap.21cn.com
ps:列子是對imap協議進行分析時的程式碼。
recv超時:
如果要設定接收超時,可以用setsockopt()和send超時一樣,只需將如下程式碼新增在connect之後就可以了。
timeout.tv_sec=0;timeout.tv_usec=500;
int result = setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout.tv_sec,sizeof(struct timeval));
if (result < 0)
{
perror("setsockopt");
}