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

本文详细介绍了一种在网络编程中处理超时问题的方法,包括读、写操作的超时检测,带超时的accept和connect函数,以及如何设置I/O为非阻塞和阻塞模式。这些技巧对于提高网络应用程序的健壮性和效率至关重要。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#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;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值