选择模型

简概

选择 select 模型是Winsock中最常见的 I/O模型。 核心便是利用 select 函数,实现对 I/O的管理

  1. 利用 select 函数来判断某 Socket 上是否有数据可读
  2. 或者能否向一个套接字写入数据
  3. 防止程序在Socket处于阻塞模式中时,在一次 I/O 调用(如sendrecvaccept等)过程中,被迫进入“锁定”状态
  4. 同时防止在套接字处于非阻塞模式中时,产生WSAEWOULDBLOCK错误。

select 的函数原型如下:

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

  1. 其中,第一个参数nfds会被忽略。
  2. 之所以仍然要提供这个参数,只是为了保持与Berkeley套接字兼容。
  3. 后面大家看到有三个 fd_set类型的参数:
  4. 一个用于检查可读性(readfds),
  5. 一个用于检查可写性(writefds),
  6. 一个用于例外数据(exceptfds)。

fd_set 结构的定义如下:

c typedef struct fd_set { u_int fd_count; SOCKET fd_array[FD_SETSIZE]; } fd_set;

#define FD_SETSIZE 64

所以 fd_set 结构中最多只能监视64个套接字。

fdset解释

fdset 代表着一系列特定套接字的集合。

  • readfds 集合包括符合下述任何一个条件的套接字: ● 有数据可以读入。 ● 连接已经关闭、重设或中止。 ● 假如已调用了listen,而且一个连接正在建立,那么accept函数调用会成功。

  • writefds 集合包括符合下述任何一个条件的套接字: ● 有数据可以发出。 ● 如果已完成了对一个非锁定连接调用的处理,连接就会成功。

  • exceptfds 集合包括符合下述任何一个条件的套接字: ● 假如已完成了对一个非锁定连接调用的处理,连接尝试就会失败。 ● 有带外(Out-of-band,OOB)数据可供读取。

例子

  1. 假设准备测试一个套接字是否可读,必须将写好的套接字增添到readfds集合中
  2. 然后调用 select 函数并等待其完成
  3. select 完成之后,再次判断自己的套接字是否仍为 readfds 集合的一部分
  4. 若答案是肯定的,则表明该套接字“可读”,可立即从它上面读取数据。

注意:

  1. 在三个参数中 readfds、writefds 和 exceptfds,任何两个都可以是空值( NULL)
  2. 但是,至少有一个不能为空值,在任何不为空的集合中,必须包含至少一个套接字句柄
  3. 否则, select 函数便没有任何东西可以等待
  4. 最后一个参数 timeout 对应的是一个指针
  5. 它指向一个 timeval 结构,用于决定select 最多等待 I/O操作完成多久的时间
  6. timeout 是一个空指针,那么 select 调用会无限期地锁定停顿下去,直到至少有一个描述符符合指定的条件后结束。

  7. timeval 结构的定义如下:

    1. tv_sec 字段以秒为单位指定等待时间;
    2. tv_usec 字段则以毫秒为单位指定等待时间。

若将超时值设置为 0 , 0,表明 select 会立即返回,出于对性能方面的考虑,应避免这样的设置。


select 函数返回值:

  1. select 成功完成后,会在 fdset 结构中,返回刚好有未完成的 I/O 操作的所有套接字句柄的总量。
  2. 若超过 timeval 设定的时间,便会返回0
  3. select 调用失败,都会返回 SOCKET_ERROR
  4. 调用 WSAGetLastError 获取错误码

select 对套接字进行监视之前,必须将套接字句柄分配给一个fdset的结构集合, 之后再来调用 select,便可知道一个套接字上是否正在发生上述的 I/O 活动。

  • Winsock 提供了下列宏操作,可用来针对 I/O活动,对 fdset 进行处理与检查: ● FD_CLR(s, *set):从set中删除套接字s ● FD_ISSET(s, *set):检查s是否set集合的一名成员;如答案是肯定的是,则返回TRUE ● FD_SET(s, *set):将套接字s加入集合set ● FD_ZERO( * set):将set初始化成空集合

例如

假定想知道是否可从一个套接字中安全地读取数据,同时不会陷于无休止的锁定状态

  1. 可使用 FDSET 宏,将套接字分配给 fdread 集合,再来调用 select
  2. 要想检测自己的套接字是否仍属 fdread 集合的一部分,可使用 FD_ISSET 宏。

采用下述步骤,便可完成用 select 操作一个或多个套接字句柄的全过程:

  1. 使用FDZERO宏,初始化一个fdset对象 - 初始化
  2. 使用FDSET宏,将套接字句柄加入到fdset集合中 - 添加到待处理socket集合中
  3. 调用 select 函数,等待其返回 - 调用 select 选择socket处理
  4. select 完成后,会返回在所有 fdset 集合中设置的套接字句柄总数,并对每个集合进行相应的更新 - select执行完后更新集合(将待决状态取消)
  5. 根据 select的返回值和 FDISSET宏,对 fdset 集合进行检查。 - 对集合进行检查,去除非待处理socket
  6. 知道了每个集合中待决定的 I/O操作之后,对 I/O进行处理,然后返回步骤1,继续进行 select 处理。 - select选择的socket进行I/O操作

  7. select 函数返回后,会修改 fdset 结构,删除那些不存在待决 I/O 操作的套接字句柄。 > 这是在上述的步骤 5 中,使用 FDISSET 宏来判断一个特定的套接字是否仍在集合中的原因。