UNIX网络编程二. I/O复用:select和poll函数

本文深入探讨了0.select、poll、epoll等IO复用技术的原理与应用,并通过服务端与客户端的具体代码实例,详细解释了五种IO模型的工作机制及其优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0.select、poll、epoll的比较

1. 5种IO模型

五种IO模型详解及优缺点

2.文件描述符就绪状态

书本p130

3. select函数

linux select函数详解详细讲解了select各个参数的意义,以及调用select的流程图

服务端代码:

#include	"unp.h"

int
main(int argc, char **argv)
{
	int					i, maxi, maxfd, listenfd, connfd, sockfd;
	int					nready, client[FD_SETSIZE];//最大FD_SETSIZE:支持并发数目1024个连接 存放套接字connfd文件描述符(init -1)
	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);//uint32_t, 0
	servaddr.sin_port        = htons(SERV_PORT);//9877

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

	Listen(listenfd, LISTENQ);

	maxfd = listenfd;			/* initialize  				  表示读集rset中下标最大的那个文件描述符*/
	maxi = -1;					/* index into client[] array  表示client中有效的最大下标*/
								//client数组初始化-1
	for (i = 0; i < FD_SETSIZE; i++)
		client[i] = -1;			/* -1 indicates available entry */

	//a.全置为0
	FD_ZERO(&allset);
	//b.监听套接字放入rset(布置感兴趣事件)
	FD_SET(listenfd, &allset);

	for ( ; ; ) {
		rset = allset;		/* 每次都要重新布置 */
		nready = Select(maxfd+1, &rset, NULL, NULL, NULL);/*返回就绪描述符数量*/

		if (FD_ISSET(listenfd, &rset)) {	/* 如果监听套接字可写了,说明有新的连接 */
			clilen = sizeof(cliaddr);
			//c.
			connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);

			for (i = 0; i < FD_SETSIZE; i++) {
				if (client[i] < 0) {
					client[i] = connfd;	/* 在client数组中找到第一个可以用的位置(值为-1),保存下connfd*/
					break;
				}
			}
			if (i == FD_SETSIZE)        /* 没有可用位置了,出错*/
				err_quit("too many clients");
			//d.
			FD_SET(connfd, &allset);	/* 布置感兴趣事件,把连接套接字connfd加入rset */
			if (connfd > maxfd)
				maxfd = connfd;			/* 更新maxfd */
			if (i > maxi)
				maxi = i;				/* 更新maxi */

			if (--nready <= 0)			/* 除了listenfd之外没有更多的可读文件描述符了*/
				continue;				
		}

		for (i = 0; i <= maxi; i++) {	/* 对每个客户检查(selest只能这么低效,不能知道是哪个客户套接字可读) */
			if ( (sockfd = client[i]) < 0)	/*如果为-1,说明已经这个客户已经和服务器关闭连接*/
				continue;
			if (FD_ISSET(sockfd, &rset)) {
				//e.
				if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {/*该客户关闭连接,则要关闭已连接套接字*/
					//f.										/*rset那一位清0,client那一位置变-1 */
					Close(sockfd);
					//g.
					FD_CLR(sockfd, &allset);
					client[i] = -1;
				} else		/*正常状态,往服务器写*/
					//h.			
					Writen(sockfd, buf, n);

				if (--nready <= 0)			/* 除了该客户已连接套接字之外没有更多的可读文件描述符了*/
					break;				
			}
		}
	}
}
/* end fig02 */


图中的a,b,c…对应写在代码注释了
在这里插入图片描述

拒绝服务型攻击

一个恶意的客户连接到服务器,发送一个字节的数据后进入睡眠,服务器会一直阻塞在read上,以至于不能给其他客户提供服务。所以,当一个服务器在处理多个客户时,他绝对不能阻塞于只与单个客户相关的某个函数调用,否则服务器可能被挂起,拒绝为其他客户提供服务。解决方法:

  1. 使用非阻塞IO
  2. 创建一个子进程或一个线程来服务每个客户
  3. 为每个IO操作设置超时时间

客户端代码

主要对str_cli函数的修改,这里用了shutdown函数,替代close
UNIX网络编程——shutdown 与 close 函数 的区别
同时用read而不是readline和fgets(readline和fgets每次只返回第一行,其余的在缓冲区)

#include	"unp.h"

int
main(int argc, char **argv)
{
	int					sockfd;
	struct sockaddr_in	servaddr;

	if (argc != 2)
		err_quit("usage: tcpcli <IPaddress>");

	sockfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(SERV_PORT);				//服务端服务端口9877
	Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);	//命令行传入服务端地址

	Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));

	str_cli(stdin, sockfd);		/* do it all */

	exit(0);
}


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

	stdineof = 0;						//标志位
	FD_ZERO(&rset);
	for ( ; ; ) {
		if (stdineof == 0)				//标准输入stdin只要没有遇到EOF,就关注stdin的可读性
			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)					/* 已经在标准输入遇到过EOF了,是正常终止*/
					return;							/* normal termination */
				else								/*服务器过早终止*/
					err_quit("str_cli: server terminated prematurely");
			}

			Write(fileno(stdout), buf, n);	//正常情况往stdout写
		}

		if (FD_ISSET(fileno(fp), &rset)) {  /* input is readable */
			if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0) {
				stdineof = 1;				/* stdin遇到EOF*/
				Shutdown(sockfd, SHUT_WR);	/* send FIN 关闭写一半*/
				FD_CLR(fileno(fp), &rset);  /* stdin清0 */
				continue;
			}

			Writen(sockfd, buf, n);			//正常情况往服务器写
		}
	}
}

4. poll函数

服务端代码

Linux网络编程——I/O复用之poll函数介绍poll函数
和select非常类似

/* 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];//不同于select,这个client数组的内容是结构体pollfd
	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].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);
		if (client[0].revents & POLLRDNORM) {	/* => FD_ISSET(listenfd, &rset), new client connection */
			clilen = sizeof(cliaddr);
			connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
			for (i = 1; i < OPEN_MAX; i++)
				if (client[i].fd < 0) {
					client[i].fd = connfd;	/* save descriptor */
					break;
				}
			if (i == OPEN_MAX)
				err_quit("too many clients");

			client[i].events = POLLRDNORM;//=> FD_SET(connfd, &allset)
			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)) {// =>FD_ISSET(sockfd, &rset)
				if ( (n = read(sockfd, buf, MAXLINE)) < 0) {
					if (errno == ECONNRESET) {
							/*4connection reset by client */
						Close(sockfd);
						client[i].fd = -1;
					} else
						err_sys("read error");
				} else if (n == 0) {
						/*4connection closed by client */
					Close(sockfd);
					client[i].fd = -1;
				} else
					Writen(sockfd, buf, n);

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值