【服务器编程】EPOLL的LT和ET模式的区别和理解

来源:互联网 发布:sqlserver分页存储过程 编辑:程序博客网 时间:2024/05/17 19:56


本文转载自博客:http://blog.csdn.net/jammg/article/details/51854436


【前言】

epoll模型是服务器编程的高性能框架,比select 和 poll模型高效很多,当然还有其它的模型,如kqueue等,具体Linux发行版提供不同的模型,一般都支持epoll吧。


【理解】

epoll提供两种工作模式:LT 和 ET。

LT模式是epoll默认的工作方式,相当于一个效率很高的poll模型;而ET是高效的工作方式。


LT 和 ET本质的区别是:

LT模式状态时,主线程正在epoll_wait等待事件时,请求到了,epoll_wait返回后没有去处理请求(recv),那么下次epoll_wait时此请求还是会返回(立刻返回了);而ET模式状态下,这次没处理,下次epoll_wait时将不返回(所以我们应该每次一定要处理),可见很大程度降低了epoll的触发次数(记住这句话先)

(所以,针对上面我对这个的高效的理解是:要看编程人员的实现方式,不是epoll一定高效,毕竟LT模式也可以在一次处理多次请求,或许是我没理解LT和ET底层还有什么数据结构、算法的差别吗,希望懂的同志指教一下!!


LT模式

其实,LT好理解,LT 模式下无论是否设置了EPOLLONESHOT,都是epoll_wait检测缓冲区有没有数据,有就返回,否则等待;

EPOLLONESHOT是在多线程环境应用的,试想如果主线程在epoll_wait返回了套接字conn,之后子线程1在处理conn,主线程回到epoll_wait,但还没等到子线程1返回conn又可读了,此时主线程epoll_wait返回,又分配给另一个线程,此时两个线程同时使用一个套接字,这当然是不行的,所以epoll模型定义了EPOLLONESHOT,意思就是设置了EPOLLONESHOT套接字在epoll_wait返回后,使用该线程的没重置此套接字前,即

[cpp] view plain copy
  1. void gResetOneshot(int epollfd, int conn)  
  2. {  
  3.     epoll_event event;  
  4.     event.data.fd = conn;  
  5.     event.events = EPOLLIN |  EPOLLONESHOT;  
  6.     epoll_ctl(epollfd, EPOLL_CTL_MOD, conn, &event);  
  7. }  

主线程不允许返回任何关于此套接字的事件,这样就做到同一时刻只可能有一个线程处理该套接字。

下面程序看看(全文基于这服务端/客户端程序测试):

服务端:

建立epoll,然后将 监听套接字 和  连接套接字 均是LT模式(默认),代码稍微有点长,无所谓,其实很易懂

[cpp] view plain copy
  1. #include <stdio.h>  
  2. #include <arpa/inet.h>  
  3. #include <unistd.h>  
  4. #include <stdlib.h>  
  5. #include <sys/socket.h>  
  6. #include <string.h>  
  7. #include <errno.h>  
  8. #include <signal.h>  
  9. #include <sys/epoll.h>  
  10. #include <fcntl.h>  
  11.   
  12. int gSetNonblocking(int fd)  
  13. {  
  14.     int old_option = fcntl(fd, F_GETFL);  
  15.     int new_option = old_option | O_NONBLOCK;  
  16.     fcntl(fd, F_SETFL, new_option);  
  17.     return old_option;  
  18. }  
  19.   
  20. /* 往epoll描述符添加套接字 */  
  21. void gAddfd(int epollfd, int fd, bool oneshoot)  
  22. {  
  23.     epoll_event event;  
  24.     event.data.fd = fd;   
  25.     event.events = EPOLLIN ;  
  26.     /* 同一时刻只允许一个线程处理该描述符 */  
  27.     if (oneshoot)  
  28.     {  
  29.         event.events = event.events | EPOLLONESHOT;  
  30.     }  
  31.     epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);  
  32.     gSetNonblocking(fd);  
  33. }  
  34.   
  35. void gResetOneshot(int epollfd, int conn)  
  36. {  
  37.     epoll_event event;  
  38.     event.data.fd = conn;  
  39.     event.events = EPOLLIN |  EPOLLONESHOT;  
  40.     epoll_ctl(epollfd, EPOLL_CTL_MOD, conn, &event);  
  41. }  
  42.   
  43. int main(int argc, char *argv[])  
  44. {  
  45.     int sock = socket(AF_INET, SOCK_STREAM, 0);  
  46.     if (sock < 0)  
  47.         write(STDERR_FILENO, "socket error", 11);  
  48.   
  49.     struct sockaddr_in addr;  
  50.     memset(&addr, 0, sizeof(addr));  
  51.     addr.sin_family = AF_INET;  
  52.     addr.sin_port = htons(10002);  
  53.     addr.sin_addr.s_addr = INADDR_ANY;  
  54.   
  55.     bind(sock, (struct sockaddr *)&addr, sizeof(addr));  
  56.   
  57.     listen(sock, 32767);  
  58.   
  59.     signal(SIGPIPE, SIG_IGN);  
  60.   
  61.     int epollfd;  
  62.     epollfd = epoll_create(5);  
  63.     gAddfd(epollfd, sock, false);  
  64.   
  65.     int connfd;  
  66.     int number;  
  67.     epoll_event event[512];  
  68.     while (1)  
  69.     {  
  70.   
  71.         number = epoll_wait(epollfd, event, 512, -1);  
  72.         if (number < 0 && errno != EINTR)  
  73.         {  
  74.             printf("epoll failure\n");  
  75.             break;  
  76.         }  
  77.         for (int i = 0; i < number; ++i)  
  78.         {  
  79.             int sockfd = event[i].data.fd;  
  80.             if (sockfd == sock && (event[i].events & EPOLLIN))  
  81.             {  
  82.                   
  83.                 struct sockaddr_in cliaddr;  
  84.                 socklen_t clilen = sizeof(sockaddr_in);  
  85.                 connfd = accept(sock, (struct sockaddr *)&cliaddr, &clilen);  
  86.   
  87.                 if (connfd < 0)  
  88.                 {  
  89.                     printf("errno is -> %d:%s\n", errno, strerror(errno));  
  90.                     continue;  
  91.                 }  
  92.                 /* 设置连接套接字EPOLLONESHOT */  
  93.                 gAddfd(epollfd, connfd, false);  
  94.                 //gResetOneshot(epollfd, sock);  
  95.                 printf("Client connect\n");  
  96.             } /* 来子外界的信号,如在终端输入kill -signal PID给此进程时 */  
  97.             else if (sockfd == connfd && (event[i].events & EPOLLIN))  
  98.             {  
  99.                 // printf("Don't process\n");  
  100.                 // gResetOneshot(epollfd, connfd);  
  101.                 // continue;  
  102.                 printf("Start sleep(10) ...\n");  
  103.                 sleep(10);  
  104.                 char text[512];  
  105.                   
  106.                 int ret = recv(connfd, text, 512, 0);  
  107.                 while (recv > 0)  
  108.                 {  
  109.                     if (ret > 0)  
  110.                     {  
  111.                         text[ret] = '\0';  
  112.                         printf("Recv(%d):%s\n", ret, text);  
  113.                     }  
  114.                     else if (ret == 0)  
  115.                     {  
  116.                         printf("Client close socket\n");  
  117.                         close(connfd);  
  118.                         break;  
  119.                     }  
  120.                     else if (errno == EWOULDBLOCK)  
  121.                     {  
  122.                         printf("Wouldblock\n");  
  123.                         break;  
  124.                     }  
  125.                     else if (errno == EPIPE)  
  126.                     {  
  127.                         printf("Broken pipe\n");  
  128.                         break;  
  129.                     }  
  130.                     ret = recv(connfd, text, 512, 0);  
  131.                 }  
  132.                 //gResetOneshot(epollfd, connfd);  
  133.             }  
  134.         }  
  135.     }  
  136.   
  137.     return 0;  
  138. }  

