【UNIX网络编程】第14章高级I/O函数

1. 套接口超时

在涉及套接口的I/O操作上设置超时的方法有3个:

  • 调用alarm,它在指定超时期满时产生SIGALRM信号;【适用于任何描述字】
  • select中阻塞等待I/O(select有内置的时间限制),以此代替直接阻塞在read或write调用上;
  • 使用SO_RCVTIMEOSO_SNDTIMEO套接口选项,但并非所有实现都支持这两个套接口选项。【仅适用于套接口描述字】

1.2 使用SIGALRM为connect设置超时

  • 它以由调用者指定的超时上限调用connect。前3个参数用于调用connect,第4个参数是等待的秒数。
static void connect_alarm(int);
int connect_timeo(int sockfd, const SA *saptr, socklen_t salen, int nsec)
{
	Sigfunc *sigfunc;
	int n;
	// 为SIGALRM建立一个信号处理函数。现有信号处理函数得以保存,以便在本函数结束时恢复它
	sigfunc = Signal(SIGALRM, connect_alarm);
	// alarm的返回值是这个报警时钟的当前剩余秒数
	if (alarm(nsec) != 0)
		err_msg("connect_timeo: alarm was already set");
	// 如果本调用被中断,即返回EINTR错误,就设置errno = ETIMEDOUT
	if ( (n = connect(sockfd, saptr, salen)) < 0)
	{
		close(sockfd);   // 关闭套接口,以防三路握手继续进行
		if (errno == EINTR)
			errno = ETIMEDOUT;	
	}
	alarm(0);   // turn off the alarm
	Signal(SIGALRM, sigfunc);  //恢复原来的信号处理函数
	return(n);
}
static void connect_alarm(int signo)
{
	// return语句将中断进程主控制流中那个未决的connect调用,使得它返回一个EINTR错误
	return;  // just interrupt the connect()
}

1.3 使用SIGALRM为recvfrom设置超时

  • 本函数通过调用alarm使得一旦在5秒内收不到任何应答就中断recvfrom.
static void sig_alarm(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);
	while (Fgets(sendline, MAXLINE, fp) != NULL) 
	{
		Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
		// 每次调用recvfrom前调用设置了5秒钟的alarm
		alarm(5);
		// 如果recvfrom被信号处理函数中断,则输出信息后继续执行。
		// 如果读到一行来自服务器的文本,则关闭报警时钟并输出服务器的应答
		if ( (n = recvfrom(sockfd, recvline, MAXLINE, 0 NULL, NULL)) < 0)
		{
			if (error == EINTR)
				fprintf(stderr, "socket timeout\n");
			else
				err_sys("recvfrom error");
		}
		else {
			alarm(0);
			recvline[n] = 0;
			Fputs(recvline, stdout);
		}
	}
}
static void sig_alarm(int signo)
{
	return;
}

1.4 使用select为recvfrom设置超时

  • select等待该描述字变为可读,或发生超时。出错返回-1,超时返回0,否则返回的正值为已就绪描述字的数目。
  • 使用Readable_timeo函数,所关注的描述字已变为可读后,才可以调用recvfrom,这保证了recvfrom函数不会阻塞。

int readable_timeo(int fd, int sec)
{
	fd_set rset;
	struct timeval tv;
	FD_ZERO(&rset);
	FD_SET(fd, &rset);
	tv.tv_sec = sec;
	tv.tv_usec = 0;
	// select等待该描述字变为可读,或发生超时。
	// 出错返回-1,超时返回0,否则返回的正值为已就绪描述字的数目
	return (select(fd+1, &rset, NULL, NULL, &tv));
}
void dg_cli (FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
	int n;
	char sendline[MAXLINE], recvline[MAXLINE];
	while (Fgets(sendline, MAXLINE, fp) != NULL)
	{
		Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
		// 使用Readable_timeo函数,所关注的描述字已变为可读后,才可以调用recvfrom,
		// 这保证了recvfrom函数不会阻塞
		if (Readable_timeo(sockfd, 5) == 0)
			fprintf(stderr, "socket timeout\n", );
		else
		{
			n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
			recvline[n] = 0;
			Fputs(recvline, stdout);
		}
	}
}

1.5 recvmsg和sendmsg函数

  • 这两个函数是最通用的I/O函数
struct msghdr
{
	void *msg_name;  // 协议地址
	// msg_namelen对于sendmsg是一个值参数,对于recvmsg是值-结果参数
	socklen_t msg_namelen;  // 协议地址大小
	struct iovec *msg_iov;  // 输入/输出缓冲区数组
	int msg_iovlen;  // msg_iov的元素
	void *msg_control;   // ancillary data
	socklen_t msg_controllen;  // length of ancillary data
	int msg_flags;   // flags returned by recvmsg()

};
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags);

1.6 套接字和标准I/O

标准I/O函数库可用于套接字,不过需要考虑以下几点:

  • 通过调用fdopen,可以从任何一个描述符创建出一个标准I/O流。类似地,通过调用fileno,可以获取一个给定标准I/O流对应的描述符。但select只能用于描述符,因此不得不获取那个标准I/O流的描述符。
  • 以r+类型打开标准I/O流,意味着进行读写,即其可以是全双工的。然而在标准I/O流上,必须在调用一个输出函数之后插入fflush、fseek、fsetpos或rewind调用才能接着调用一个输入函数。【同理,输入函数->fseek、fsetpos或rewind | EOF->输出函数 】

解决办法:一个套接字打开两个标准I/O流:一个用于读、一个用于写。

1.6.1 使用标准I/O的str_echo函数
void str_echo(int sockfd)
{
	char line[MAXLINE];
	FILE *fpin, *fpout;
	fpin = Fdopen(sockfd, "r");
	fpout = Fdopen(sockfd, "w");

	while (Fgets(line, MAXLINE, fpin) != NULL)
		Fputs(line, fpout);
}

标准I/O函数库执行以下三类缓冲:

  • **完全缓冲:**①缓冲区满;②进程显式调用fflush;③进程调用exit终止自身等3种情况时,才发生I/O。【缓冲区大小8192=8KB】
  • 行缓冲:
  • 不缓冲:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值