• Linux网络编程(四)


    1.IO多路复用(IO多路转接)

    I/O多路复用使得程序能同时监听多个文件描述符,能够提高程序的性能,Linux 下实现 I/O 多路复用的系统调用主要有 selectpollepoll

    BIO(blocking IO)模型

    通过多线程或进程处理多个客户端,每个线程/进程对应一个客户端
    在这里插入图片描述

    NIO模型

    在这里插入图片描述

    IO多路转接技术

    select/poll:
    在这里插入图片描述
    epoll:
    在这里插入图片描述

    2.select

    select原理

    1. 首先要构造一个关于文件描述符的列表,将要监听的文件描述符添加到该列表中;
    2. 调用一个系统函数(函数是阻塞的),监听该列表中的文件描述符,直到这些描述符中的一个或者多个进行I/O操作时,该函数才返回(函数对文件描述符的检测的操作是由内核完成的)。
    3. 在返回时,函数会告诉进程有多少(哪些)描述符要进行I/O操作。

    select相关函数

    // sizeof(fd_set) = 128 1024 
    #include <sys/time.h> 
    #include <sys/types.h> 
    #include <unistd.h> 
    #include <sys/select.h> 
    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 
        参数: 
             - nfds : 委托内核检测的最大文件描述符的值 + 1 
             - readfds : 要检测的文件描述符的读的集合,委托内核检测哪些文件描述符的读的属性 
                         - 一般只检测读操作 
                         - 对应的是对方发送过来的数据,因为读是被动的接收数据,检测的就是读缓冲区 
                         - 是一个传入传出参数 
             - writefds : 要检测的文件描述符的写的集合,委托内核检测哪些文件描述符的写的属性 
                          - 委托内核检测写缓冲区是不是还可以写数据(不满的就可以写) 
             - exceptfds : 检测发生异常的文件描述符的集合
             - timeout : 设置的超时时间
                         struct timeval { 
                                long tv_sec; /* seconds */ 
                                long tv_usec; /* microseconds */ 
                         };
                         - NULL : 永久阻塞,直到检测到了文件描述符有变化 
                         - tv_sec = 0 tv_usec = 0, 不阻塞 
                         - tv_sec > 0 tv_usec > 0, 阻塞对应的时间 
             - 返回值 : - -1 : 失败 
                       - >0(n) : 检测的集合中有n个文件描述符发生了变化
    
    // 将参数文件描述符fd对应的标志位设置为0 
    void FD_CLR(int fd, fd_set *set); 
    // 判断fd对应的标志位是0还是1, 返回值 : fd对应的标志位的值,0,返回0, 1,返回1 
    int FD_ISSET(int fd, fd_set *set); 
    // 将参数文件描述符fd 对应的标志位,设置为1 
    void FD_SET(int fd, fd_set *set);
    // fd_set一共有1024 bit, 全部初始化为0 
    void FD_ZERO(fd_set *set);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    select()工作过程分析

    客户端A、B、C、D对应的描述符为3、4、100、101,并连接到服务器,
    假设要检测读时间,那么首先创建fd_set_reads集合,fd_set有1024个比特位,每一个比特位对应一个文件描述符;
    要检测这四个客户端是否有数据到达,就通过FD_SET()将客户端对应的描述符的标志位置为1,然后调用select(),将fd_set从用户态拷贝到内核态;
    假设A、B发送了数据,那么内核从0遍历到101,当遍历到3-4时发现有数据,就将3-4置为1,当遍历到100-101时发现没有数据,就将100-101置为0;
    内核检测完成后将fd_set从内核态拷贝到用户态,遍历fd_set发现3-4发生变化、接收到了数据。
    在这里插入图片描述

    在这里插入图片描述

    select()缺点

    在这里插入图片描述

    3.poll

    poll相关函数

    对select()的改进,实现原理类似

    #include <poll.h> 
    struct pollfd { 
        int fd;         //委托内核检测的文件描述符
        short events;   //委托内核检测文件描述符的什么事件
        short revents;  //文件描述符实际发生的事件
    };
    
    例如:
    struct pollfd myfd; 
    myfd.fd = 5; 
    myfd.events = POLLIN | POLLOUT; 
    
    int poll(struct pollfd *fds, nfds_t nfds, int timeout); 
    参数:
         - fds : 是一个struct pollfd 结构体数组,需要检测的文件描述符的集合 
         - nfds : 第一个参数数组中最后一个有效元素的下标 + 1 
         - timeout : 阻塞时长 
                     0 : 不阻塞 
                     -1 : 阻塞,当检测到需要检测的文件描述符有变化,解除阻塞 
                     >0 : 阻塞的时长 
    返回值: 
           -1 : 失败 
           >0(n) : 成功,n表示检测到集合中有n个文件描述符发生变化
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

    poll()缺点

    poll()改进了select()支持的文件描述符数量太小和fds集合不能重用的缺点,但依然每次调用poll()时需要把fd集合从用户态拷贝到内核态,并且在内核态遍历传递进来的所有fd.

    4.epoll()

    epoll原理

    通过epoll_create在内核创建一个epoll实例(eventpoll结构体类型),返回值是一个文件描述符,可以通过文件描述符使用epoll API操作内核中的epoll实例;
    通过epoll_ctl()委托内核向eventpoll数据结构中添加要检测数据发送改变的文件描述符的信息,在eventpoll数据结构中,有两个重要的成员:rbr是需要检测的文件描述符的信息(红黑树)rdlist是就绪列表,存放检测到数据发生改变的文件描述符信息(双向链表)
    通过epoll_wait()委托内核检测eventpoll数据结构中是否有数据发生改变,如果有就发生改变的文件描述符信息存储到rdlist中,并将改变了的数据返回;
    最后通过for循环遍历数据发生改变的数据进行读写操作。
    在这里插入图片描述

    epoll相关函数

    #include <sys/epoll.h> 
    int epoll_create(int size); 
    参数:
         size : 没有意义,任意一个大于0的数都可 
    返回值: -1 : 失败 
            > 0 : 文件描述符,操作epoll实例的
    
    // 对epoll实例进行管理:添加文件描述符信息,删除信息,修改信息 
    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 : 检测文件描述符什么事情,读还是写
    
    struct epoll_event { 
        uint32_t events;      //Epoll检测事件
        epoll_data_t data;    //用户数据信息
    };
    常见的Epoll检测事件: 
       - EPOLLIN
       - EPOLLOUT 
       - EPOLLERR 
    typedef union epoll_data { 
        void *ptr; 
        int fd; 
        uint32_t u32; 
        uint64_t u64; 
    } epoll_data_t; 
    
    // 检测函数 
    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 
        参数:
             - epfd : epoll实例对应的文件描述符 
             - events : 传出参数,保存了发送了变化的文件描述符的信息(与epoll_ctl不一样)
             - maxevents : 第二个参数结构体数组的大小 
             - timeout : 阻塞时间 
                         0 : 不阻塞 
                         -1 : 阻塞,直到检测到fd数据发生变化,解除阻塞 - 
                         > 0 : 阻塞的时长(毫秒) 
    返回值: - 成功,返回发送变化的文件描述符的个数 > 0 
            - 失败,返回-1
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    epoll的两种工作模式

    LT模式(水平触发)

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

    示例:
    假设委托内核检测读事件 -> 检测fd的读缓冲区
    读缓冲区有数据 - > epoll检测到了会给用户通知
    a.用户不读数据,数据一直在缓冲区,epoll 会一直通知
    b.用户只读了一部分数据,epoll会通知
    c.缓冲区的数据读完了,不通知

    ET模式(边沿触发)

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

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

    示例:
    假设委托内核检测读事件 -> 检测fd的读缓冲区
    读缓冲区有数据 - > epoll检测到了会给用户通知
    a.用户不读数据,数据一致在缓冲区中,epoll下次检测的时候就不通知了
    b.用户只读了一部分数据,epoll不通知
    c.缓冲区的数据读完了,不通知

    struct epoll_event { 
        uint32_t events;      //Epoll检测事件
        epoll_data_t data;    //用户数据信息
    };
    常见的Epoll检测事件: 
       - EPOLLIN
       - EPOLLOUT 
       - EPOLLERR
       - EPOLLET     //设置边沿触发
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • 相关阅读:
    最好用的BT下载工具Qbittorrent ~群晖DSM7.0 Docker如何安装QB
    vue3组件通信
    Linux网络栈学习合集(自用)
    Python OpenCV 单目相机标定、坐标转换相关代码
    亚马逊云科技帮助客户在云中构建具有高可靠性和韧性的应用程序
    神经网络算法的关键参数,神经网络的参数有哪些
    GBRank:一种基于回归的排序方法
    需要知道的字符串函数
    3.1、前端异步编程(超详细手写实现Promise;实现all、race、allSettled、any;async/await的使用)
    部门来了个测试开发,听说是00后,上来一顿操作给我看呆了...
  • 原文地址:https://blog.csdn.net/STATEABC/article/details/125440408