第六章 IO复用:select和poll函数

本章大意
讲解了IO复用的相关知识。所谓IO复用,是指内核一旦发现某个或多个IO条件就绪,就通知相应进程,以便作出及时处理。

细节摘录
1. 五种IO模型:阻塞式IO,非阻塞式IO,IO复用,信号驱动IO,异步IO。
2. select的优势其实在于我们可以等待多个描述符的就绪。
3. 异步IO模型没有阻塞,其他类型或多或少都有阻塞阶段。
4. 描述符集的初始化很重要,别漏。
5. 增大描述符集的唯一方法是先改变FD_SETSIZE的值,然后重新编译内核。
6. 初步的IO复用代码:
#include	"unp.h"

void
str_cli(FILE *fp, int sockfd)
{
	int			maxfdp1; //存放描述符的最大值
	fd_set		rset; // 描述符集
	char		sendline[MAXLINE], recvline[MAXLINE]; // 字符缓冲区

	FD_ZERO(&rset); // 描述符清零
	for ( ; ; ) {
		/*
		 * 打开两个标记位
		*/
		FD_SET(fileno(fp), &rset);
		FD_SET(sockfd, &rset);
		/*
		 * 获得最大标记位
		*/
		maxfdp1 = max(fileno(fp), sockfd) + 1;
		/*
		 * 执行IO复用函数
		*/
		Select(maxfdp1, &rset, NULL, NULL, NULL);

		/*
		 * 如果套接字IO可用
		*/
		if (FD_ISSET(sockfd, &rset)) {	
			if (Readline(sockfd, recvline, MAXLINE) == 0)
				err_quit("str_cli: server terminated prematurely");
			Fputs(recvline, stdout);
		}

		/*
		 * 如果IO可用
		*/
		if (FD_ISSET(fileno(fp), &rset)) { 
			if (Fgets(sendline, MAXLINE, fp) == NULL)
				return;		
			Writen(sockfd, sendline, strlen(sendline));
		}
	}
}
7. TCPIP是全双工的,管道,缓冲区容量有限制,这就导致如果上述代码过早地结束,一部分还在线路或者缓冲区上的数据将会被丢失。
8. str_cli函数的再修订版:
/*
 * shutdown函数解决了批量处理的问题
 * 同时针对缓冲区进行处理避免了一些问题的发生(P136)。
*/
#include	"unp.h"

void
str_cli(FILE *fp, int sockfd)
{
	int			maxfdp1, stdineof;
	fd_set		rset;
	char		buf[MAXLINE];
	int		n;

	stdineof = 0;
	FD_ZERO(&rset);
	for ( ; ; ) {
		/* 只有标志为0,我们才去测试标准输入的可读性 */
		if (stdineof == 0)
			FD_SET(fileno(fp), &rset);
		FD_SET(sockfd, &rset);
		maxfdp1 = max(fileno(fp), sockfd) + 1;
		Select(maxfdp1, &rset, NULL, NULL, NULL);

		if (FD_ISSET(sockfd, &rset)) {	/* socket is readable */
			if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
				if (stdineof == 1) // 如果标准输入已经结束后收到终止
					return;		/* normal termination */
				else
					err_quit("str_cli: server terminated prematurely");
			}

			Write(fileno(stdout), buf, n);
		}

		if (FD_ISSET(fileno(fp), &rset)) {  /* input is readable */
			if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0) {
				stdineof = 1; // 标记,表示标准输入已经结束了。
				Shutdown(sockfd, SHUT_WR);	/* send FIN */
				FD_CLR(fileno(fp), &rset);
				continue;
			}

			Writen(sockfd, buf, n);
		}
	}
}
9. str_cli再次修订(使用IO复用替代fork):
/* include fig01 */
#include	"unp.h"

int
main(int argc, char **argv)
{
	/*
	 * 前期工作一样,都是SBL。
	*/
	int					i, maxi, maxfd, listenfd, connfd, sockfd;
	int					nready, client[FD_SETSIZE];
	ssize_t				n;
	fd_set				rset, allset;
	char				buf[MAXLINE];
	socklen_t			clilen;
	struct sockaddr_in	cliaddr, servaddr;

	listenfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family      = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port        = htons(SERV_PORT);

	Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

	Listen(listenfd, LISTENQ);

	maxfd = listenfd; /* 初始化唯一的监听描述符 */
	maxi = -1;
	for (i = 0; i < FD_SETSIZE; i++)
		client[i] = -1;			/* 初始化已连接套接字记录数组 */
	FD_ZERO(&allset); // 初始化描述符集。
	FD_SET(listenfd, &allset);
/* end fig01 */

/* include fig02 */
	for ( ; ; ) {
		/* 
		 * 启用select监听信号发生 
		*/
		rset = allset;		/* structure assignment */
		nready = Select(maxfd+1, &rset, NULL, NULL, NULL);

		if (FD_ISSET(listenfd, &rset)) {	/* 如果监听套接字可读 */
			clilen = sizeof(cliaddr);
			connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
#ifdef	NOTDEF
			printf("new client: %s, port %d\n",
					Inet_ntop(AF_INET, &cliaddr.sin_addr, 4, NULL),
					ntohs(cliaddr.sin_port));
#endif

			for (i = 0; i < FD_SETSIZE; i++)
				if (client[i] < 0) {
					client[i] = connfd;	/* 用记录数组中第一个未被用到的位置记录已连接描述符 */
					break;
				}
			if (i == FD_SETSIZE)
				err_quit("too many clients");   /* 太多的描述符 */

			FD_SET(connfd, &allset);	/* 将连接描述符添加进描述符集 */
			if (connfd > maxfd)
				maxfd = connfd;			/* for select */
			if (i > maxi)
				maxi = i;				/* max index in client[] array */

			if (--nready <= 0)
				continue;				/* no more readable descriptors */
		}

		for (i = 0; i <= maxi; i++) {	/* 接收所有客户的数据 */
			if ( (sockfd = client[i]) < 0)
				continue;
			if (FD_ISSET(sockfd, &rset)) {
				if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
						/*4connection closed by client */
					Close(sockfd);
					/*
					 * 对记录数组和描述符集作出处理
					*/
					FD_CLR(sockfd, &allset);
					client[i] = -1;
				} else
					Writen(sockfd, buf, n);

				if (--nready <= 0)
					break;				/* no more readable descriptors */
			}
		}
	}
}
/* end fig02 */
10. 注意上述代码可能会阻塞于某个客户,因此我们需要作出相应的处理。解决思路有:非阻塞IO,多线程,设置超时函数等等。
11. pselect可以用来防止一种信号永远丢失的情况,参见P143.

吃饭 下午继续
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值