Linux:四种timeout(read,write,accept,connect)

/ Linux / 没有评论 / 437浏览
/*
测试伪代码
*/
int ret;
ret = read_timeout(fd,5);
if(ret == 0)
{
	read(fd...)
}
else if(ret == -1 && errno == ETIMEDOUT)
{
	timeout...
}
else
{
	ERR_EXIT("read_timeout");
}

/*
读超时
*/
int read_timeout(int fd,unsigned int wait_seconds)
{
	int ret=0;
	if(wait_seconds > 0)
	{
		fd_set read_fdset;
		struct timeval timeout;
		
		FD_ZERO(&read_fdset);
		FD_SET(fd,&read_fdset);

		timeout.tv_sec = wait_seconds;
		timeout.tv_usec = 0;

		do{
			ret = select(fd+1,&read_fdset,NULL,NULL,&timeout); //写操作的化是把文件描述符放在写的集合
		}while(ret < 0 && errno = EINTR);

		if(ret == 0)
		{
			ret = -1;
			errno = ETIMEDOUT;
		}
		else if(ret == 1)
		{
			ret = 0;
		}
	}
	return ret;
}


/*
带超时的accept
addr 返回对方的地址
*/
int accept_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds)
{
	int ret;
	socklen_t addrlen = sizeof(struct sockaddr_in);
	if(wait_seconds > 0)
	{
		fd_set accept_fdset;
		struct  timeval timeout;
		FD_ZERO(&accept_fdset);
		FD_SET(fd,&accept_fdset);
		timeout.tv_sec = wait_seconds;
		timeout.tv_usec = wait_seconds;
		do
		{
			ret = select(fd+1,&accept_fdset,NULL,NULL,&timeout);
		}while(ret<0 && errno == EINTR);
		if(ret == -1)
			return -1;
		else if(ret == 0)
		{
			errno = ETIMEDOUT;
			return -1;
		}
	}
	if(addr != NULL)
	{
		ret = accept(fd,(struct sockaddr*)addr,&addrlen);
	}
	else
	{
		ret = accept(fd,NULL,NULL);
	}
	if(ret == -1)
		ERR_EXIT("accept");

	return ret;
}

/*
连接超时
ESTABLISED状态是连接成功的
*/
void activate_monblock(int fd)//将文件描述符设置成非阻塞模式
{
	int ret;
	int flags = fcntl(fd,F_GETFL);
	if(flags == -1)
		ERR_EXIT("fcntl");
	flags |= O_NONBLOCK; // 变成阻塞 flags ~= O_NONBLOCK
	ret = fcntl(fd,F_SETFL,flags);
	if(ret == -1)
		ERR_EXIT("fcntl");
}

int connect_timeout(int fd,struct sockaddr_in *addr,unsigned int wait_seconds)
{
	int ret;
	socklen_t addrlen = sizeof(struct sockaddr_in);
	if(wait_seconds > 0)
		activate_monblock(fd);
	ret = connect(fd,(struct sockaddr*)addr,addrlen);
	if(ret < 0 && errno == EINPROGRESS)
	{
		fd_set connect_fdset;
		struct timeval_timeout;
		FD_ZERO(&connect_fdset);
		FD_SET(fd,&connect_fdset);
		timeout.tv_sec = wait_seconds;
		timeout.tv_usec = 0;
		do
		{
			/*连接建立,套接字可写*/
			ret = select(fd+1,NULL,&connect_fdset,NULL,&timeout);
		}while(ret < 0 && errno = EINTR); //信号中断返回
		if(ret == 0)
		{
			ret = -1;
			errno = ETIMEDOUT;
		}
		else if(ret < 0)
		{
			return -1;
		}
		else if(ret == 1)
		{
			/*ret返回1,可能有两种情况,一种是连接建立成功,一种是套接字产生错误
			此时的错误信息不会被储存在errno中,因此需要调用getsockopt来获取。
			*/
			int err;
			socklen_t socklen = sizeof(err);
			int sockoptret = setsockopt(fd,SOL_SOCKET,SO_ERROR,&on,&socklen);
			if(sockoptret == -1)
				return -1;
			if(err == 0)
				ret = 0;
			else
			{
				errno = err;
				ret = -1;
			}
		}
	}
	if(wait_seconds > 0)
	{
		//在套接口变成阻塞模式
	}
	return ret;
}