select poll epoll

以下都是个人通过视频学习的理解,描述不够严谨但大体如此,参考链接:https://www.bilibili.com/video/BV1fg411376j?p=5
创建一个套接字的时候会创建一个用于监听的文件描述符(fd),每个套接字只有一个监听fd

fd又分为监听fd和通信fd
每个fd(不论是监听fd和通信fd结构都是一样的)都有一个读缓冲区(简称rb)和写缓冲区(简称wb)
accept 检测是否有客户端连接,本质上是检测用于监听的文件描述符的读缓冲区中是否有可读数据
当有客户端链接时,accept从监听fd的rb中获取链接信息,创建用于通信的fd(每链接一个客户端就会创建一个)
read 从用于通信的fd的读缓冲区读取数据
write 往通信fd缓冲区写数据, 这三个函数是阻塞的,阻塞就阻塞在用于监听或通信的文件描述符的读写缓冲区不满足操作的情况下,他会阻塞或空转

传统的socket模型,由用户来直接调用这三个阻塞函数,用户并不知道操作系统中到底发生了什么事情
所以一般都是 accept 阻塞检测用户链接 read阻塞检测对应的fd的读缓冲区中是否有可读数据
write阻塞检测写缓冲区中是否有空间可写并写,在单线程的情况下,这三个操作是互斥的


IO多路复用

在单线程情况下,由用户提供在未知情况下阻塞等待事件的发生,会造成不必要的阻塞
为了解决这种情况,操作系统提供了select,poll,epoll三个函数帮用户检测发生事件的fd,其中select是可以跨平台使用的,poll和epoll只能在Linux下使用

select函数要求用户传入需要检测的read_fd_set(标志位为1的都是用户关心读缓冲区的fd),write_fd_set(标志位为1的都是用户关心写缓冲
区的fd),大小是1024bit,通过将需要检测的fd对应的bit位置为1来告诉操作系统关心的fd,调用时需要将fd_set拷贝到内核空间由内核线程进行遍历线性表检测,同样将满足条件的fd拷贝回用户空间,同时返回总数,用户线程需要遍历fdset,通过fd_isset方法判断关心的fd是否有事件发生,用户就可以在知道有事件发生的情况下调用accept/read/wirte
方法,这样就避免了阻塞的情况。

poll函数在本质上和select没有区别,只是将select入参中的readset和writeset以及fd封装成 pollfd ,pollfd中存储fd和events对象events可以选择枚举值填入,表示用户关心的读/写/异常事件,同样存在拷贝过程,也同样使用线性表的结构和遍历方式,与select大同小异,效率上没有明显的区别

epoll在select和poll的基础上做了较大的修改,首先使用了共享内存shm,不用在内核和用户内存中互相拷贝,另外还使用rbtree结构存储event,epoll.wait函数中也有传出参数epoll_event对象,在初始化epfd的时候将epollevent对象中的data(此字段只供用户使用)信息设置为fd,则在传出时的event对象中的data就是用户设置的fd,
这样,传出参数epoll_event中都是发生的epfd,其中的用户字段包含fd,就不需要用户去遍历来寻找是哪个fd发生了事件

 

posted @ 2022-03-03 13:14  有一个小梦想  阅读(32)  评论(0编辑  收藏  举报