• epoll实现 IO复用


    1、epoll实现 IO复用

    epoll的提出--》它所支持的文件描述符上限是系统可以最大打开的文件的数目;eg:1GB机器上,这个上限10万个左右。 每个fd上面有callback(回调函数)函数,只有活跃的fd才有主动调用callback,不需要轮询。 注意: Epoll处理高并发,百万级,不关心底层怎样实现,只需要会调用就可以。

    函数接口

    1. #include <sys/epoll.h>
    2. int epoll_create(int size);
    3. 功能:创建红黑树根节点
    4.  参数:size:不作为实际意义值 >0 即可
    5. 返回值:成功时返回epoll文件描述符,失败时返回-1
    6. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    7. 功能:控制epoll属性
    8. epfd:epoll_create函数的返回句柄。7、】=
    9. op:表示动作类型。有三个宏 来表示:
    10. EPOLL_CTL_ADD:注册新的fd到epfd中
    11. EPOLL_CTL_MOD:修改已注册fd的监听事件
    12. EPOLL_CTL_DEL:从epfd中删除一个fd
    13. Fd:需要监听的fd
    14. event:告诉内核需要监听什么事件
    15. EPOLLIN:表示对应文件描述符可读
    16. EPOLLOUT:可写
    17. EPOLLPRI:有紧急数据可读;
    18. EPOLLERR:错误;
    19. EPOLLHUP:被挂断;
    20. EPOLLET:触发方式,边缘触发;(默认使用边缘触发)
    21. ET模式:表示状态的变化;
    22. 返回值:成功时返回0,失败时返回-1
    23. typedef union epoll_data
    24. {
    25. void* ptr;(无效)
    26. int fd;
    27. uint32_t u32;
    28. uint64_t u64;
    29. } epoll_data_t;
    30. struct epoll_event
    31. {
    32. uint32_t events; / * Epoll事件* /
    33. epoll_data_t data; / *用户数据变量* /
    34. };
    35. //等待事件到来
    36. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
    37. 功能:等待事件的产生,类似于select的用法
    38. epfd:句柄;
    39. events:用来保存从内核得到事件的集合;
    40. maxevents:表示每次能处理事件最大个数;
    41. timeout:超时时间,毫秒,0立即返回,-1阻塞
    42. 成功时返回发生事件的文件描述个数,失败时返回-1

    select,poll都属于 同步IO机制(轮询)

    epoll属于异步IO机制(不轮询): 

    epoll的提出--》它所支持的文件描述符上限是系统可以最大打开的文件的数目;

                           eg:1GB机器上,这个上限10万个左右。

    每个fd上面有callback(回调函数)函数,只有产生事件的fd才有主动调用callback,不需要轮询。

    注意:

      Epoll处理高并发,百万级

    1. 红黑树: 是特殊的二叉树(每个节点带有属性),Epoll怎样能监听很多个呢?首先创建树的根节点,每个节点都是一个fd以结构体的形式存储(节点里面包含了一些属性,callback函数)
    2. 就绪链表: 当某一个文件描述符产生事件后,会自动调用callback函数,通过回调callback函数来找到链表对应的事件(读时间还是写事件)。

    2、epoll特点

    1. 监听的最大的文件描述符没有个数限制(取决与你自己的系统 1GB - 10万个左右)
    2. 异步I/O,epoll当有事件产生被唤醒之后,文件描述符主动调用callback(回调函数)函数直接拿到唤醒的文件描述符,不需要轮询,效率高

    3.epoll不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可.

    3、epoll的流程: 

    Epoll的使用:

    1.创建红黑树 和 就绪链表         

    2.添加文件描述符和事件信息到树上

    3.阻塞等待事件的产生,一旦产生事件,则进行处理

    4.根据链中准备处理的文件描述符 进行处理

    epoll 要使用一组函数:  epoll_create 创建红黑树 和 就序列表

                                          epoll_ctl   添加文件描述符和事件到树上 / 从树上删除

                                         epoll_wait  等待事件产生

    1)epoll_create 创建红黑树以及链表
    1. 头文件:#include 
    2. 声明:int epoll_create(int size);
    3. 功能:创建红黑树根节点(创建epoll实例) , 同时也会创建就绪链表
    4. 返回值:成功时返回一个实例(二叉树句柄),失败时返回-1
    2)epoll_ctl 控制epoll属性

    1. 声明: int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    2. 功能:控制epoll属性,比如给红黑树添加节点
    3. 参数: 1. epfd:   epoll_create函数的返回句柄。//一个标识符
    4. 2. op:表示动作类型,有三个宏:         
    5.                 EPOLL_CTL_ADD:注册新的fd到epfd中
    6.       EPOLL_CTL_MOD:修改已注册fd的监听事件
    7.       EPOLL_CTL_DEL:从epfd中删除一个fd
    8. 3. 要操作的文件描述符
    9. 4. 结构体信息:
    10. typedef union epoll_data 
    11. {
    12. int fd; //要添加的文件描述符
    13. uint32_t u32; typedef unsigned int
    14. uint64_t u64; typedef unsigned long int
    15. } epoll_data_t;
    16. struct epoll_event
    17. {
    18. uint32_t events; 事件
    19.    epoll_data_t data; //共用体(看上面)
    20. };
    21.   关于events事件:
    22.  EPOLLIN:  表示对应文件描述符可读
    23.     EPOLLOUT: 可写
    24.  EPOLLPRI:有紧急数据可读;
    25.     EPOLLERR:错误;
    26.     EPOLLHUP:被挂断;
    27.  EPOLLET:触发方式,边缘触发;(默认使用边缘触发)
    28.  ET模式:表示状态的变化;
    29. NULL: 删除一个文件描述符使用,无事件
    30. 返回值:成功:0, 失败:-1
    3)epoll_wait等待事件产生
    1. 声明: int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
    2. 功能:等待事件产生
    3.    内核会查找红黑树中有事件响应的文件描述符, 并将结构体放入就绪链表
    4.     就绪链表中的内容, 执行epoll_wait会同时复制到第二个参数events
    5. 参数:  epfd:句柄;
    6. events:用来保存从就绪链表中响应事件的集合;
    7. maxevents:  表示每次在链表中拿取响应事件的个数;
    8. timeout:超时时间,毫秒,0立即返回  ,-1阻塞
    9. 返回值: 成功: 实际从链表中拿出的数目     失败时返回-1

    练习epoll实现服务器端

    1. #include <stdio.h>
    2. #include <sys/types.h> /* See NOTES */
    3. #include <sys/socket.h>
    4. #include <netinet/in.h>
    5. #include <netinet/ip.h> /* superset of previous */
    6. #include <netinet/in.h>
    7. #include <arpa/inet.h>
    8. #include <unistd.h>
    9. #include <stdlib.h>
    10. #include <stdio.h>
    11. #include <unistd.h>
    12. #include <string.h>
    13. #include <sys/types.h>
    14. #include <sys/stat.h>
    15. #include <fcntl.h>
    16. #include <sys/epoll.h>
    17. int main(int argc, char const *argv[])
    18. {
    19.     if (argc < 2)
    20.     {
    21.         printf("plase input \n");
    22.         return -1;
    23.     }
    24.     //1.创建套接字,用于链接
    25.     int sockfd;
    26.     int acceptfd;
    27.     sockfd = socket(AF_INET, SOCK_STREAM, 0);
    28.     if (sockfd < 0)
    29.     {
    30.         perror("socket err");
    31.         return -1;
    32.     }
    33.     printf("sockfd:%d\n", sockfd);
    34.     //2.绑定 ip+port 填充结构体
    35.     struct sockaddr_in saddr;
    36.     saddr.sin_family = AF_INET;                   //协议族ipv4
    37.     saddr.sin_port = htons(atoi(argv[1]));        //端口号,htons将无符号短整数hostshort从主机字节顺序到网络字节顺序。
    38.     saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); //ip地址,转化为16进制表示
    39.     socklen_t len = sizeof(saddr);                //结构体大小
    40.     //bind绑定ip和端口
    41.     if (bind(sockfd, (struct sockaddr *)&saddr, len) < 0)
    42.     {
    43.         perror("bind err");
    44.         return -1;
    45.     }
    46.     printf("bind success\n");
    47.     //3.启动监听,把主动套接子变为被动套接字
    48.     if (listen(sockfd, 1< 0)
    49.     {
    50.         perror("listen err");
    51.         return -1;
    52.     }
    53.     printf("listen success\n");
    54.     //引入epoll
    55.     //创建结构体变量
    56.     struct epoll_event event;
    57.     struct epoll_event revents[32];//存放epoll_wait拿取的内容
    58.     //1.创建树
    59.     int epfd=epoll_create(1);
    60.     // if (epfd = epoll_creat(1< 0)
    61.     // {
    62.     //     perror(" creat err");
    63.     //     return -1;
    64.     // }
    65.     //2.将关心的文件描述符添加到树上
    66.     //标准输入上树
    67.     event.events = EPOLLIN | EPOLLET;
    68.     event.data.fd = 0;
    69.     epoll_ctl(epfd, EPOLL_CTL_ADD0&event);
    70.     //套接字上树
    71.     event.data.fd = sockfd;
    72.     epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);
    73.     char buf[64= {0};//字符数组
    74.     //3.在链表中取事件
    75.     while (1)
    76.     {
    77.         //3.阻塞 等待文件描述符产生事件
    78.         int ret = epoll_wait(epfd, revents, 10, -1);
    79.         if (ret < 0)
    80.         {
    81.             perror(" epoll_wait err");
    82.             return -1;
    83.         }
    84.         //4.根据文件描述符 进行处理
    85.         for (int i = 0; i < ret; i++)
    86.         {
    87.             if (revents[i].data.fd == 0)
    88.             {
    89.                 //5.执行操作
    90.                 fgets(buf, sizeof(buf), stdin);
    91.                 if (buf[strlen(buf)] == '\0'//去掉fgets补的'\n'
    92.                 {
    93.                     buf[strlen(buf) - 1= '\0';
    94.                 }
    95.                     send(event.data.fd, buf, sizeof(buf), 0); //发送
    96.             }
    97.             else if (revents[i].data.fd == sockfd)
    98.             {
    99.                 int acceptfd = accept(sockfd, (struct sockaddr *)&saddr, &len);
    100.                 if (acceptfd < 0)
    101.                 {
    102.                     perror("accept is err:");
    103.                     return -1;
    104.                 }
    105.                 printf("fd: %d ip: %s   port: %d is connect\n",acceptfd, inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));
    106.                 event.data.fd = acceptfd;//新的acceptfd上树
    107.                 event.events = EPOLLIN | EPOLLET;
    108.                 epoll_ctl(epfd, EPOLL_CTL_ADD, acceptfd, &event);
    109.             }
    110.             else
    111.             {
    112.                 int recvbyte = recv(revents[i].data.fd, buf, sizeof(buf), 0);
    113.                 if (recvbyte < 0)
    114.                 {
    115.                     perror("recv is err:");
    116.                     return -1;
    117.                 }
    118.                 else if (recvbyte == 0)
    119.                 {
    120.                     printf("client is exit\n");
    121.                     close(revents[i].data.fd);
    122.                     epoll_ctl(epfd, EPOLL_CTL_DEL, revents[i].data.fdNULL);
    123.                 }
    124.                 else
    125.                 {
    126.                     printf("%d : %s\n", revents[i].data.fd, buf);
    127.                 }
    128.             }
    129.         }
    130.     }
    131.     close(sockfd);
    132.     close(acceptfd);
    133.     return 0;
    134. }

  • 相关阅读:
    【图像分割】基于元胞自动机实现图像分割附matlab代码
    如何拿取 macOS 系统中的图标文件
    批量虚化边框并一键褪色的简单教程
    golang结构与接口方法实现与交互使用示例
    面向mMTC的5G网络多随机接入机制性能优化策略
    【PTHREAD】线程互斥与同步之读写锁
    HNUCM 您好中国
    Docker 启动容器
    知识图谱-KGE-对抗模型-2018:KBGAN
    代码优雅之道——Springboot统一返回结果
  • 原文地址:https://blog.csdn.net/m0_74937538/article/details/134326345