socket编程I/O超时函数select封装

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>//使用signal函数
#include <sys/wait.h>//使用wait函数

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define ERR_EXIT(m) \
	do \
	{ \
		perror(m);	\
		exit(EXIT_FAILURE);\
	}while(0)

/*
函数只进行读超时检测,不进行读操作
fd文件描述符;wait_seconds为等待超时秒数,为0表示不检测超时
成功(未超时)返回0,失败返回-1,超时返回-1且errno=ETIMEDOUT
*/
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;
} 

/*
函数只进行读超时检测,不含写操作
fd为文件描述符
fd文件描述符;wait_seconds为等待超时秒数,为0表示不检测超时
成功(未超时)返回0,失败返回-1,超时返回-1且errno=ETIMEDOUT
*/
int write_timeout(int fd, unsigned int wait_seconds)
{
	int ret =0;
	if(wait_seconds > 0)
	{
		fd_set write_fdset;//设置可写套接口集合
		struct timeval timeout;//超时时间结点

		FD_ZERO(&write_fdset);
		FD_SET(fd, &write_fdset);

		timeout.tv_sec = wait_seconds;//只关心秒
		timeout.tv_usec =0;//不关心微秒
		do
		{
			ret = select(fd+1, NULL, &write_fdset, NULL, &timeout);
		}while(ret<0 &&errno == EINTR);//信号中断引起的失败,需要重启检测

		if(ret==0)//没有检测到事件发生,即时间超时
		{
			ret=-1;
			errno = ETIMEDOUT;
		}
		else if(ret == 1)
			ret =0;
	}
	return ret;
} 

/*
accept_timeout - 带超时的accept
fd为套接字
addr:输出参数,返回对方地址
wait_seconds:等待超时秒数,如果为0表示正常模式
成功(未超时)返回已连接套接字,超时返回-1并且errno = ETIMEDOUT
*/
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 = 0;
		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;
		}
	}
	//进行下方代码时,ret初始为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;
}

/*
activate_nonblock —— 设置I/O为非阻塞模式
*/
void activate_nonblock(int fd)
{
	int ret;
	int flags = fcntl(fd, F_GETFL);//获取文件标志
	if(flags == -1)
	{
		ERR_EXIT("fcntl");
	}

	flags |= O_NONBLOCK;//设置非阻塞模式
	ret = fcntl(fd, F_SETFL, flags);
	if(ret == -1)
		ERR_EXIT("fcntl");
}

/*
deactivate_nonblock —— 设置I/O为阻塞模式
*/
void deactivate_nonblock(int fd)
{
	int ret;
	int flags = fcntl(fd, F_GETFL);

	if(flags ==-1)
		ERR_EXIT("fcntl");

	flags &= ~O_NONBLOCK;
	ret = fcntl(fd, F_SETFL, flags);
	if(ret ==-1)
		ERR_EXIT("fcntl");
}


/*
connect_timeout —— connect
addr为要连接的对方地址
wait_seconds:等待超时秒数,如果为0表示正常模式
成功(未超时)返回0,超时返回-1并且errno = ETIMEDOUT
*/
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_nonblock(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{
			//一旦连接建立,套接字可写,所以fd放进可写集合
			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 = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &socklen);
			if(sockoptret == -1)
			{
				return -1;
			}
			if(err ==0)
				ret =0;
			else
			{
				errno = err;
				ret =-1;
			}
		}	
	}
	if(wait_seconds >0)
	{
		deactivate_nonblock(fd);
	}
	return ret;
}





猜你喜欢

转载自blog.csdn.net/qq_22753933/article/details/82844205
今日推荐