客户端:

客户端没什么需要注意,测试用而已,也是使用epoll模型,也不关事,能连服务器测试就好,贴出来好对比一下而已

[cpp] view plain copy
  1. #include <unistd.h>  
  2. #include <sys/socket.h>  
  3. #include <sys/types.h>  
  4. #include <stdio.h>  
  5. #include <sys/un.h>  
  6. #include <string.h>  
  7. #include <arpa/inet.h>  
  8. #include <errno.h>  
  9. #include <stdlib.h>  
  10. #include <signal.h>  
  11. #include <sys/epoll.h>  
  12. #include <fcntl.h>  
  13.   
  14. #define path "tempfile.socket"  
  15.   
  16. void sig(int sig)  
  17. {  
  18.     char buf[512];  
  19.     memset(buf, 0, sizeof(buf));  
  20.     sprintf(buf, "write error, ertrno(%d) -> %s\n", errno, strerror(errno));  
  21.     write(STDERR_FILENO, buf, strlen(buf));  
  22.     sleep(3);  
  23.     exit(-1);  
  24.           
  25. }  
  26.   
  27. int main(int argc, char *argv[])  
  28. {  
  29.     if (argc != 2)  
  30.     {  
  31.         printf("Usage:./exec [port]\n");  
  32.         exit(-1);  
  33.     }  
  34.     signal(SIGPIPE, sig);  
  35.     int sock = socket(AF_INET, SOCK_STREAM, 0);  
  36.   
  37.     struct sockaddr_in addr;  
  38.     memset(&addr, 0, sizeof(addr));  
  39.     addr.sin_family = AF_INET;  
  40.     addr.sin_port = htons(atoi(argv[1]));  
  41.     addr.sin_addr.s_addr = inet_addr("192.168.1.115");  
  42.   
  43.     connect(sock, (struct sockaddr *)&addr, sizeof(addr));  
  44.   
  45.     int old = fcntl(sock, F_GETFL);  
  46.     int newoption = old | O_NONBLOCK;  
  47.     fcntl(sock, F_SETFL, newoption);  
  48.   
  49.     char buf[512];  
  50.     memset(buf, 0, sizeof(buf));  
  51.     int epollfd = epoll_create(5);  
  52.   
  53.     epoll_event event[128];  
  54.   
  55.     epoll_event e1,e2;  
  56.     e1.data.fd = sock;  
  57.     e1.events = EPOLLIN | EPOLLET;  
  58.     epoll_ctl(epollfd, EPOLL_CTL_ADD, sock, &e1);   
  59.     e2.data.fd = STDIN_FILENO;  
  60.     e2.events = EPOLLIN | EPOLLET;  
  61.     epoll_ctl(epollfd, EPOLL_CTL_ADD, STDIN_FILENO, &e2);   
  62.   
  63.     int nR;  
  64.     int ret;  
  65.     while (1)  
  66.     {  
  67.         int number = epoll_wait(epollfd, event, 128, -1);  
  68.         if (number <= 0)  
  69.         {  
  70.             printf("epoll_wait error\n");  
  71.             break;  
  72.         }  
  73.         for (int i = 0; i < number; ++i)  
  74.         {  
  75.   
  76.             if (event[i].data.fd == sock && (event[i].events & EPOLLIN))  
  77.             {  
  78.                 memset(buf, 0, sizeof(buf));  
  79.                 nR = read(sock, buf, 512);  
  80.                 if (nR == 0)  
  81.                 {  
  82.                     close(sock);  
  83.                     break;  
  84.                 }  
  85.                 write(STDOUT_FILENO, buf, nR);  
  86.             }  
  87.             else if (event[i].data.fd == STDIN_FILENO && (event[i].events & EPOLLIN))  
  88.             {  
  89.                 printf("please input string:");  
  90.                 fflush(stdout);  
  91.                 memset(buf, 0, sizeof(buf));  
  92.                 nR = read(STDIN_FILENO, buf, 512);  
  93.                 if (nR <= 0)  
  94.                 {  
  95.                     printf("errno[%d]:%s\n", errno, strerror(errno));  
  96.                 }  
  97.                 ret = write(sock, buf, nR);  
  98.                 if (ret == 0 && errno == EINTR)  
  99.                 {  
  100.                     printf("write sock error\n");  
  101.                     exit(-1);  
  102.                 }  
  103.                 else if (ret < 0)  
  104.                 {  
  105.                     printf("write sock ret < 0\n");  
  106.                     exit(-1);  
  107.                 }  
  108.                 printf("Send [%d]byte\n", ret);  
  109.             }  
  110.         }  
  111.     }  
  112.   
  113.     close(sock);  
  114.   
  115.     return 0;  
  116. }  

