linux 多路复用---epoll

rbr(红黑树):需要检测的文件描述符

rdlist:有数据传递的文件描述符

epoll API:

复制代码
/*
    #include <sys/epoll.h>
    //创建一个新的 epoll 实例,在内核中创建了一个数据,这个数据中有两个比较重要的数据,
    一个是需要检测的文件描述符的信息(红黑树),还有一个是就绪列表,存放检测到数据发生改变的文件描述符的信息(双向链表)
    int epoll_create(int size);
        - 参数:
            size: 目前没有意义了,随便写一个数, 必须要大于0
        - 返回值:
            -1: 调用失败
            >0: 文件描述符,操作epoll实例

    typedef union epoll_data
    {
        void *ptr;
        int fd;
        uint32_t u32;
        uint64_t u64;
    }epoll_data_t;

    struct epoll_event
    {
        uint32_t events;        //Epoll events
        epoll_data_t data;      //User data variable
    };
    常见的epoll检测事件:
        - EPOLLIN
        - EPOLLOUT
        - EPOLLERR

    //对 epoll 实例进行管理, 添加文件描述符信息,删除信息, 修改信息(读-读写)
    int epoll_ctl(int epfd, int op, struct eopll_event *event);
        - 参数:
            - epfd: epoll实例对应的文件描述符
            - op: 要进行什么操作
                EPOLL_CTL_ADD: 添加
                EPOLL_CTL_MOD: 修改
                EPOLL_CTL_DEL: 删除
            - fd: 要检测的文件描述符
            - event: 检测文件描述符什么事情(读取、写入)

    //检测函数
    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
        - 参数:
            - epfd: epol实例对应的文件描述符
            - events: 传出参数,保存了发生变化的文件描述符的信息
            - maxevents: 第二个参数结构体数组的大小
            - timeout: 阻塞事件
                - 0: 不阻塞
                - -1: 阻塞,直到检测到fd数据发生变化,解除阻塞
                - >0: 阻塞的时长(毫秒)
        - 返回值:
            - 成功: 返回发送变化的文件描述符个数 > 0
            - 失败: -1
*/
复制代码

epoll代码编写:

复制代码
 1 #include <stdio.h>
 2 #include <arpa/inet.h>
 3 #include <unistd.h>
 4 #include <stdlib.h>
 5 #include <string.h>
 6 #include <sys/epoll.h>
 7 
 8 int main()
 9 {
10     //创建 socket
11     int lfd = socket(PF_INET, SOCK_STREAM, 0);
12     struct  sockaddr_in saddr;
13     saddr.sin_port = htons(9999);
14     saddr.sin_family = AF_INET;
15     saddr.sin_addr.s_addr = INADDR_ANY;
16     //绑定
17     bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
18     //监听
19     listen(lfd, 8);
20     //调用epoll_create()创建一个epoll实例
21     int epfd = epoll_create(10);
22     //将监听的文件描述符相关的检测信息添加到 epoll实例中
23     struct epoll_event epev;
24     epev.events = EPOLLIN;
25     epev.data.fd = lfd;
26     epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);
27     struct epoll_event epevs[1024];
28     while(1)
29     {
30         int ret = epoll_wait(epfd, epevs, 1024, -1);//检测
31         if(ret == -1)
32         {
33             perror("epoll_wait");
34             exit(-1);
35         }
36         printf("ret = %d\n",ret);
37         //将客户端sleep改为小的睡眠值,否则返回为1 -> 同一时刻连接一个客户端
38         for(int i = 0; i < ret; i++)
39         {
40             int curfd = epevs[i].data.fd;
41             if(curfd == lfd)
42             {
43                 //监听的文件描述符有数据到达,有客户端连接
44                 struct sockaddr_in cliaddr;
45                 int len = sizeof(cliaddr);
46                 int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
47                 //epev.events = EPOLLIN;
48                 epev.events = EPOLLIN | EPOLLOUT;///
49                 epev.data.fd = cfd;
50                 epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);
51             }
52             else
53             {
54               if(epevs[i].events & EPOLLOUT)///不同情况 需要不同处理
55                 {
56                     continue;///
57                 }
58                 //有数据到达,需要通信
59                 char buf[1024] = {0};
60                 int len = read(curfd, buf, sizeof(buf));
61                 if(len == -1)
62                 {
63                     perror("read");
64                     exit(-1);
65                 }
66                 else if(len == 0)
67                 {
68                     printf("client closed...\n");
69                     epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
70                     close(curfd);
71                 }
72                 else if(len > 0)
73                 {
74                     printf("read buf = %s\n",buf);
75                     write(curfd, buf, strlen(buf) + 1);
76                 }
77             }
78         }
79     }
80     close(lfd);
81     close(epfd);
82     return 0;
83 }
复制代码

