UNP卷一chapter14 高级I/O函数

以下知识点来均来自steven先生所著UNP卷一(version3),刚开始学习网络编程,如有不正确之处请大家多多指正。

本章主要讲述在I/O操作上设置超时的三种方法,然后介绍5种I/O函数中的3种,如何确定套接字接收缓冲区中的数据量,在套接字上使用C的标准I/O函数库,最后讨论等待事件的一些高级方法。东西有点杂,并且与标题有点不太符合,将就吸收吧!

1、套接字超时

i、使用alarm函数和SIGALRM信号,具体举列见代码,如下

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

static void	connect_alarm(int);

int
connect_timeo(int sockfd, const SA *saptr, socklen_t salen, int nsec)//前三个参数用于调用connect函数,最后是等待的秒数
{
	Sigfunc	*sigfunc;
	int		n;

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

	if ((n = connect(sockfd, saptr, salen)) < 0) {//connect函数调用成功返回0,出错返回-1
		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)
{
	return;		/* just interrupt the connect() */
}
/* end connect_timeo */

void
Connect_timeo(int fd, const SA *sa, socklen_t salen, int sec)//包裹函数
{
	if (connect_timeo(fd, sa, salen, sec) < 0)
		err_sys("connect_timeo error");
}
ii、使用select为recvfrom设置超时 ,具体举列见代码,如下
#include	"unp.h"
struct timeval {
	long tv_sec;
	long tv_usec;
};

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

	FD_ZERO(&rset);//描述符集置0
	FD_SET(fd, &rset);//打开相应给定描述符对应的比特位

	tv.tv_sec = sec;
	tv.tv_usec = 0;
	//select函数的最后一个参数即为所设置的时间参数
	return(select(fd + 1, &rset, NULL, NULL, &tv));//阻塞在select且有时间限制,若最后一个参数为NULL,则一直阻塞。
						       //出错时返回-1,超时发生时为0,否则就是就绪描述符数目
	/* 4> 0 if descriptor is readable */
}
iii、使用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));

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

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

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

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

2、5种I/O函数

分别是指read/write、recv/send、readv/writev、recvfrom/sendto、recvmsg/sendmsg。由于recvmsg/sendmsg可以包含前4种情况且运用的最广,此处重点突出介绍。
#include<sys/socket.h>
//均返回:若成功则为读或写的字节数,若出错则为-1
ssize_t read(int fileds, void *buff, size_t nbytes);
ssize_t write(int fileds,const void *buff, size_t nbytes);

ssize_t recv(int sockfd, void *buff, size_t nbytes,int flags);
ssize_t send(int sockfd,const void *buff, size_t nbytes,int flags);

struct iovec {
	void *iov_base;//内存超始地址
	size_t iov_len;//内存大小
};
ssize_t readv(int fileds,const struct iovec* iov, size_t iovcnt);
ssize_t writev(int fileds, const struct iovec* iov, size_t iovcnt);

ssize_t recvfrom(int sockfd, void* buff,size_t nbytes,int flags,struct sockaddr* from,socklent_t* addrlen);
ssize_t sendto(int sockfd,const void* buff, size_t nbytes, int flags,const struct sockaddr* to, socklent_t* addrlen);

ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr* msg, int flags);
//主要讲解一下recvmsg和sendmsg的第二个参数
struct msghdr {
	void* msg_name;//指向协议地址
	socklen_t msg_namelen;//协议地址大小
	struct iovec* msg_iov;//指定输入或输出缓冲区数组,类似readv和writev的第二个参数,即可以携带数据的buff
	int msg_iovlen;//指明Buff的大小
	void* msg_control;//指定可选的辅助数据的位置和大小,msg_controllen对于recvmsg是一个值-结果参数
	socklen_t msg_controllen;//msg_controllen对于recvmsg是一个值-结果参数
	int msg_flags;//recvmsg使用msg_flags成员,而sendmsg则忽略msg_flags成员
};
针对send/recv、sendto/recvfrom、sendmsg/recvmsg函数的flags参数以及结构msghdr内的成员msg_flags,具体标志设置见书上P308页,此处不再码出,相当懒哈!
以及结构msghdr在发收时,所产生的 数据变化情况见下图

3、辅助数据(针对上一点中struct msghdr中msg_control所指向的buff区域所携带的信息)


辅助数据由一个或多个辅助数据对象(ancillary data object)构成,每个对象以一个定义在头文件<sys/socket.h>的cmsghdr结构开头。其结构见下

struct cmsghdr {
	socklen_t cmsg_len;
	int cmsg_level;
	int cmsg_type;
	/*followed by unsigned char cmsg_data[]*/
};

其中cmsg_level、cmsg_type参考值见书上P310 图14-11。

4、套接字和标准I/O(存在着输入输出缓冲区问题)

标准I/O函数库处理UNIX I/O函数时,需要注意自动缓冲输入流和输出流问题以及附加的问题。

标准I/O函数库可用于套接字:

i、通过调用fdopen,可以从任何一个描述符创建出一个标准I/O流;代码如下:

#include	"unp.h"

