• socket -- epoll模型


    目录

    1. epoll和select的区别

    2. epool提供的函数

    2.1 epoll_create

    2.2 epoll_ctl

    2.3 epoll_wait

    3. LT模式和ET模式

     4. 程序代码

    总结


    1. epoll和select的区别

    I/O多路有select和epoll

            select当有事件发生时,会去轮训检查加入的1-max的全部监控集合的描述符,找到发生事件的fd。显而易见,当待监控连接是数以十万计的,返回的只是数百个活跃连接事件,这就是低效率的表现。可见,处理并发上万个连接时,select就完全力不从心了。

    epoll同样可以实现select做的事情,而且更高效。

    1. 返回 ==  epoll_fd描述符
    2. poll_create() epoll_ctrl(epoll_fd描述符,添加或者删除待监控的连接fd)
    3. 返回事件的活跃连接fd == epoll_wait( epoll描述符 )

    与select相比,epoll只会返回发生事件的events集合和发生事件的数量,效率高。

    2. epool提供的函数

    2.1 epoll_create

    1. #include
    2. int epoll_create(int size)
    3. 函数说明:
    4. 参数size:必须设置一个大于0的值。
    5. 返回值:调用成功返回一个非负值的文件描述符fd,调用失败返回-1

    2.2 epoll_ctl

    有了epoll_fd之后,我们需要将我们需要检测事件的其他fd绑定到这个epoll_fd上,或者修改一个已经绑定上去的fd的事件类型,或者在不需要时将fd从epollfd上解绑,需要使用epoll_ctl函数。

    1. #include
    2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

    函数说明:

    1. 参数epfd:调用epoll_create函数创建的epoll_fd。
    2. 参数op:操作类型,取值有EPOLL_CTL_ADD、EPOLL_CTL_MOD和EPOLL_CTL_DEL,分别表示向epoll_fd上添加、修改和移除一个其他fd,当取值是EPOLL_CTL_DEL,第四个参数event忽略不计,可以设置为NULL。
    3. 参数fd:即需要被操作的fd。
    4. 参数event:是一个epoll_event结构体的地址,epoll_event结构体定义下面会详细介绍。
    5. 返回值:调用成功返回0,调用失败返回-1,可以通过errno错误码获取具体的错误原因。

    epoll_event结构体定义如下:

    1. struct epoll_event {
    2.     uint32_t  events; // 需要检测的fd事件,取值与poll函数一样
    3.     epoll_data_t data; // 用户自定义数据
    4. };
    5. typedef union epoll_data {
    6.     void    *ptr;
    7.     int      fd;   //监控的fd
    8.     uint32_t u32;
    9.     uint64_t u64;
    10. } epoll_data_t;

    epoll_event支持的事件宏如下表:

    事件宏

    描述

    EPOLLIN

    数据可读(包括普通数据&优先数据)

    EPOLLOUT

    数据可写(包括普通数据&优先数据)

    EPOLLRDHUP

    TCP连接被对端关闭,或者关闭了写操作

    EPOLLPRI

    高优先级数据可读,例如TCP带外数据

    EPOLLERR

    错误

    EPOLLHUP

    挂起

    EPOLLET

    边缘触发模式

    EPOLLONESHOT

    最多触发其上注册的事件一次

    2.3 epoll_wait

            创建了epoll_fd,设置好某个fd上需要检测事件并将该fd绑定到epoll_fd上去后,就调用epoll_wait检测事件了。

    1. #include
    2. int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

    函数说明:

    1. 参数epfd:调用epoll_create函数创建的epoll_fd。
    2. 参数events:是一个epoll_event结构数组的首地址,这是一个输出参数,函数调用成功后,events中存放的是与就绪事件相关epoll_event结构体数组。
    3. 参数maxevents:数组元素的个数。
    4. 参数timeout:超时时间,单位是毫秒,如果设置为0,epoll_wait会立即返回。-1 阻塞等待,有事件循环。>0 定时,毫秒。
    5. 返回值:调用成功会返回有事件的fd数目;如果返回0表示超时;调用失败返回-1。

    3. LT模式和ET模式

    设置方式

    ev.events = EPOLLIN;//使用默认LT模式

    ev.events = EPOLLIN|EPOLLET;//监听读状态同时设置ET模式

    LT:(Level_triggered,水平触发):

            当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你,如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率。

            当接收buffer长度小于接收缓存区数据的长度时,会分包,会连续出发事件多次,循环recv,直到接收完。

    ET:(Edge_triggered,边缘触发):

            当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你,这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符。

     4. 程序代码

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #define BUFFER_LENGTH 8196
    11. #define EVENTS_LENGTH 128
    12. char rbuffuf[BUFFER_LENGTH] = {0};
    13. char wbuffuf[BUFFER_LENGTH] = {0};
    14. int main() {
    15. // block
    16. int listenfd = socket(AF_INET, SOCK_STREAM, 0); //
    17. if (listenfd == -1) return -1;
    18. // listenfd
    19. struct sockaddr_in servaddr;
    20. servaddr.sin_family = AF_INET;
    21. servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    22. servaddr.sin_port = htons(9999);
    23. if (-1 == bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr))) {
    24. return -2;
    25. }
    26. //fd --> epoll
    27. int epfd = epoll_create(1); //只需大于零
    28. struct epoll_event ev, events[EVENTS_LENGTH];
    29. ev.events = EPOLLIN;
    30. ev.data.fd = listenfd;
    31. epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); //epoll非阻塞
    32. printf("fd : %d , %d \n", epfd, listenfd);
    33. while(1){
    34. int nready = epoll_wait(epfd, events, EVENTS_LENGTH, 1000);
    35. printf("----- nready = %d\n", nready);
    36. int i = 0;
    37. for(i = 0; i
    38. int clientfd = events[i].data.fd; //发生事件的fd.
    39. if(listenfd == clientfd){ //sever
    40. struct sockaddr_in client;
    41. socklen_t len = sizeof(client);
    42. int counfd = accept(listenfd, (struct sockaddr*)&client, &len);
    43. if(counfd == -1)break;
    44. printf("accept = %d \n" , counfd);
    45. ev.events = EPOLLIN ;
    46. ev.data.fd = counfd;
    47. epoll_ctl(epfd, EPOLL_CTL_ADD, counfd, &ev); //epoll非阻塞
    48. }
    49. else if(events[i].events & EPOLLIN){ //client 读
    50. int n = recv(clientfd, rbuffuf, BUFFER_LENGTH, 0);
    51. if(n > 0){
    52. rbuffuf[n] = '\0';
    53. printf("recv = %s, n =%d\n", rbuffuf, n);
    54. memcpy(wbuffuf, rbuffuf , BUFFER_LENGTH);
    55. ev.events = EPOLLOUT ;
    56. ev.data.fd = clientfd;
    57. epoll_ctl(epfd, EPOLL_CTL_MOD, clientfd , &ev);
    58. }
    59. }
    60. else if(events[i].events & EPOLLOUT){ //写
    61. int sendlen = send(clientfd, wbuffuf, BUFFER_LENGTH, 0);
    62. printf("sendlen =%d\n", sendlen);
    63. ev.events = EPOLLIN ;
    64. ev.data.fd = clientfd;
    65. epoll_ctl(epfd, EPOLL_CTL_MOD, clientfd , &ev);
    66. }
    67. }
    68. }
    69. }

    总结

    1. epoll监视的描述符数量不受限制
    2. epoll不同于select和poll轮询的方式,而是通过每个fd定义的回调函数来实现的,只有就绪的fd才会执行回调函数。

    一般在fd数量比较多,但某段时间内,就绪事件fd数量较少的情况下,epoll才会体现出它的优势,也就是说socket连接数量较大时而活跃连接较少时epoll模型更高效。

  • 相关阅读:
    最新Next14 路由处理器 Route Handlers
    如何优雅编写测试用例
    资产管理与检测系统“H”介绍(使用和优化中)
    SL1588A 18V转3.3V、5V、12V/ 2A同步降压稳压器
    数据库的基本操作
    策略引擎Kyverno
    玩客云Armbian_23.08.0-trunk_Onecloud_bookworm_edge_6.4.14.burn配置
    八月最新阿里云服务器配置表汇总
    ffmpeg ts 关于av_seek_frame
    盘点54个Python实用工具源码Python爱好者不容错过
  • 原文地址:https://blog.csdn.net/kakaka666/article/details/126150827