• 【文末送书】计算机网络编程 | epoll详解


    在这里插入图片描述

    欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C++、Python、Matlab,机器人运动控制、多机器人协作,智能优化算法,滤波估计、多传感器信息融合,机器学习,人工智能等相关领域的知识和技术。关注公粽号 《机器和智能》 回复关键词 “python项目实战” 即可获取美哆商城视频资源!


    博主介绍:
    CSDN优质创作者,CSDN实力新星,CSDN内容合伙人;
    阿里云社区专家博主;
    华为云社区云享专家;
    51CTO社区入驻博主,掘金社区入驻博主,支付宝社区入驻博主,博客园博主。



    专栏:《网络编程》


    事件模型

    EPOLL事件有两种模型:

    • Edge Triggered (ET) 边缘触发只有数据到来才触发,不管缓存区中是否还有数据。
    • Level Triggered (LT) 水平触发只要有数据都会触发。

    思考如下步骤:

    • 1.假定我们已经把一个用来从管道中读取数据的文件描述符(RFD)添加到epoll描述符。
    • 2.管道的另一端写入了2KB的数据。
    • 3.调用epoll_wait,并且它会返回RFD,说明它已经准备好读取操作。
    • 4.读取1KB的数据。
    • 5.调用epoll_wait……

    在这个过程中,有两种工作模式:

    ET模式

    ET模式即Edge Triggered工作模式。
    如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。

    1. 基于非阻塞文件句柄
    2. 只有当read或者write返回EAGAIN(非阻塞读,暂时无数据)时才需要挂起、等待。但这并不是说每次read时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。

    LT模式

    LT模式即Level Triggered工作模式。
    与ET模式不同的是,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll,无论后面的数据是否被使用。
    LT(level triggered):LT是缺省的工作方式,并且同时支持block和no-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。
    ET(edge-triggered):ET是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知。请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once).

    基于管道epoll ET触发模式

    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define MAXLINE 10
    
    int main(int argc, char *argv[])
    {
    	int efd, i;
    	int pfd[2];
    	pid_t pid;
    	char buf[MAXLINE], ch = 'a';
    
    	pipe(pfd);
    	pid = fork();
    	if (pid == 0) {
    		close(pfd[0]);
    		while (1) {
    			for (i = 0; i < MAXLINE/2; i++)
    				buf[i] = ch;
    			buf[i-1] = '\n';
    			ch++;
    
    			for (; i < MAXLINE; i++)
    				buf[i] = ch;
    			buf[i-1] = '\n';
    			ch++;
    
    			write(pfd[1], buf, sizeof(buf));
    			sleep(2);
    		}
    		close(pfd[1]);
    	} else if (pid > 0) {
    		struct epoll_event event;
    		struct epoll_event resevent[10];
    		int res, len;
    		close(pfd[1]);
    
    		efd = epoll_create(10);
    		/* event.events = EPOLLIN; */
    		event.events = EPOLLIN | EPOLLET;		/* ET 边沿触发 ,默认是水平触发 */
    		event.data.fd = pfd[0];
    	epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);
    
    		while (1) {
    			res = epoll_wait(efd, resevent, 10, -1);
    			printf("res %d\n", res);
    			if (resevent[0].data.fd == pfd[0]) {
    				len = read(pfd[0], buf, MAXLINE/2);
    				write(STDOUT_FILENO, buf, len);
    			}
    		}
    		close(pfd[0]);
    		close(efd);
    	} else {
    		perror("fork");
    		exit(-1);
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    基于网络C/S模型的epoll ET触发模式

    server

    /* server.c */
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define MAXLINE 10
    #define SERV_PORT 8080
    
    int main(void)
    {
    	struct sockaddr_in servaddr, cliaddr;
    	socklen_t cliaddr_len;
    	int listenfd, connfd;
    	char buf[MAXLINE];
    	char str[INET_ADDRSTRLEN];
    	int i, efd;
    
    	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, (struct sockaddr *)&servaddr, sizeof(servaddr));
    
    	listen(listenfd, 20);
    
    	struct epoll_event event;
    	struct epoll_event resevent[10];
    	int res, len;
    	efd = epoll_create(10);
    	event.events = EPOLLIN | EPOLLET;		/* ET 边沿触发 ,默认是水平触发 */
    
    	printf("Accepting connections ...\n");
    	cliaddr_len = sizeof(cliaddr);
    	connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
    	printf("received from %s at PORT %d\n",
    			inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
    			ntohs(cliaddr.sin_port));
    
    	event.data.fd = connfd;
    	epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);
    
    	while (1) {
    		res = epoll_wait(efd, resevent, 10, -1);
    		printf("res %d\n", res);
    		if (resevent[0].data.fd == connfd) {
    			len = read(connfd, buf, MAXLINE/2);
    			write(STDOUT_FILENO, buf, len);
    		}
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    client

    /* client.c */
    #include 
    #include 
    #include 
    #include 
    
    #define MAXLINE 10
    #define SERV_PORT 8080
    
    int main(int argc, char *argv[])
    {
    	struct sockaddr_in servaddr;
    	char buf[MAXLINE];
    	int sockfd, i;
    	char ch = 'a';
    
    	sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
    	bzero(&servaddr, sizeof(servaddr));
    	servaddr.sin_family = AF_INET;
    	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    	servaddr.sin_port = htons(SERV_PORT);
    
    	connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    
    	while (1) {
    		for (i = 0; i < MAXLINE/2; i++)
    			buf[i] = ch;
    		buf[i-1] = '\n';
    		ch++;
    
    		for (; i < MAXLINE; i++)
    			buf[i] = ch;
    		buf[i-1] = '\n';
    		ch++;
    
    		write(sockfd, buf, sizeof(buf));
    		sleep(10);
    	}
    	Close(sockfd);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    基于网络C/S非阻塞模型的epoll ET触发模式

    server

    /* server.c */
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define MAXLINE 10
    #define SERV_PORT 8080
    
    int main(void)
    {
    	struct sockaddr_in servaddr, cliaddr;
    	socklen_t cliaddr_len;
    	int listenfd, connfd;
    	char buf[MAXLINE];
    	char str[INET_ADDRSTRLEN];
    	int i, efd, flag;
    
    	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, (struct sockaddr *)&servaddr, sizeof(servaddr));
    
    	listen(listenfd, 20);
    
    	struct epoll_event event;
    	struct epoll_event resevent[10];
    	int res, len;
    	efd = epoll_create(10);
    	/* event.events = EPOLLIN; */
    	event.events = EPOLLIN | EPOLLET;		/* ET 边沿触发 ,默认是水平触发 */
    
    	printf("Accepting connections ...\n");
    	cliaddr_len = sizeof(cliaddr);
    	connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
    	printf("received from %s at PORT %d\n",
    			inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
    			ntohs(cliaddr.sin_port));
    
    	flag = fcntl(connfd, F_GETFL);
    	flag |= O_NONBLOCK;
    	fcntl(connfd, F_SETFL, flag);
    	event.data.fd = connfd;
    	epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);
    
    	while (1) {
    		printf("epoll_wait begin\n");
    		res = epoll_wait(efd, resevent, 10, -1);
    		printf("epoll_wait end res %d\n", res);
    
    		if (resevent[0].data.fd == connfd) {
    			while ((len = read(connfd, buf, MAXLINE/2)) > 0)
    				write(STDOUT_FILENO, buf, len);
    		}
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    client

    /* client.c */
    #include 
    #include 
    #include 
    #include 
    
    #define MAXLINE 10
    #define SERV_PORT 8080
    
    int main(int argc, char *argv[])
    {
    	struct sockaddr_in servaddr;
    	char buf[MAXLINE];
    	int sockfd, i;
    	char ch = 'a';
    
    	sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
    	bzero(&servaddr, sizeof(servaddr));
    	servaddr.sin_family = AF_INET;
    	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
    	servaddr.sin_port = htons(SERV_PORT);
    
    	connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    
    	while (1) {
    		for (i = 0; i < MAXLINE/2; i++)
    			buf[i] = ch;
    		buf[i-1] = '\n';
    		ch++;
    
    		for (; i < MAXLINE; i++)
    			buf[i] = ch;
    		buf[i-1] = '\n';
    		ch++;
    
    		write(sockfd, buf, sizeof(buf));
    		sleep(10);
    	}
    	Close(sockfd);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    epoll的三种工作模式

    水平触发模式 - (根据读来解释)

    • 只要fd对应的缓冲区有数据,epoll_wait就会返回
    • 返回的次数与发送数据的次数没有关系
    • epoll默认的工作模式

    边沿触发模式 - ET

    • fd - 默认阻塞属性
    • 客户端给server发数据:
      发一次数据server 的 epoll_wait就返回一次
      不在乎数据是否读完
      如果读不完,如何把数据全部读出来?
      while(recv());
      数据读完之后recv会阻塞
      解决阻塞问题 —— 设置非阻塞fd

    边沿非阻塞触发

    • 效率最高

    水平触发模式会多次返回,只要server的read缓冲区有数据,epoll_wait就返回,也就会通知server去读数据,那么在循环检测的时候,只要server的read缓冲区有数据,epoll_wait就会多次调用,多次返回,并通知server去读数据;假如说client发送过了100个数据,也就是serve的read缓冲区有100个数据,但是调用recv函数的时候只能读50个数据,而本次循环只调用了一次recv,那么只能下次循环再读剩余的50个数据,所以下次循环检测的时候,epoll_wait还是会返回,因为缓冲区还是剩余数据。这就是水平触发模式。这样的话虽然client只发了1次,但是epoll_wait会通知两次server去读数据。 —— (printf函数是标准C库函数,C库函数都有一个默认缓冲区,printf的大小是8K。printf函数是行缓冲,使用printf函数的时候,如果不加 \n 会默认等到写满的时候才打印内容,加 \n 会强制把缓冲区的内容打印出来。另外 \0 表示结束,不加 \0 就会一直输出直到遇到 \0,用write(STDOUT_FILENO)替代printf函数就可以解决这些问题。)
    边沿触发模式,client发一次数据epoll_wait只返回一次,也就只读一次,这样的话server的read缓冲区可能会有很多数据堆积,server读数据的时候可能读到的是上一次剩余的数据,并且只有client发的时候,epoll_wait才会通知server去读数据,边沿触发模式尽可能减少了epoll_wait的调用次数,缺点是数据有可能读不完导致堆积。

    推书推荐

    书名:《速学Linux:系统应用从入门到精通》
    在这里插入图片描述
    购买链接:点击购买

    • 如果你是刚刚开始学习Linux的小白,那么本书可作为入门宝典,带你快速入门Linux。
    • 如果你希望获得更多超值内容,那么本书为你提供150段教学视频+电子教案+学习资料,更有价值50元的5节精品线上课程。
    • 如果你希望获得更多实战经验,那么本书提供了47个知识拓展和220个动手练习

    🎉本次送2套书,评论区抽2位小伙伴送书
    🎉活动时间:截止到 2023-10-07 10:00:00
    🎉抽奖方式:评论区随机抽奖。
    🎉参与方式:关注博主、点赞、收藏,评论。
    ❗注意:一定要关注博主,不然中奖后将无效!
    🎉通知方式:通过私信联系中奖粉丝。
    💡提示:有任何疑问请私信公粽号 《机器和智能》


    在这里插入图片描述
    在这里插入图片描述


  • 相关阅读:
    面试题:Rabbitmq怎么保证消息的可靠性?
    【分享 10 个日常使用的脚本】
    数字化转型具体包含哪些内容?
    【JavaEE】Spring Boot MyBatis详解(一)
    SAP 采购订单行项目屏幕增强(BADI)
    linux可用内存不足如何排查清理
    Github贡献PR六部曲
    基于weixin小程序乡村旅游系统的设计
    登录网页优化与最佳做法
    面向对象的照妖镜——UML类图绘制指南
  • 原文地址:https://blog.csdn.net/qq_43471489/article/details/130515119