Linux编程入门三网络编程三 epoll的LT和ET模式以及EPOLLONESHOT事件
epoll对文件描述符的操作有两种模式:LT(Level Trigger 电平触发)模式和ET(Edge Trigger 边沿触发)模式。
LT是默认的工作模式,这种模式下epoll相当于一个效率较高的poll。对于采用LT工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该使事件。这样,当应用程序下一次调用epoll_wait时,epoll_wait还会再次向应用程序通告此事件,直到该事件被处理。
ET是高效的工作模式,对于采用ET模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件。
ET模式在很大程序上降低了同一epoll事件被重复触发的次数,因此效率要比LT模式高。每个使用ET模式的文件描述符都应该是非阻塞的。如果文件描述符是阻塞的,那么读或写操作将会因为没有后续的事件而一直处于阻塞状态。
1 #include <sys/types.h>2 #include <sys/socket.h>3 #include <netinet/in.h>4 #include <arpa/inet.h>5 #include <assert.h>6 #include <stdio.h>7 #include <unistd.h>8 #include <errno.h>9 #include <string.h>10 #include <fcntl.h>11 #include <stdlib.h>12 #include <sys/epoll.h>13 #include <pthread.h>14 #define MAX_EVENT_NUMBER 102415 #define BUFFER_SIZE 1016 17 //将文件描述符设置成非阻塞的18 int setnonblocking(int fd);19 //将文件描述符fd上的EPOLLIN注册到epollfd指示的epoll内核事件表中20 //参数enable_et指定是否对fd启用ET模式21 void addfd(int epollfd, int fd, bool enable_et);22 //LT模式工作流程23 void lt(epoll_event* events, int number, int epollfd, int listenfd);24 //ET模式工作流程25 void et(epoll_event* events, int number, int epollfd, int listenfd);26 27 int main(int argc, char* argv[])28 {29 if(argc <= 2)30 {31 printf("usage: %s ip_address port_number", basename(argv[0]));32 return 1;33 }34 const char* ip = argv[1];35 int port = atoi(argv[2]);36 int ret = 0;37 struct sockaddr_in address;38 bzero(&address,sizeof(address));39 address.sin_family = AF_INET;40 inet_pton(AF_INET,ip,&address.sin_addr);41 address.sin_port = htons(port);42 int listenfd = socket(PF_INET,SOCK_STREAM,0);43 assert(listenfd>=0);44 ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));45 assert(ret!=-1);46 ret = listen(listenfd,5);47 assert(ret!=-1);48 49 epoll_event events[MAX_EVENT_NUMBER];50 int epollfd = epoll_create(5);51 assert(epollfd!=-1);52 addfd(epollfd, listenfd, true); //ET模式53 while(1)54 {55 int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);56 if(ret < 0)57 {58 printf("epoll failure\n");59 break;60 }61 lt(events,ret,epollfd,listenfd); //使用LT模式62 //et(events,ret,epollfd,listenfd); //使用ET模式63 }64 close(listenfd);65 return 0;66 }67 68 int setnonblocking(int fd)69 {70 int old_option = fcntl(fd, F_GETFL);71 int new_option = old_option|O_NONBLOCK;72 fcntl(fd,F_SETFL,new_option);73 return old_option;74 }75 void addfd(int epollfd, int fd, bool enable_et)76 {77 epoll_event event;78 event.data.fd = fd;79 event.events = EPOLLIN;80 if(enable_et)81 {82 event.events |= EPOLLET;83 }84 epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);85 setnonblocking(fd);86 }87 void lt(epoll_event* events, int number, int epollfd, int listenfd)88 {89 char buf[BUFFER_SIZE];90 for(int i = 0; i < number; i++)91 {92 int sockfd = events[i].data.fd;93 if(sockfd == listenfd)94 {95 struct sockaddr_in client_address;96 socklen_t client_addrlength = sizeof(client_address);97 int connfd = accept(listenfd, (struct sockaddr*)&client_address, &clie nt_addrlength);98 addfd(epollfd, connfd, false);99 }else if(events[i].events & EPOLLIN){
100 //只要socket读缓存中还有未读出的数据,这段代码就被触发
101 printf("event trigger once");
102 memset(buf, '\0', BUFFER_SIZE);
103 int ret = recv(sockfd,buf,BUFFER_SIZE-1,0);
104 if(ret<=0)
105 {
106 close(sockfd);
107 continue;
108 }
109 printf("get %d bytes of content: %s\n",ret,buf);
110
111 }else{
112 printf("something else happend\n");
113 }
114 }
115 }
116 void et(epoll_event* events, int number, int epollfd, int listenfd)
117 {
118 char buf[BUFFER_SIZE];
119 for(int i = 0; i < number; i++)
120 {
121 int sockfd = events[i].data.fd;
122 if(sockfd == listenfd)
123 {
124 struct sockaddr_in client_address;
125 socklen_t client_addrlength = sizeof(client_address);
126 int connfd = accept(listenfd,(struct sockaddr*)&client_address,&client _addrlength);
127 addfd(epollfd,connfd,true);
128 }else if(events[i].events & EPOLLIN){
129 //这段代码不会被重复触发,所以我们循环读取数据,确保把socket读缓存中的所有数据读出
130 printf("event trigger once\n");
131 while(1)
132 {
133 memset(buf,'\0', BUFFER_SIZE);
134 int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
135 if(ret<0)
136 {
137 //对于非阻塞IO,下面的条件成立表示数据已经全部读取完毕,此后,epoll就再次触发sockfd上的EPOLLIN事件,以驱动下一次读操作
138 if((errno==EAGAIN)||(errno==EWOULDBLOCK))
139 {
140 printf("read later\n");
141 break;
142 }
143 close(sockfd);
144 break;
145 }else if(ret==0){
146 close(sockfd);
147 }else{
148 printf("get %d bytes of content: %s\n",ret,buf);
149 }
150 }
151 }else{
152 printf("something else happened\n");
153 }
154 }
155 }
EPOLLONESHOT事件:即使使用ET模式,一个socket上的某个事件还是可能被触发多次。这在并发程序中就会引起一个问题。比如一个线程(或进程,下同)在读取完某个socket上的数据后开始处理这些数据,而在数据的处理过程中该socket上又有新数据可读(EPOLLIN再次被触发),此时另外一个线程被唤醒来读取这些新的数据。于是就出现了两个线程同时操作一个socket的局面。而我们期望的是一个socket连接在任一时刻都只被一个线程处理。这点可以使用epoll的EPOLLONESHOT事件实现。
对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、可写或异常事件,且只触发一次,除非我们使用epoll_ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。这样,当一个线程在处理某个socket时,其他线程是不可能有机会操作该socket的。但反过来思考,注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置这个socket上的EPOLLONESHOT事件,以确保这个socket下一次可读时,其EPOLLIN事件能被触发,进而让其他工作线程有机会继续处理这个socket。
1 #include <sys/types.h>2 #include <sys/socket.h>3 #include <netinet/in.h>4 #include <arpa/inet.h>5 #include <assert.h>6 #include <stdio.h>7 #include <unistd.h>8 #include <errno.h>9 #include <string.h>10 #include <fcntl.h>11 #include <stdlib.h>12 #include <sys/epoll.h>13 #include <pthread.h>14 #define MAX_EVENT_NUMBER 102415 #define BUFFER_SIZE 102416 struct fds{17 int epollfd;18 int sockfd;19 };20 //将文件描述符设置成非阻塞的21 int setnonblocking(int fd);22 //将fd上的EPOLLIN和EPOLLET事件注册到epollfd指示的epoll内核事件表中23 //参数oneshot指定是否注册fd上的EPOLLONESHOT事件24 void addfd(int epollfd, int fd, bool oneshot);25 //重置fd上的事件,操作系统会触发fd上的EPOLLIN事件且只触发一次26 void reset_oneshot(int epollfd, int fd);27 //工作线程28 void* worker(void* arg);29 30 int main(int argc, char* argv[])31 {32 if( argc<=2 )33 {34 printf("usage: %s ip_address port_number\n", basename(argv[0]));35 return 1;36 }37 const char* ip = argv[1];38 int port = atoi(argv[2]);39 int ret = 0;40 struct sockaddr_in address;41 bzero(&address,sizeof(address));42 address.sin_family = AF_INET;43 inet_pton(AF_INET, ip, &address.sin_addr);44 address.sin_port = htons(port);45 46 int listenfd = socket(PF_INET, SOCK_STREAM, 0);47 assert(listenfd>=0);48 ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));49 assert(ret!=-1);50 ret = listen(listenfd,5);51 assert(ret!=-1);52 53 epoll_event events[MAX_EVENT_NUMBER];54 int epollfd = epoll_create(5);55 assert(ret!=-1);56 //监听socket listenfd是不能注册EPOLLONESHOT事件,否则应用程序只能处理一个客户连接57 //后续客户连接请求不再触发listenfd上的EPOLLIN事件58 addfd(epollfd, listenfd, false);59 60 while(1)61 {62 int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);63 if( ret<0 )64 {65 printf("epoll failure\n");66 break;67 }68 for(int i = 0; i < ret; i++)69 {70 int sockfd = events[i].data.fd;71 if(sockfd==listenfd)72 {73 struct sockaddr_in client_address;74 socklen_t client_addrlength = sizeof(client_address);75 int connfd = accept(listenfd, (struct sockaddr*)&client_addres s,&client_addrlength);76 //对每个非监听文件描述符都注册EPOLLONESHOT事件77 addfd(epollfd, connfd, true);78 }else if(events[i].events & EPOLLIN)79 {80 pthread_t thread;81 fds fds_for_new_worker;82 fds_for_new_worker.epollfd = epollfd;83 fds_for_new_worker.sockfd = sockfd;84 //启动一个工作线程为sockfd服务85 pthread_create(&thread, NULL, worker, (void*)&fds_for_new_work er);86 }else{87 printf("something else happened\n");88 }89 }90 }91 close(listenfd);92 return 0;93 }94 95 int setnonblocking(int fd)96 {97 int old_option = fcntl(fd,F_GETFL);98 int new_option = old_option | O_NONBLOCK;99 fcntl(fd,F_SETFL,new_option);
100 return old_option;
101 }
102 void addfd(int epollfd, int fd, bool oneshot)
103 {
104 epoll_event event;
105 event.data.fd = fd;
106 event.events = EPOLLIN|EPOLLET;
107 if(oneshot)
108 {
109 event.events |= EPOLLONESHOT;
110 }
111 epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
112 setnonblocking(fd);
113 }
114 void reset_oneshot(int epollfd, int fd)
115 {
116 epoll_event event;
117 event.data.fd = fd;
118 event.events = EPOLLIN|EPOLLET|EPOLLONESHOT;
119 epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&event);
120 }
121 void* worker(void* arg)
122 {
123 int sockfd = ((fds*)arg)->sockfd;
124 int epollfd = ((fds*)arg)->epollfd;
125 printf("start new thread to receive data on fd: %d\n", sockfd);
126 char buf[BUFFER_SIZE];
127 memset(buf, '\0', BUFFER_SIZE);
128 while(1)
129 {
130 int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
131 if(ret==0)
132 {
133 close(sockfd);
134 printf("foreiner closed the connection\n");
135 break;
136 }else if(ret<0){
137 if(errno==EAGAIN)
138 {
139 reset_oneshot(epollfd,sockfd);
140 printf("read later\n");
141 break;
142 }else{
143 printf("get content: %s\n",buf);
144 sleep(5);
145 }
146 }
147 }
148 printf("end thread receiving data on fd: %d\n",sockfd);
149 }
从工作线程函数worker来看,如果一个工作线程处理完某个socket上的一次请求(用休眠5s来模拟这个过程)之后,又接收到该socket上新的客户请求,则该线程将继续为这个socket服务。并且因为该socket上注册了EPOLLONESHOT事件,其他线程没有机会接触这个socket,如果工作线程处理5s后仍然没有收到该socket上的下一批客户数据,则它将放弃为该socket服务。同时,它调用reset_oneshot函数来重置该socket上的注册事件,这将使epoll有机会再次检测到该socket上的EPOLLIN事件,进而使得其他线程有机会为该socket服务。
Linux编程入门三网络编程三 epoll的LT和ET模式以及EPOLLONESHOT事件相关推荐
- GPU 编程入门到精通(三)之 第一个 GPU 程序
博主由于工作当中的需要,开始学习 GPU 上面的编程,主要涉及到的是基于 GPU 的深度学习方面的知识,鉴于之前没有接触过 GPU 编程,因此在这里特地学习一下 GPU 上面的编程.有志同道合的小伙伴 ...
- 【JavaSe】网络编程篇(一) 网络编程入门
JavaSe·网络编程篇(一) 网络编程入门 1. 软件结构 C/S结构:全称为Client/Server结构,是指客户端和服务器结构.常见程序有QQ.百度网盘等软件 B/S结构 :全称为Browse ...
- 网络编程笔记之网络编程入门
网络编程的概念 网络编程最主要的工作就是在发送端把信息通过规定好的协议进行组装包,在接收端按照规定好的协议把包进行解析,从而提取出对应的信息,达到通信的目的.中间最主要的就是数据包的组装,数据包的过滤 ...
- 【Java 18】网络编程 - 概述、网络编程要素、IP和端口号、网络协议、TCP、UDP、URL
网络编程 - 概述.网络编程要素.IP和端口号.网络协议.TCP.UDP.URL 网络编程 1 网络编程概述 2 网络通信要素概述 3 通信要素1:IP和端口号 3.1 内容 3.2 InetAddr ...
- python游戏编程入门 免费-python游戏编程入门 python游戏编程入门课
python游戏编程入门 python游戏编程入门课 什么是python游戏编程入门?首先我们需要认识什么是Python Python既是一个软件工具包,也是一种语言.Python软件包包含了一个名为 ...
- java 网络编程 聊天_Java——网络编程(实现基于命令行的多人聊天室)
目录: 1.ISO和TCP/IP分层模型 2.IP协议 3.TCP/UDP协议 4.基于TCP的网络编程 5.基于UDP的网络编程 6.基于TCP的多线程的聊天室的实现 1.ISO和TCP/IP分层模 ...
- python游戏编程入门免费_python游戏编程入门 python游戏编程入门课
python游戏编程入门 python游戏编程入门课 什么是python游戏编程入门?首先我们需要认识什么是Python Python既是一个软件工具包,也是一种语言.Python软件包包含了一个名为 ...
- linux服务器开发三(网络编程)
转载自:http://www.cnblogs.com/zfc2201/archive/2017/05/04/6804990.html 作者:水之原 网络基础 协议的概念 什么是协议 从应用的角度出发, ...
- 【Linux】Linux系统编程(入门与系统编程)(三)(深入理解操作系统、进程、环境变量、内存分布)
本博客操作系统最多涉及30%的理论,重点在于部分进程的内容,部分文件系统的内容,部分文件管理的内容不是主讲操作系统,我们的最终目的是理解系统中最高频的知识点,然后被完全利用指导我们编程. 下面是这三篇 ...
最新文章
- 【Hadoop Summit Tokyo 2016】Rakuten是如何解决由于大规模多租户Hadoop集群造成的迷之问题的...
- 数据仓库与数据集市建模
- bwapp之xss(blog)
- C++数据类型和变量类型。
- SpringBoot实战 之 异常处理篇
- IHostingEnvironment VS IHostEnvironment - .NET Core 3.0中的废弃类型
- unity3d 预制体
- 11.docker tag
- 【愚公系列】2022年10月 微信小程序-电商项目-收货地址功能实现
- STM32WB55开发板(一)单板设计-硬件介绍
- Java2022面试题集锦
- TM1650芯片使用经验
- yum -y --downloadonly --downloaddir=/root/ruiy update
- 平头哥CH2601开发环境(CDK)搭建
- 一次系统宕机认识系统日志
- mysql查询排名名次
- Android UI 绘制流程及原理
- 一款非常方便简单的Mac个人理财软件:Moneydance
- 用Python制作动态饼图
- 用大数据思考用户体验 纪学锋谈《江湖》特色
热门文章
- CAD图纸转Word文件,看看这个方法你会吗?
- 微生物实验之革兰氏染色
- 计算机毕业设计PHP+安卓英语答题APP(源码+程序+lw+远程调试)
- 调用云服务实现语音识别合成以及感情分析
- Excel表格公式如何快速向右填充
- 一些IT段子,娱乐一下
- Jenkins的流水线(Pipeline)
- Java基础——2、基本语法(下)—程序流程控制
- pta上怎么搜题目_完成pta(函数题)习题6-3、6-5、6-6,代码复制在下方答案中,并在pta平台中完成。_学小易找答案...
- webmin安装php,CentOS 下搭建 LAMP 运行环境 Webmin