epoll的两种工作模式:

  LT模式(水平触发):

    假设委托内核检测读事件   ->  检测fd的读取缓冲区:

      读缓冲区有数据   ->  epoll检测到了会给用户通知

      a. 用户不读数据,数据一直在缓冲区,epoll会一直通知

      b. 用户只读了一部分数据,epoll会通知

      c. 缓冲区的数据读完了,不通知

  LT(Level - triggered)是缺省(默认)的工作方式,并且同时支持 block 和 no-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的 fd 进行 IO 操作。如果你不作任何操作,内核还是会继续通知你的。

  ET模式(边沿触发):

    假设委托内核检测读事件   ->  检测fd的读取缓冲区:

      读缓冲区有数据   ->  epoll检测到了会给用户通知

      a. 用户不读数据,数据一直在缓冲区,epoll下次检测的时候就不通知了

      b. 用户只读了一部分数据,epoll不通知

      c. 缓冲区的数据读完了,不通知

  ET(edge - triggered)是高速工作方式,只支持 no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过 epoll 告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是请注意,如果一直不对这个 fd 作 IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。

  ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比 LT 模式高。epoll 工作在 ET 模式的时候,必须使用非阻塞的套接口,以避免由于一个文件句柄(文件描述符)的 阻塞读/阻塞写 操作把处理多个文件描述符的任务饿死。

复制代码
    struct epoll_event
    {
        uint32_t events;    //Epoll events
        epoll_data_t dat;   //User data variable
    };
    常见的Epoll检测事件:
         - EPOLLIN
         - EPOLLOUT
         - EPOLLERR
         - EPOLLET
复制代码

Epoll_et:

复制代码
 1 #include <stdio.h>
 2 #include <arpa/inet.h>
 3 #include <unistd.h>
 4 #include <stdlib.h>
 5 #include <string.h>
 6 #include <fcntl.h>
 7 #include <sys/epoll.h>
 8 #include <errno.h>
 9 
10 int main()
11 {
12     //创建 socket
13     int lfd = socket(PF_INET, SOCK_STREAM, 0);
14     struct  sockaddr_in saddr;
15     saddr.sin_port = htons(9999);
16     saddr.sin_family = AF_INET;
17     saddr.sin_addr.s_addr = INADDR_ANY;
18     //绑定
19     bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
20     //监听
21     listen(lfd, 8);
22     //调用epoll_create()创建一个epoll实例
23     int epfd = epoll_create(100);
24     //将监听的文件描述符相关的检测信息添加到 epoll实例中
25     struct epoll_event epev;
26     epev.events = EPOLLIN;
27     epev.data.fd = lfd;
28     epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);
29     struct epoll_event epevs[1024];
30     while(1)
31     {
32         int ret = epoll_wait(epfd, epevs, 1024, -1);//检测
33         if(ret == -1)
34         {
35             perror("epoll_wait");
36             exit(-1);
37         }
38         printf("ret = %d\n", ret);
39         //将客户端sleep改为小的睡眠值,否则返回为1 -> 同一时刻连接一个客户端
40         for(int i = 0; i < ret; i++)
41         {
42             int curfd = epevs[i].data.fd;
43             if(curfd == lfd)
44             {
45                 //监听的文件描述符有数据到达,有客户端连接
46                 struct sockaddr_in cliaddr;
47                 int len = sizeof(cliaddr);
48                 int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
49                 
50                 //设置cfd属性非阻塞
51                 int flag = fcntl(cfd, F_GETFL);
52                 //flag = flag | O_NONBLOCK;
53                 flag | O_NONBLOCK;
54                 fcntl(cfd, F_SETFD, flag);
55 
56                 epev.events = EPOLLIN | EPOLLET;//ET模式 设置边沿触发
57                 epev.data.fd = cfd;
58                 epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);
59             }
60             else
61             {
62                 if(epevs[i].events & EPOLLOUT)///不同情况 需要不同处理
63                 {
64                     continue;///
65                 }
66                 //有数据到达,循环读取所有数据
67                 int len = 0;
68                 char buf[5];
69                 while((len = read(curfd, buf, sizeof(buf))) > 0)
70                 {
71                     //打印数据
72                     //printf("rev data = %s\n", buf);
73                     write(STDOUT_FILENO, buf, len);
74                     write(curfd, buf, len);
75                 }
76                 if(len == 0)
77                 {
78                     printf("client closed...\n");
79                 }
80                 else if(len == -1)
81                 {
82                     if(errno == EAGAIN)
83                     {
84                         printf("data over...");
85                     }
86                     else
87                     {
88                         perror("read");
89                         exit(-1);
90                     }
91                 }
92             }
93         }
94     }
95     close(lfd);
96     close(epfd);
97     return 0;
98 }
复制代码

 

posted on   廿陆  阅读(56)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示