版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/clirus/article/details/50577352
这几天发现一个现象,客户端正常连接服务器connect显然不会出现问题。
在异常情况下,如果是服务器出现异常,connect能够立即返回失败;但是当客户端出现异常的情况下,分为两种情况:
一种是不插网线,客户端没有获得ip地址,在这种情况下,connect也可以立即返回错误;
二是但是当客户端插上网线,但是连接网络失败,也就是说能够获取到ip地址,但是和服务器是ping不通的。这种情况下connect就可能会发生阻塞,因为按照《UNIX 网络编程》中讲解,connect的在进行三次握手,如果失败情况,需要等待75s的超市时间的。
我们主要讨论第二种情况如何解决,可以让connect快速返回结果,不至于阻塞等待超长的时间。
如下是我的代码
/******************************
* Time out for connect()
******************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#define TIME_OUT_TIME 20 //connect超时时间20秒
bool setBlockOpt(int m_fd,bool blocked)
{
#ifndef WIN32
int flags;
flags = fcntl(m_fd, F_GETFL, 0);
if(flags < 0)
{
return false;
}
if(blocked)
{
printf("Set BLOCK !!!\n");
flags &= ~O_NONBLOCK;
}
else
{
printf("Set NONBLOCK !!!\n");
flags |= O_NONBLOCK;
}
if(fcntl(m_fd, F_SETFL, flags) < 0)
{
return false;
}
#else
u_long ulValue;
if(blocked)
{
ulValue = 1;
}
else
{
ulValue = 0;
}
int n = ioctlsocket(m_fd, FIONBIO, &ulValue);
if (n != 0)
{
return false;
}
#endif
return true;
}
int connectWithTimeout(int m_fd,int timeout)
{
int selectFlag = -1;
int error=-1, len;
len = sizeof(int);
bool ret = false;
int connectFlag = -1;
const char* m_ip = "115.239.210.27";
int m_port = 80;
if("" == m_ip || 0 > m_port)
{
return -1;
}
if(m_fd < 0 && "" != m_ip && m_port >=0)
{
m_fd = socket(AF_INET, SOCK_STREAM, 0);
if(m_fd < 0)
{
return -1;
}
}
if(m_fd < 0)
{
return -1;
}
struct sockaddr_in servAddr;
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons((unsigned short)m_port);
servAddr.sin_addr.s_addr = inet_addr(m_ip);
setBlockOpt(m_fd,false); //设置为非阻塞模式
if( (connectFlag= connect(m_fd, (struct sockaddr *) &servAddr, sizeof(servAddr)) < 0))
{
if(errno != EINPROGRESS)
{
goto done;
}
}
else
{
ret = true;
goto done;
}
timeval tm;
tm.tv_sec = timeout/1000;
tm.tv_usec = timeout%1000;
fd_set rest, west;
FD_ZERO(&rest);
FD_ZERO(&west);
FD_SET(m_fd, &rest);
FD_SET(m_fd, &west);
if( (selectFlag = select(m_fd+1, &rest, &west, NULL, &tm)) > 0)
{
//如果套接口及可写也可读,需要进一步判断
if(FD_ISSET(m_fd, &rest) && FD_ISSET(m_fd, &west))
{
if(getsockopt(m_fd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len) < 0)
{
printf("getsockopt error!!!\n");
}
else
{
if(error == 0)
{
ret = true;
}
else
{
printf("connect getsockopt error!!! %d\n",error);
}
}
}
//如果套接口可写不可读,则链接完成
else if(FD_ISSET(m_fd, &west) && !FD_ISSET(m_fd, &rest))
{
ret = true;
}
}
else if(selectFlag == 0)
{
printf("connect select timeout!!!\n");
}
else
{
printf("connect select error!!!\n");
}
done:
setBlockOpt(m_fd,true);// 设置为阻塞模式
if(!ret)
{
return -1;
}
return 0;
}
int main(int argc,char* argv[])
{
if(argc <= 1)
{
printf("input error!!!\n");
exit(1);
}
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
exit(1);
}
if(connectWithTimeout(sockfd,atoi(argv[1])) == 0)
{
printf("connect sucess!!!\n");
}
else
{
printf("connect filed!!!\n");
}
close(sockfd);
return 0;
}
原理很简单,就是先把套接字设置为非阻塞,因为在非阻塞情况下,connect的结果是立即返回的,然后我们再使用select或者poll等机制来检测套接字一定的时间,如果在超时时间内不可写,则认为connect失败,然后需要把套接字重新设置为阻塞,当然如果你不需要在阻塞模式下工作,可以不用设置。
如上,我们就可以对connect的超时进行可控。