返回
Featured image of post 操作系统 —— I/O 多路复用之 select

操作系统 —— I/O 多路复用之 select

select

select 函数允许进程指示内核等待多个事件,并在有一个或多个时间或经历指定时间后唤醒进程。

1. select

#include <sys/select.h>

 int select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds,
         fd_set *restrict errorfds, struct timeval *restrict timeout);
  • 作用:指示内核等待多个事件,并在有一个或多个时间或经历指定时间后唤醒进程。
  • 参数:
    • nfds: 待测试的最大描述符 +1;
    • readfds: 监听读的 fd 集合;
    • writefds: 监听写得 fd 集合;
    • errorfds: 监听异常的 fd 集合;
    • timeout: 等待超时时间。
      • NULL: 永远等下去;
      • 非 NULL 且不为值 0: 等待指定时间;
      • 非 NULL 且值为 0: 不等待,检查描述符后立即返回。(轮询)
  • 返回:
  • 返回:
    • 成功:就绪描述符数目
    • 超时:0
    • 出错:-1

2. 描述符就绪条件

  • 读就绪:
    • 该套接字接收缓冲区的数据字节数>=套接字缓冲区低水位标记的当前大小。
      • 高于水位,认为可读。
    • 该套接字的读半部关闭 (接收了 FIN 的 TCP 连接)。
      • 返回 EOF
    • 该套接字是一个监听套接字且已完成的连接数不为 0。
      • 此时 accept 通常不阻塞。
    • 其上有一个套接字错误待处理。
      • 读操作不阻塞、返回 -1,并设置 errno。
      • 待处理错误,可通过 getsockoptvidkSO_ERROR 套接字获取并清除。
  • 写就绪:
    • 发送缓冲区的数据字节数>=套接字缓冲区低水位标记的当前大小。
    • 该连接的写半部关闭。
      • 此时写,会产生SIGPIPE信号。
    • 使用非阻塞 connect 的套接字已建立连接,或者 connect 已经以失败告终。
    • 其上有一个套接字错误待处理。
  • 异常就绪:(?!)
    • 套接字存在带外数据或者仍处于带外标记。
      • 注意:当某个套接字上发生错误时,它将由 select 标记为可读又可写
  • 汇总:
    条件 可读吗? 可写吗? 异常吗?
    有数据可读 +
    关闭连接的读一半 +
    给监听套接字准备好新连接 +
    有可用于写得空间 +
    关闭连接的写一半 +
    待处理错误 + +
    TCP 带外数据 +

3. shutdown

终止网络连接的通常方法是 close 函数,但是 close 有两个限制:(可以用 shutdown 来避免)。

  • close 把描述符的引用计数 -1,仅在计数为 0 时才关闭套接字。
  • close 终止读和写两个方向的数据传送。
#include <sys/socket.h>

int shutdown(int sockfd, int howto);
  • 作用:关闭网络连接。

  • 参数:

    • howto:
      • SHUT_RD: 关闭连接的读一半。
      • SHUT_WR: 关闭连接的写一半。
      • SHUT_RDWR: 读写都关闭。(与调用两次 shutdown 等效。)
  • 返回:

    • 成功:0
    • 失败:Exxx 值

A. 注意、拓展

  • timeval 结构允许指定微秒级的分辨率,然而内核支持的真实分辨率往往粗糙得多。
  • timeval 结构能支持 select 不支持的值。
  • 如果 select 的三个 fds 都设置为 NULL,则可以得到一个比 sleep 函数更为精确的定时器。
  • 使用 select 的最常见错误:
    • 忘记对最大描述符 +1;
    • 忘记描述符集是值——结果参数。
  • 接收低水位标记和发送低水位标记的作用是什么?
    • 目的在于:允许应用进程控制在 select 返回可读/可写条件之前,有多少数据可读或有多大空间可用于写。
    • 例如:如果我们知道除非至少存在 64 字节数据,否则我们的应用程序无事可做,那么可以将接受低水位标记设置为 64。防止过早唤醒。
  • 任何 UDP 套接字只要其发送低水位标记``<=发送缓冲区大小,就总是可写的。(因为 UDP 不需要连接)
相信美好的事情即将发生。
Built with Hugo
Theme Stack designed by Jimmy