UNP卷一chapter6 I/O复用:select和poll函数

时间:2021-06-30 22:50:17

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

1、I/O复用模型:通过调用select或poll函数,阻塞在这两个系统调用中的某一个之上,见下图模型

UNP卷一chapter6 I/O复用:select和poll函数

解释一下,阻塞I/O、非阻塞I/O、同步I/O、异步I/O

阻塞I/O:从调用recvfrom开始到它返回的整段时间内是被阻塞的

非阻塞I/O:调用recvfrom开始,只要无数据准备好,就返回一个EWOULDBLOCK错误。持续轮询内核,以查看某个操作是否就绪

同步I/O:导致请求进程阻塞,直至I/O操作完成

异步I/O:不导致请求进程阻塞

2、select函数

#include<sys/select.h>
#include<sys/time.h>
int select(int maxfdp1, fd_set* readset, fd_set *writeset, fd_set* exceptset, const struct timeval* timeout);
//返回:若有则为跨所有描述符集的就绪描述符数目,若超时则为0,若出错则为-1
i、针对最后一个结构参数说明
struct timeval {
	long tv_sec;
	long tv_usec;
};

当timeout=NULL时,永远等待下去;当指定好时间,等待一段固定时间;当为0时,根本不等待。

调用之前,必要对timeout进行初始化。

ii、中间三个描述符集,数据类型为fd_set,每次调用之前初始化是必要的

void FD_ZERO(fd_set* fdset);//将描述符集清0
void FD_SET(int fd, fd_set * fdset);//在描述符集中开启指定套接字
void FD_CLR(int fd, fd_set* fdset);//在描述符集中关闭指定套接字
int FD_ISSET(int fd, fd_set* fdset);//检测fd_set中的就绪套接字

iii、maxfdp1:待测试的最大描述符加1

调用select函数,指定所关心的描述符的值,函数返回时,结果将指示哪些描述符已就绪。函数返回后,使用FD_ISSET宏来测试fd_set数据类型中的描述符。描述符集内任何与未就绪描述符对应的位返回时均清为0。故每次重新调用select函数时,都得把所有描述符集内所关心的位均置为1。

3、使用select的str_cli函数的实现(此处解决了缓冲区问题)

#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 (; ; ) {
		//每次循环都要对rset进行初始化
		if (stdineof == 0)
			FD_SET(fileno(fp), &rset);//turn on bit for fileno(fp)
		FD_SET(sockfd, &rset);//turn on bit for sockfd
		maxfdp1 = max(fileno(fp), sockfd) + 1;
		Select(maxfdp1, &rset, NULL, NULL, NULL);//阻塞于select调用或是等待标准输入可读或是等待套接字可读

		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 *///SHUT_WR:关闭写,但还能读,SHUT_RD:关闭读,还能写,SHUT_RDWR:关闭读,再关闭写
				FD_CLR(fileno(fp), &rset);//复位
				continue;
			}

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

4、shutdown函数

#include<sys/socket.h>
int shutdown(int sockfd, int howto);//返回:若成功则为0,若出错则为-1

shutdown函数避免了close函数的两个限制,其一,close把描述符的引用计数减1,仅在该计数变为0时才关闭套接字,而shutdown可以不管引用计数就激发tcp的正常连接终止序列;其二,close终止读和写两个方向的数据发送。tcp连接是全双工,有时需要单方向终止连接。

针对传递的给形参howto,可以有的实参为

SHUT_RD:关闭读连接——套接字中不再有数据可接收,而且套接字接收缓冲区中的现有数据都被丢弃;

SHUT_WR:关闭写连接(半关闭)——当前留在套接字发送缓冲区中的数据将被发送掉,后跟tcp的正常连接终止序列;

SHUT_RDWR:关闭读写连接,等效调用shutdown两次,第一次调用指定SHUT_RD,第二次调用SHUT_WR。

5、poll函数

#include<poll.h>
int poll(struct pollfd* fdarray, unsigned long nfds, int timeout);
//返回:若有就绪描述符则为其数目,若超时则为0.若出错则为-1
struct pollfd {
	int fd;//用来检测的描述符
	short events;//有关描述符感兴趣事件
	short revents;//发生在描述符上的感兴趣事件
};
poll第一个参数特点:测试的条件由events成员指定,函数在相应的revents成员中返回该描述符的状态。
/* include fig01 */
#include	"unp.h"
#include	<limits.h>		/* for OPEN_MAX */

int
main(int argc, char **argv)
{
	int					i, maxi, listenfd, connfd, sockfd;
	int					nready;
	ssize_t				n;
	char				buf[MAXLINE];
	socklen_t			clilen;
	struct pollfd		client[OPEN_MAX];
	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);

	client[0].fd = listenfd;//client[0]固定用于监听套接字
	client[0].events = POLLRDNORM;//普通数据可读
	for (i = 1; i < OPEN_MAX; i++)
		client[i].fd = -1;		/* -1 indicates available entry */
	maxi = 0;					/* max index into client[] array */

	for ( ; ; ) {
		nready = Poll(client, maxi+1, INFTIM);//nfds=maxi+1

		if (client[0].revents & POLLRDNORM) {	/* new client connection */
			clilen = sizeof(cliaddr);
			connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
#ifdef	NOTDEF
			printf("new client: %s\n", Sock_ntop((SA *) &cliaddr, clilen));
#endif

			for (i = 1; i < OPEN_MAX; i++)
				if (client[i].fd < 0) {//寻找第一个描述符成员为-1值的位置(可用项)
					client[i].fd = connfd;	/* save descriptor */
					break;
				}
			if (i == OPEN_MAX)
				err_quit("too many clients");

			client[i].events = POLLRDNORM;//设置为普通数据可读
			if (i > maxi)
				maxi = i;				/* max index in client[] array */

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

		for (i = 1; i <= maxi; i++) {	/* check all clients for data */
			if ( (sockfd = client[i].fd) < 0)//首先检测到相应客户就绪描述符
				continue;
			if (client[i].revents & (POLLRDNORM | POLLERR)) {
				if ( (n = read(sockfd, buf, MAXLINE)) < 0) {//读入
					if (errno == ECONNRESET) {//应对客户发送RST情况
							/*4connection reset by client */
#ifdef	NOTDEF
						printf("client[%d] aborted connection\n", i);
#endif
						Close(sockfd);
						client[i].fd = -1;
					} else
						err_sys("read error");
				} else if (n == 0) {//遇见EOF输入
						/*4connection closed by client */
#ifdef	NOTDEF
					printf("client[%d] closed connection\n", i);
#endif
					Close(sockfd);
					client[i].fd = -1;
				} else
					Writen(sockfd, buf, n);//写出

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