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");
}