服务器代码:

服务器在监听到连接套接字connfd有信息来时,epoll_wait返回,我设置了睡眠10秒,因为我想在recv之前在发送数据给服务端,测试结果如下:

因为server接受缓冲区(text)为512字节,可以容纳所有字符,所以一次输出了所有数据。

我再改一改程序,将recv那里的512改成5,然后我客户端每次发5个字节过去,结果如下:

先发送第一个信息(sadf),等到epoll_wait返回后,在发送两个,LT模式也能像ET模型那样处理数据,一样是减少了触发次数,我想问,ET高效的原因是???

ET模式

ET模式要求使用非阻塞套接字,然后在处理请求时也是用一个while循环,直到没数据读就返回,就像是LT用的那个循环。

再回到上面那句话-> ET很大程度降低了触发次数(难道所谓的降低触发次数就是‘强逼’了程序员必须一次处理完所有请求?)! ,LT和ET的本质区别,ET到底比LT高效在哪?高效是模式高效,还是说 程序员 编程利用ET模式更有可能写出高效的服务器??我认为是后者,因为我找不到任何理由支持前者,了解的同志,再次提醒下知道的麻烦跟我说说,谢谢谢!!!


下面再来了解一下ET的其它特性 --->>> 什么是 这次不处理下次epoll_wait不再返回

