• epoll(二)


    epoll概念

    epoll对文件描述符的操作方式有两种工作模式:LT模式(Level Trigger,水平触发) 和ET模式(Edge Trigger,边缘触发)。

    • LT模式:当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件,这样,当应用程序下一次调用epoll_wait时,epoll_wait还会向应用程序通告此事件,直到该事件被处理。
    • ET模式:当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不在向应用程序通告此事件。

    1. 水平触发
     对于读操作

    • 只要缓冲内容不为空,LT模式返回读就绪。

     对于写操作

    • 只要缓冲区还不满,LT模式会返回写就绪。

    2. 边缘触发
     对于读操作

    • 当缓冲区由不可读变为可读的时候,即缓冲区由空变为不空的时候。
    • 当有新数据到达时,即缓冲区中的待读数据变多的时候。
    • 当缓冲区有数据可读,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLIN事件时。

    对于写操作

    • 当缓冲区由不可写变为可写时。
    • 当有旧数据被发送走,即缓冲区中的内容变少的时候。
    • 当缓冲区有空间可写,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLOUT事件时。

    实验一

    1. 测试代码:

     1 #include <unistd.h>
     2 #include <stdio.h>
     3 #include <sys/epoll.h>
     4 
     5 int main()
     6 {
     7     int epfd, nfds;
     8     struct epoll_event event, events[5];
     9     epfd = epoll_create(1);
    10     event.data.fd = STDIN_FILENO;
    11     event.events = EPOLLIN | EPOLLET;
    12     epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event);
    13     while (1) 
    14     {
    15         nfds = epoll_wait(epfd, events, 5, -1);
    16         int i;
    17         for (i = 0; i < nfds; ++i) 
    18         {
    19             if (events[i].data.fd == STDIN_FILENO)
    20                 printf("hello world
    ");
    21         }
    22     }
    23 }

    输出结果:

    分析:当用户输入一组字符,这组字符被送入缓冲区,因为缓冲区由空变成不空,所以ET返回读就绪,输出”hello world”。之后再次执行epoll_wait,但ET模式下只会通知应用进程一次,故导致epoll_wait阻塞。 如果用户再次输入一组字符,导致缓冲区内容增多,ET会再返回就绪,应用进程再次输出”hello world”。 如果将上面的代码中的event.events = EPOLLIN | EPOLLET;改成event.events = EPOLLIN;,即使用LT模式,则运行程序后,会一直输出hello world。

     2. 测试代码:

     1 #include <unistd.h>
     2 #include <stdio.h>
     3 #include <sys/epoll.h>
     4 
     5 int main()
     6 {
     7     int epfd, nfds;
     8     char buf[256];
     9     struct epoll_event event, events[5];
    10     epfd = epoll_create(1);
    11     event.data.fd = STDIN_FILENO;
    12     event.events = EPOLLIN;  // LT是默认模式
    13     epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event);
    14     while (1) {
    15         nfds = epoll_wait(epfd, events, 5, -1);
    16         int i;
    17         for (i = 0; i < nfds; ++i) 
    18         {
    19             if (events[i].data.fd == STDIN_FILENO) 
    20             {
    21                 read(STDIN_FILENO, buf, sizeof(buf));
    22                 printf("hello world
    ");
    23             }
    24         }
    25     }
    26 }

    输出:

    分析:

    实验2中使用的是LT模式,则每次epoll_wait返回时我们都将缓冲区的数据读完,下次再调用epoll_wait时就会阻塞,直到下次再输入字符。 
    如果将上面的程序改为每次只读一个字符,那么每次输入多少个字符,则会在屏幕中输出多少个“hello world”。

    3. 测试代码:

     1 #include <unistd.h>
     2 #include <stdio.h>
     3 #include <sys/epoll.h>
     4 
     5 int main()
     6 {
     7     int epfd, nfds;
     8     struct epoll_event event, events[5];
     9     epfd = epoll_create(1);
    10     event.data.fd = STDIN_FILENO;
    11     event.events = EPOLLIN | EPOLLET;
    12     epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event);
    13     while (1) 
    14     {
    15         nfds = epoll_wait(epfd, events, 5, -1);
    16         int i;
    17         for (i = 0; i < nfds; ++i) 
    18         {
    19             if (events[i].data.fd == STDIN_FILENO) 
    20             {
    21                 printf("hello world
    ");
    22                 event.data.fd = STDIN_FILENO;
    23                 event.events = EPOLLIN | EPOLLET;
    24                 epoll_ctl(epfd, EPOLL_CTL_MOD, STDIN_FILENO, &event);
    25             }
    26         }
    27     }
    28 }

    输出结果:

    对标准输入文件描述符使用ET模式进行监听。当我们输入任何输入并接下回车时,屏幕中会死循环输出”hello world”。

    分析:

    实验3使用ET模式,但是每次读就绪后都主动对描述符进行EPOLL_CTL_MOD 修改EPOLLIN事件,由上面的描述我们可以知道,会再次触发读就绪,这样就导致程序出现死循环,不断地在屏幕中输出”hello world”。但是,如果我们将EPOLL_CTL_MOD 改为EPOLL_CTL_ADD,则程序的运行将不会出现死循环的情况。

    实验二

    问题:

    1.阻塞读数据(不用epoll),你说读到一半有新消息又来了怎么办? 
    2.非阻塞读数据(不用epoll),你说读到一半有新消息又来了怎么办? 
    3.epoll的ET模式时,如果数据只读了一半,也就是缓冲区的数据只读了一点,然后又来新事件了怎么办?

    答案:

    1:来了就来了呗,读就是了啊。可能我们一次读到两次发过来的消息。 
    2:来了就来了呗,读就是了啊。可能我们一次读到两次发过来的消息。 
    3:单线程/进程不会有任何问题,多进程/多线程我们只需要设置EPOLLONESHOT这个参数就好了

    客户端代码:(下面四个示例都是同一个客户端)

     1 #include <stdio.h>
     2 #include <unistd.h>
     3 #include <sys/types.h>
     4 #include <sys/socket.h>
     5 #include <strings.h>
     6 #include <string.h>
     7 #include <ctype.h>
     8 #include <arpa/inet.h>
     9 
    10 int main()
    11 {
    12     int sock, n;
    13     sock= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    14     if(sock < 0)
    15         return 0;
    16 
    17     struct sockaddr_in servaddr;
    18     memset(&servaddr,0,sizeof(servaddr));
    19     servaddr.sin_family = AF_INET;
    20     servaddr.sin_port = htons(8888);
    21     servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    22 
    23     if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
    24         return 0;
    25     char *buf1 = "hello ";
    26     n = write(sock, buf1, strlen(buf1) + 1);
    27     printf("%d : buf = %s
    ", n, buf1);
    28 
    29     sleep(1);
    30 
    31     char *buf2 = "world ";
    32     n = write(sock,buf2,strlen(buf2) + 1);
    33     printf("%d : buf = %s
    ", n, buf2);
    34 
    35     sleep(2);
    36 
    37     char *buf3 = "陈明东";
    38     n = write(sock,buf3,strlen(buf3) + 1);
    39     printf("%d : buf = %s
    ", n, buf3);
    40 
    41     sleep(10);
    42     close(sock);
    43 }

    输出:

    1. 服务端阻塞读:

     1 nclude <stdio.h>
     2 #include <unistd.h>
     3 #include <sys/types.h>
     4 #include <sys/socket.h>
     5 #include <strings.h>
     6 #include <string.h>
     7 #include <ctype.h>
     8 #include <arpa/inet.h>
     9 
    10 #define SERV_PORT 8888
    11 
    12 int main(void)
    13 {
    14     int sfd, cfd;
    15     int len, i;
    16     char buf[BUFSIZ], clie_IP[BUFSIZ];
    17 
    18     struct sockaddr_in serv_addr, clie_addr;
    19     socklen_t clie_addr_len;
    20 
    21     sfd = socket(AF_INET, SOCK_STREAM, 0);
    22     bzero(&serv_addr, sizeof(serv_addr));
    23     serv_addr.sin_family = AF_INET;
    24     serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    25     serv_addr.sin_port = htons(SERV_PORT);
    26 
    27     bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    28 
    29     listen(sfd, 64);
    30     printf("wait for client connect ...
    ");
    31 
    32     clie_addr_len = sizeof(clie_addr_len);
    33     cfd = accept(sfd, (struct sockaddr *)&clie_addr, &clie_addr_len);
    34     printf("client IP:%s	port:%d
    ",
    35         inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)),
    36         ntohs(clie_addr.sin_port));
    37 
    38     while (1)
    39     {
    40         printf("sleep
    ");
    41         sleep(2);
    42         int len = read(cfd, buf, 1024);
    43         if (0 == len)
    44         {
    45             printf("客户端退出
    ");
    46             close(cfd);
    47             break;
    48         }
    49         /*把读到的数据打印出来*/
    50         printf("%d: ", len);
    51         for (int i = 0; i < len; ++i)
    52             printf("%c", buf[i]);
    53         printf("
    ");
    54     }
    55     close(sfd);
    56     return 0;
    57 }
    View Code

    输出:

    • 客户端

    • 服务端

    参考资料

  • 相关阅读:
    asp.net前台绑定数据和后台绑定数据什么区别
    一个页面多个input 按钮 如何回车控制
    (转)Asp.net中Application Session Cookie ViewState Cache Hidden 区别
    url传值IE6浏览器传值后台读取为乱码
    checkbox实现单选多选
    webconfig和appconfig中出现特殊字符如何处理
    WINCE上遇到空间不足问题
    MessageBox知多少

    for循环之删除注意细节
  • 原文地址:https://www.cnblogs.com/sunbines/p/10320536.html
Copyright © 2020-2023  润新知