1. 程式人生 > >socket程式設計I/O超時函式select封裝

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