void
str_echo(int sockfd)
{
	char		line[MAXLINE];
	FILE		*fpin, *fpout;
        //以下问题在于输出流是完全缓冲(后面介绍)
	fpin = Fdopen(sockfd, "r");//从sockfd创建一个标准I/O流,用于流入
	fpout = Fdopen(sockfd, "w");//从sockfd创建一个标准I/O流,用于流出

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

ii、通过调用fdopen,可以获取一个给定标准I/O流对应的描述符;代码如下:

FILE* fp;
iofd = fileno(fp);

iii、tcp和udp套接字是全双工的,标准I/O流也可以是全双工:只要以r+类型打开流,r+意味着读写。但在这样的流上,必须在调用一个输出函数之后插入一个fflush、fseek、fsetpos或rewind调用才能接着调用一个输入函数。反之,调用一个输入函数后也必须插入一个fseek、fsetpos或rewind调用才能接着调用一个输出函数,除非输入函数遇见一个EOF。fseek、fsetpos或rewind这3个函数的问题是它们都调用lseek而lseek用在套接字上只会失败。

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

i、完全缓冲:意味着只在出现下列情况时才发生I/O:缓冲区满,进程显式调用fflush(需要注意一下怎么用才好??),或进程调用exit终止自身。

ii、行缓冲:意味着只在出现下列情况时才发生I/O:碰到一个换行符,进程调用fflush,或进程调用exit终止自身。

iii、不缓冲:意味着每次调用标准I/O输出函数都发生I/O。

标准I/O函数库的大多数UNIX实现使用如下规则:

i、标准错误输出总是不缓冲;

ii、标准输入和标准输出完全缓冲,除非它们指代终端设备(这种情况下它们行缓冲);

iii、所有其他I/O流都是完全缓冲,除非它们指代终端设备(这种情况下它们行缓冲)。

5、高级轮询技术(只介绍/dev/poll接口(此机制代码应被认为不可移值的))

/dev/poll的特殊文件提供了一个可扩展的轮询大量描述符的方法,其与selectt和poll的最大区别:此轮询进程可以预先设置好待查询描述符的列表,然后进入一个循环等待事件发生,每次循环回来时不必再次设置该列表(这点我对比过,有点似懂非懂唉)。

使用的操作过程:打开/dev/poll,轮询进程必须先初始化一个pollfd结构数组。再调用write往/dev/poll设备上写这个结构数组以把它传递给内核,然后执行ioctl(第17章讲解)的DP_POLL命令阻塞自身以等待事件发生。传递给ioctl调用的结构如下:

struct pollfd {
	int fd;
	short events;
	short revents;
};
struct dvpoll {
	struct pollfd* dp_fds;//供ioctl返回时存放pollfd结构数组
	int dp_nfds;//指定上述buff的大小
	int dp_timeout;//0:非阻塞,-1没有超时设置
};

实际应用代码:

#include	"unp.h"
#include	<sys/devpoll.h>

void
str_cli(FILE *fp, int sockfd)
{
	int		stdineof;
	char		buf[MAXLINE];
	int		n;
	int		wfd;
	struct pollfd	pollfd[2];
	struct dvpoll	dopoll;
	int		i;
	int		result;

	wfd = Open("/dev/poll", O_RDWR, 0);//打开/dev/poll
	//轮询进程初始化pollfd结构数组
	pollfd[0].fd = fileno(fp);//获取一个给定I/O流对应的描述符
	pollfd[0].events = POLLIN;
	pollfd[0].revents = 0;

	pollfd[1].fd = sockfd;
	pollfd[1].events = POLLIN;
	pollfd[1].revents = 0;

	Write(wfd, pollfd, sizeof(struct pollfd) * 2);//再调用write往/dev/poll设备上精心pollfd结构数组以传递给内核

	stdineof = 0;
	for (; ; ) {
		/* block until /dev/poll says something is ready */
		dopoll.dp_timeout = -1;//设置超时
		dopoll.dp_nfds = 2;//指定缓冲区大小
		dopoll.dp_fds = pollfd;//供ioctl返回时存放pollfd结构数组
		result = Ioctl(wfd, DP_POLL, &dopoll);//轮询,阻塞于此,等待有事可做。ioctl返回值即已就绪描述符个数

		/* loop through ready file descriptors */
		for (i = 0; i < result; i++) {
			if (dopoll.dp_fds[i].fd == sockfd) {
				/* socket is readable */
				if ((n = Read(sockfd, buf, MAXLINE)) == 0) {
					if (stdineof == 1)//说明之前有EOF结束符
						return;		/* normal termination */
					else
						err_quit("str_cli: server terminated prematurely");
				}

				Write(fileno(stdout), buf, n);
			}
			else {
				/* input is readable */
				if ((n = Read(fileno(fp), buf, MAXLINE)) == 0) {
					stdineof = 1;
					Shutdown(sockfd, SHUT_WR);	/* send FIN */
					continue;
				}

				Writen(sockfd, buf, n);
			}
		}
	}
}

猜你喜欢

转载自blog.csdn.net/tt_love9527/article/details/80378843