• I/O多路复用技术


    目录

    1.select/poll简介

    2.epoll(重点) 

    2.1epoll简介

    2.2函数原型

    epoll_create

    epoll_event

    epoll_ctl

    epoll_wait

    fcntl

    2.3epoll的两种工作模式以及对应的案例

    LT模式(水平触发模式)

    ET模式(边沿触发模式)


    1.select/poll简介

    poll是对select缺点进行一个改进(没有解决内核态和用户态转变的问题,只是解决了1024比特位的限制以及不能重用每次都需要重置的问题。

    2.epoll(重点) 

    2.1epoll简介

     

     rbr红黑树+rdlist双链表

    2.2函数原型

    epoll_create

    1. #include
    2. int epoll_create(int size);

    创建一个新的epoll实例。在内核中创建了一个数据,这个数据中有两个比较重要的数据,一个是需要检测的文件描述符的信息(红黑树),还有一个是就绪列表,存放检测到数据发送改变的文件描述符信息(双向链表)。

    - 参数:

            size : 目前没有意义了。随便写一个数,必须大于0

    - 返回值:

            -1 : 失败

            > 0 : 文件描述符,操作epoll实例的

    epoll_event

    1. struct epoll_event {
    2. uint32_t events; /* Epoll events */
    3. epoll_data_t data; /* User data variable */
    4. };

    常见的Epoll检测事件:

    - EPOLLIN

    - EPOLLOUT

    - EPOLLERR

    - EPOLLET

    epoll_ctl

    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

    - 参数:

            - epfd : epoll实例对应的文件描述符

            - op : 要进行什么操作 EPOLL_CTL_ADD: 添加 EPOLL_CTL_MOD: 修改 EPOLL_CTL_DEL: 删除

            - fd : 要检测的文件描述符

            - event : 检测文件描述符什么事情

    epoll_wait

    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    

    - 参数:

            - epfd : epoll实例对应的文件描述符

            - events : 传出参数,保存了发送了变化的文件描述符的信息

            - maxevents : 第二个参数结构体数组的大小

            - timeout : 阻塞时间 - 0 : 不阻塞 - -1 : 阻塞,直到检测到fd数据发生变化,解除阻塞 - > 0 : 阻塞的时长(毫秒)

            - 返回值: - 成功,返回发送变化的文件描述符的个数 > 0 - 失败 -1

    fcntl

    1. #include
    2. #include
    3. int fcntl(int fd, int cmd, ... );

    参数:
            fd:表示需要操作的文件描述符
            cmd:表示对文件描述符进行如何操作
                1.F_DUPFD:复制文件描述符,复制的是第一个参数fd,得到一个新的文件描述符
                    int ret = fcntl(fd,F_DUPFD);
                2.F_GETFL:获取指定的文件描述符文件状态flag
                    获取的flag和我们通过open函数传递的flag是一个东西
                3.F_SETFL:设置文件描述符文件状态flag
                    必选项:O_RDONLY,O_WRONDLY,O_RDWR 不可以被修改
                    可选项:O_APPEND,O_NONBLOCK
                        O_APPEND 表示追加数据
                        O_NONBLOCK 设置成非阻塞 

    2.3epoll的两种工作模式以及对应的案例

    LT模式(水平触发模式)

    假设委托内核检测读事件 -> 检测fd的读缓冲区 读缓冲区有数据 - > epoll检测到了会给用户通知

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

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

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

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

    服务器案例

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int main()
    8. {
    9. //1.创建socket
    10. int lfd = socket(AF_INET,SOCK_STREAM,0);
    11. if(lfd==-1){
    12. perror("socket");
    13. exit(-1);
    14. }
    15. printf("创建socket成功\n");
    16. //2.绑定
    17. struct sockaddr_in saddr;
    18. saddr.sin_family=AF_INET;
    19. saddr.sin_port=htons(9999);
    20. saddr.sin_addr.s_addr=INADDR_ANY;
    21. int ret=bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));
    22. if(ret==-1){
    23. perror("bind");
    24. exit(-1);
    25. }
    26. printf("绑定成功\n");
    27. //3.监听
    28. ret=listen(lfd,10);
    29. if(ret==-1){
    30. perror("listen");
    31. exit(-1);
    32. }
    33. printf("监听成功\n");
    34. //4.创建epoll实例
    35. int epfd=epoll_create(1);
    36. if(epfd==-1){
    37. perror("epoll_create");
    38. exit(-1);
    39. }
    40. printf("创建epoll实例成功\n");
    41. //5.加入监听的文件描述相关信息
    42. struct epoll_event epev;
    43. epev.events=EPOLLIN;
    44. epev.data.fd=lfd;
    45. epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);
    46. struct epoll_event epevs[1024];
    47. while(1){
    48. ret = epoll_wait(epfd,epevs,1024,-1);
    49. if(ret == -1){
    50. perror("epoll_wait");
    51. exit(-1);
    52. }
    53. printf("ret = %d\n",ret);
    54. for(int i=0;i
    55. int curfd=epevs[i].data.fd;
    56. if(curfd==lfd){
    57. struct sockaddr_in cliaddr;
    58. int len = sizeof(cliaddr);
    59. int cfd = accept(lfd,(struct sockaddr*)&cliaddr,&len);
    60. epev.events=EPOLLIN;
    61. epev.data.fd=cfd;
    62. epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev);
    63. }else {
    64. if(epevs[i].events&EPOLLOUT){
    65. continue;
    66. }
    67. //有数据到达需要通信
    68. char buf[1024]={0};
    69. int len = read(curfd,buf,sizeof(buf));
    70. if(len==-1){
    71. perror("read");
    72. exit(-1);
    73. }
    74. else if(len==0){
    75. printf("client closed...\n");
    76. epoll_ctl(epfd,EPOLL_CTL_DEL,curfd,NULL);
    77. close(curfd);
    78. }
    79. else if(len>0){
    80. printf("read buf = %s\n",buf);
    81. memset(buf,0,sizeof (buf));
    82. sprintf(buf,"recv");
    83. write(curfd,buf,strlen(buf)+1);
    84. }
    85. }
    86. }
    87. }
    88. close(lfd);
    89. close(epfd);
    90. return 0;
    91. }

    客户端案例

    1. #include
    2. #include
    3. #include
    4. #include // read write close
    5. #include
    6. int main()
    7. {
    8. //1.创建套接字
    9. int cfd=socket(AF_INET,SOCK_STREAM,0);
    10. if(cfd==-1){
    11. perror("socket");
    12. exit(0);
    13. }
    14. printf("1.客户端创建套接字成功\n");
    15. //2.连接服务器
    16. struct sockaddr_in saddr;
    17. saddr.sin_family=AF_INET;
    18. saddr.sin_port=htons(9999);
    19. inet_pton(AF_INET,"192.168.108.128",&saddr.sin_addr);//服务器的ip地址
    20. /*
    21. p:点分十进制的IP字符串,n:表示network,网络字节序的整数
    22. int inet_pton(int af, const char *src, void *dst);
    23. af:地址族: AF_INET AF_INET6
    24. src:需要转换的点分十进制的IP字符串
    25. dst:转换后的结果保存在这个里面
    26. */
    27. int ret=connect(cfd,(struct sockaddr*)&saddr,sizeof(saddr));
    28. if(ret==-1){
    29. perror("connect");
    30. exit(-1);
    31. }
    32. printf("2.客户端服务器连接成功\n");
    33. //3.通信
    34. char recvBuf[1024]={0};
    35. while(1){
    36. printf("请输入你想要发给服务端的内容\n");
    37. memset(recvBuf,0,sizeof(recvBuf));
    38. scanf("%s",recvBuf);
    39. write(cfd,recvBuf,strlen(recvBuf)+1);
    40. memset(recvBuf,0,sizeof(recvBuf));
    41. int data=read(cfd,recvBuf,sizeof(recvBuf));
    42. if(data>0)
    43. {
    44. printf("服务端返回数据:%s\n",recvBuf);
    45. }
    46. else if(data==0){
    47. printf("服务端断开连接\n");
    48. }else if(data<0){
    49. perror("read");
    50. exit(-1);
    51. }
    52. }
    53. close(cfd);
    54. return 0;
    55. }

    ET模式(边沿触发模式)

    假设委托内核检测读事件 -> 检测fd的读缓冲区 读缓冲区有数据 - > epoll检测到了会给用户通知

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

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

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

    ET(edge - triggered)是高速工作方式,只支持 no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。ET 模式在很大程度上减少了epoll 事件被重复触发的次数,因此效率要比 LT 模式高。epoll工作在 ET 模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

    服务器案例:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. int main(){
    10. int lfd = socket(AF_INET,SOCK_STREAM,0);
    11. if(lfd==-1){
    12. perror("socket");
    13. exit(-1);
    14. }
    15. printf("服务器创建socket成功\n");
    16. struct sockaddr_in saddr;
    17. saddr.sin_addr.s_addr=INADDR_ANY;
    18. saddr.sin_family=AF_INET;
    19. saddr.sin_port=htons(9999);
    20. int ret=bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));
    21. if(ret == -1){
    22. perror("bind");
    23. exit(-1);
    24. }
    25. printf("socket绑定成功\n");
    26. ret = listen(lfd,8);
    27. if(ret == -1){
    28. perror("listen");
    29. exit(-1);
    30. }
    31. printf("监听套接字成功\n");
    32. int epfd = epoll_create(1);
    33. if(epfd==-1){
    34. perror("epoll_create");
    35. exit(-1);
    36. }
    37. struct epoll_event epev;
    38. epev.events=EPOLLIN;
    39. epev.data.fd=lfd;
    40. ret = epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);
    41. struct epoll_event epevs[1024];
    42. while(1){
    43. ret = epoll_wait(epfd,epevs,1024,-1);
    44. if(ret == -1){
    45. perror("epoll_wait");
    46. exit(-1);
    47. }
    48. printf("ret = %d\n",ret);
    49. for(int i=0;i
    50. int curfd=epevs[i].data.fd;
    51. if(curfd==lfd){
    52. struct sockaddr_in cliaddr;
    53. int len = sizeof(cliaddr);
    54. int cfd = accept(lfd,(struct sockaddr*)&cliaddr,&len);
    55. //这里是设置cfd属性非阻塞 因为et模式只支持文件非阻塞
    56. int flag = fcntl(cfd,F_GETFL);
    57. flag |= O_NONBLOCK;
    58. epev.events=EPOLLIN|EPOLLET;//设置et模式
    59. epev.data.fd=cfd;
    60. epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev);
    61. }else{
    62. if(epevs[i].events&EPOLLOUT){
    63. continue;
    64. }
    65. //循环读取所有的数据
    66. char buf[5];
    67. int len=0;
    68. while((len=read(curfd,buf,sizeof(buf)))>0){
    69. //打印数据
    70. write(STDOUT_FILENO,buf,len);
    71. write(curfd,buf,len);
    72. }
    73. puts("");
    74. if(len==0){
    75. printf("连接断开\n");
    76. }else if(len==-1){
    77. if(errno==EAGAIN){
    78. printf("数据过多\n");
    79. }else{
    80. perror("read");
    81. exit(-1);
    82. }
    83. }
    84. }
    85. }
    86. }
    87. close(lfd);
    88. close(epfd);
    89. return 0;
    90. }

  • 相关阅读:
    apple pencil一定要买吗?Ipad电容笔推荐品牌
    数据分享|SAS数据挖掘EM贷款违约预测分析:逐步Logistic逻辑回归、决策树、随机森林...
    Unity DOTS中的baking(五)prefabs
    Python线程(thread)
    我的创作纪念日 - 一周年 【2022-09-17】
    Blob把html导出为excel文件
    [Python]Open CV 基础知识学习
    阿里云原生应用平台架构师田伟:应用架构的规划、治理与演进
    环境变量简单解释
    MATLAB主窗口与编辑器窗口分开为两个界面的解决办法
  • 原文地址:https://blog.csdn.net/l_ethan/article/details/125988385