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 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  7 #include 
  8 #include 
  9 #include 
 10 #include 
 11 #include 
 12 #include 
 13 #include 
 14 #define MAX_EVENT_NUMBER 1024
 15 #define BUFFER_SIZE 10
 16 
 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 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  7 #include 
  8 #include 
  9 #include 
 10 #include 
 11 #include 
 12 #include 
 13 #include 
 14 #define MAX_EVENT_NUMBER 1024
 15 #define BUFFER_SIZE 1024
 16 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事件_第1张图片
在这里插入图片描述

你可能感兴趣的:(linux编程)