我们修改一下服务器程序,只修改一个if条件如下(注意,没有使用while):

[cpp] view plain copy
  1. else if (sockfd == connfd && (event[i].events & EPOLLIN))  
  2. {  
  3.     printf("Start sleep(10) ...\n");  
  4.     sleep(10);  
  5.     char text[512];  
  6.     /* 最多返回5个字符 */  
  7.     int ret = recv(connfd, text, 5, 0);  
  8.     text[ret] = '\0';  
  9.     printf("Recv(%d):%s\n", ret, text);  
  10.       
  11. }  

其它代码一致,测试步骤:

①发送1条信息

②等epoll返回再发送2条信息

测试结果:

在epoll返回之后 和 recv之前,又发送了2条信息,然我第一次时我只处理一条数据,第二次epoll返回时也是处理一条数据,然后第三条数据并没有被处理。

所以,第三条信息属于没被立即处理的请求,由此,我们知道【调用epoll_wait后】 到【  epoll_waitf返回】 之间是一个信息集,一个信息集只通知返回一次;另外,每次【epoll_wait返回后】到【调用epoll_wait后】就来到的信息,又是一个信息集,必须一次全部处理,不然下次不返回

再来验证,上面的客户端程序又发1条信息,结果如下:

信息PTYU被处理了,因为一个新的信息集来了,每来一个信息集就返回一次,当然,在处理过程中如果有新的请求(它的请求将会复制到套接字缓冲区),可以用while进行此次新请求处理也行的,下次就不返回了。

信息集是我自己起的称呼哈,其实我也觉得合场景,这个不管了!


最重要的是,ET到底比LT高效的原因?有见解的同志麻烦解释一下,在此谢过!


===========================================================================================================================

查阅发现LT和ET是否高效主要还是要看程序员程序设计的逻辑。

以下推荐两个博文连接:

http://www.cppblog.com/peakflys/archive/2012/08/26/188344.html

http://www.ccvita.com/515.html

http://blog.chinaunix.NET/uid-20775448-id-3603224.html