UNIX网络编程——套接字I/O操作上的超时设置

1、调用alarm(SIGALRM信号处理)

调用alarm,它在指定超时期满时产生SIGALRM信号。

(1)使用SIGALRM为connect设置超时:因为在多线程程序中处理信号非常困难,因此建议只是在未线程化程序 或 单线程化程序中使用该技术。

/* include connect_timeo */
#include	"unp.h"

static void	connect_alarm(int);

int  connect_timeo(int sockfd, const SA *saptr, socklen_t salen, int nsec)
{
	Sigfunc	*sigfunc;
	int		n;

	sigfunc = Signal(SIGALRM, connect_alarm);      //为SIGALRM建立一个信号处理函数
	if (alarm(nsec) != 0)         //调用alarm,设置调用者指定的秒数
		err_msg("connect_timeo: alarm was already set");

	if ( (n = connect(sockfd, saptr, salen)) < 0) {   //调用connect
		close(sockfd);
		if (errno == EINTR)
			errno = ETIMEDOUT;
	}
	alarm(0);			/* turn off the alarm */  //关闭本进程的报警时钟
	Signal(SIGALRM, sigfunc);	/* restore previous signal handler */   // 恢复原来的信号处理函数

	return(n);
}

static void connect_alarm(int signo)       //为SIGALRM建立的信号处理函数
{
	return;		/* just interrupt the connect() */
}


void Connect_timeo_demo(int fd, const SA *sa, socklen_t salen, int sec)
{
	if (connect_timeo(fd, sa, salen, sec) < 0)     //调用connect_timeo
		err_sys("connect_timeo error");
}

(2)使用SIGALRM为recvfrom设置超时:

#include	"unp.h"

static void	sig_alrm(int);

void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
	int	n;
	char	sendline[MAXLINE], recvline[MAXLINE + 1];

	Signal(SIGALRM, sig_alrm);    // 为SIGALRM建立一个信号处理函数

	while (Fgets(sendline, MAXLINE, fp) != NULL) {

		Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

		alarm(5);     // 设置一个5秒钟超时
		if ( (n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL)) < 0) {  //如果recvfrom被我们的信号处理函数中断
			if (errno == EINTR)
				fprintf(stderr, "socket timeout\n");
			else
				err_sys("recvfrom error");
		} else {          //如果读到来自服务器的一行文本
			alarm(0);          //关闭本进程的报警时钟
			recvline[n] = 0;	/* null terminate */
			Fputs(recvline, stdout);
		}
	}
}

static void sig_alrm(int signo)   // 为SIGALRM建立的信号处理函数
{
	return;			/* just interrupt the recvfrom() */
}


2、使用select内置时间限制

将I/O阻塞在select调用上,以此代替直接阻塞在read或者write调用上,并设置select内置时间限制。

#include	"unp.h"

int
readable_timeo(int fd, int sec)
{
	fd_set			rset;
	struct timeval	tv;

	FD_ZERO(&rset);
	FD_SET(fd, &rset);   // 在描述符集只中打开给定的描述符

	tv.tv_sec = sec;     // 把要设置的等待秒数设置在timeval结构中
	tv.tv_usec = 0;

	return(select(fd+1, &rset, NULL, NULL, &tv));    //等待描述符变为可读,或者发生超时
		/* 4> 0 if descriptor is readable */
}
/* end readable_timeo */

int
Readable_timeo(int fd, int sec)
{
	int		n;

	if ( (n = readable_timeo(fd, sec)) < 0)     // 调用readable_timeo
		err_sys("readable_timeo error");
	return(n);
}

//使用、调用示例:
void
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
    int    n;
    char    sendline[MAXLINE], recvline[MAXLINE + 1];

    while (Fgets(sendline, MAXLINE, fp) != NULL) {

        Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

        if (Readable_timeo(sockfd, 5) == 0) {
            fprintf(stderr, "socket timeout\n");      //调用Readable_timeo,直到Readable_timeo告知所关注的描述符变为可读,才调用recvfrom,用来保证recvfrom不会被阻塞
        } else {
            n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
            recvline[n] = 0;    /* null terminate */
            Fputs(recvline, stdout);
        }
    }
}



3、使用较新的SO_RCVTIMEO 和 SO_SNDTIMEO 套接字选项

(1)使用SO_RCVTIMEO套接字选项为recvfrom设置超时

本方法的优势在于一次设置,一旦设置到某个描述符,其超时设置将应用于该描述符上的所有读操作。

#include	"unp.h"

void
dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
	int				n;
	char			sendline[MAXLINE], recvline[MAXLINE + 1];
	struct timeval	tv;

	tv.tv_sec = 5;      //填入期望的超时值
	tv.tv_usec = 0;
	Setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));      //设置SO_RCVTIMEO套接字选项,并指向timeval结构tv

	while (Fgets(sendline, MAXLINE, fp) != NULL) {

		Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);

		n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
		if (n < 0) {
			if (errno == EWOULDBLOCK) {                       //如果I/O操作超时,则recvfrom将返回一个EWOULDBLOCK错误
				fprintf(stderr, "socket timeout\n");
				continue;
			} else
				err_sys("recvfrom error");
		}

		recvline[n] = 0;	/* null terminate */
		Fputs(recvline, stdout);
	}
}

(2)SO_RCVTIMEO套接字选项仅仅应用于读操作, SO_SNDTIMEO 套接字选项仅仅应用于写操作。两者都不能用于设置connect超时。



你可能感兴趣的:(Linux)