Linux socket通信–select

1 为什么要有select

将socket fd设置为nonblock(非阻塞)是在服务器编程中常见的做法,采用blocking IO并为每一个client创建一个线程的模式开销巨大且可扩展性不佳(带来大量的切换开销),更为通用的做法是采用线程池+Nonblock I/O+Multiplexing(select/poll,以及Linux上特有的epoll)。这也就是为什么存在io复用的原因

2 什么是select

系统提供select函数来实现多路复用输入/输出模型.select系统调用是用来让我们程序监视多个文件描述符的状态变化;程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变。

2.1 select 函数

  • 接口形式

    int select(int nfds, fd_set *readfds, fd_set *writefds,
    fd_set *exceptfds, struct timeval *timeout);
    

    头文件:#include <sys/select.h>

  • 参数说明

  1. 参数nfds是需要监视的最大的文件描述符值+1;
  2. fd_set:描述符集合 这个结构体中有一个数组,作用是用于向数组中添加描述符,将描述符添加到集合中,实际上是将描述符这个数字对应的比特位置1;而这个位图中能够添加多少描述符取决于一个宏:_FD_SETSIZE=1024,因此select模型所能够监控的描述符是有最大数量限制的;readfds,writefds,exceptfds分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合;为什么是1024,原因是fd_set 数组定义如下:
    typedef struct
    {
        long int __fds_bits[16]; //可以看作是128bit 的数组
    } fd_set;
    

    __fds_bits 是一个long int数组,使用bit位记录fd的事件,0:表示无事件,1:表示有事件,数组长度为16。因此一共可以表示 8 * 8 * 16 = 1024个fd的状态。这是为什么select函数支持的最大fd数量。

  3. timeout: 结构timeval,用来设置select()的等待时间
  • 返回值
  1. ( >0 ) 表示当前集合中多少描述符就绪了
  2. ( ==0 ) 表示等待超时了(在阻塞的时间段内,一直没有就绪的描述符)
  3. ( <0 ) 表示监控出错了(select本次监控出错)

2.2 fd_set类型变量相关宏定义

  • FD_ZERO(fd_set *fdset):清空fdset与所有文件描述符的联系。
  • FD_SET(int fd, fd_set *fdset):建立文件描述符fd与fdset的联系。
  • FD_CLR(int fd, fd_set *fdset):清除文件描述符fd与fdset的联系。
  • FD_ISSET(int fd, fd_set *fdset):检查fd_set联系的文件描述符fd是否可读写,>0表示可读写。

2.3 select 原理

select原理

  • 用户进程向内核发起select函数的调用,并携带socket描述符集合从用户空间复制到内核空间,由内核对socket集合进行可读状态的监控。
  • 其次当前内核没有数据可达的时候,将注册的socket集合分别以entry节点的方式添加到链表结构的等待队列中等待数据报可达。
  • 这个时候网卡设备接收到网络发起的数据请求数据,内核接收到数据报,就会通过轮询唤醒的方式(内核并不知道是哪个socket可读)逐个进行唤醒通知,直到当前socket描述符有可读状态的时候就退出轮询然后从等待队列移除对应的socket节点entry,并且这个时候内核将会更新fd集合中的描述符的状态,以便于用户进程知道是哪些socket是具备可读性从而方便后续进行数据读取操作。
  • 同时在轮询唤醒的过程中,如果有对应的socket描述符是可读的,那么此时会将read_process加入到cpu就绪队列中,让cpu能够调度执行read_process任务。
  • 最后是用户进程调用select函数返回成功,此时用户进程会在socket描述符结合中进行轮询遍历具备可读的socket,此时也就意味着数据此时在内核已经准备就绪,用户进程可以向内核发起数据读取操作,也就是执行上述的read_process任务操作。

2.4 select 事件

select函数用于检测在一组socket中是否有事件就绪,主要分为以下三类:

  1. 读事件就绪
    • 在socket 内核中,接收缓冲区中的字节数大于或者等于低水位标记 SO_RCVLOWAT,此时调用recv或者read函数可以无阻塞地读该文件描述符,并且返回值大于0。
    • TCP 连接的对端关闭连接,此时本端调用recv或read函数对socket进行读操作,recv或read函数会返回0值。
    • 在监听socket上有新的连接请求。
    • 在socket上有未处理的错误。
  2. 写事件就绪
    • 在socket内核中,发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小)大于或者等于低水位标记SO_SNDLOWAT时,可以无阻塞地写,并且返回值大于0。
    • socket 的写操作被关闭(调用了close或shutdown函数)时,对一个写操作被关闭的socket进行写操作,会触发SIGPIPE信号。
    • socket使用非阻塞connect连接成功或失败时。
  3. 异常事件就绪

原文链接: https://www.cnblogs.com/lihaihui1991/p/14599305.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    Linux socket通信--select

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/400329

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年4月14日 下午2:05
下一篇 2023年4月14日 下午2:05

相关推荐