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

输出:

  • 客户端

  • 服务端

参考资料

原文地址:https://www.cnblogs.com/sunbines/p/10320536.html