抗弯曲能力与质量无关:SOCKET学习第三阶段(SELECT模型

来源:百度文库 编辑:偶看新闻 时间:2024/05/01 01:19:55
SELECT套接字模型的学习

Winsock提供了五种类型的套接字I/O 模型,可让Winsock应用程序
对I/O 进行管理,它们包括:select(选择)、WSAAsyncSelect(异步选择)、
WSAEventSelect(事件选择)、overlapped(重叠)以及completion port
(完成端口)
我们将对这几个模型进行学习,今天学习的是SELECT模型
 
select 模型
select(选择)模型是Winsock中最常见的I/O模型。
之所以称其为“select 模型”,是由于它的“中心思想”便是利用select 函数,
实现对I/O 的管理! 最初设计该模型时,主要面向的是某些使用Unix 操作系统的计算机,
它们采用的是Berkeley 套接字方案。
select 模型已集成到Winsock 1.1 中,它使那些想避免在套接字调用过程中被无辜“锁定”的应用程序,
采取一种有序的方式,同时进行对多个套接字的管理。
由于Winsock 1.1 向后兼容于Berkeley 套接字实施方案,所以假如有一个Berkeley套接字应用使用了select函数,那么从
理论角度讲,毋需对其进行任何修改,便可正常运行。
利用select函数,我们判断套接字上是否存在数据,或者能否向一个套接字写入
数据。之所以要设计这个函数,唯一的目的便是防止应用程序在套接字处于锁定
模式中时,在一次I/O绑定调用(如send或recv)过程中,被迫进入“锁定”状态;
同时防止在套接字处于非锁定模式中时,产生WSAEWOULDBLOCK错误。
除非满足事先用参数规定的条件,否则select函数会在进行I/O 操作时锁定。
select的函数原型如下:
int select(
     int nfds,
     fd_set FAR * readfds,
     fd_set FAR * writefds,
     fd_set FAR * exceptfds,
     const struct timeval FAR * timeout
);
    其中,第一个参数nfds会被忽略。之所以仍然要提供这个参数,只是为了
保持与早期的Berkeley套接字应用程序的兼容。
大家可注意到三个fd_ set 参数:
typedef struct fd_set {
        u_int fd_count;               /* how many are SET? */
        SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
} fd_set;

一个用于检查可读性(readfds ),
一个用于检查可写性(writefds),
另一个用于例外数据(exceptfds)。
从根本上说,fd_set数据类型代表着一系列特定套接字的集合。
其中,
readfds 集合包括符合下述任何一个条件的套接字:
 *有数据可以读入。
 *连接已经关闭、重设或中止。
 *假如已调用了listen ,而且一个连接正在建立,那么accept 函数调用会成功。
writefds集合包括符合下述任何一个条件的套接字:
 *有数据可以发出。
 *如果已完成了对一个非锁定连接调用的处理,连接就会成功。
  最后,
exceptfds 集合包括符合下述任何一个条件的套接字:
 *假如已完成了对一个非锁定连接调用的处理,连接尝试就会失败。
 *有带外(Out-of-band,OOB)数据可供读取。
 
例如,假定我们想测试一个套接字是否“可读”,必须将自己的套接字增添到
readfds集合,再等待select 函数完成。select完成之后,必须判断自己的套接字
是否仍为readfds集合的一部分。
若答案是肯定的,便表明该套接字“可读”,可立即着手从它上面读取数据。
在三个参数中(readfds 、writefds 和exceptfds ),
任何两个都可以是空值(NULL);
但是,至少有一个不能为空值!在任何不为空的
集合中,必须包含至少一个套接字句柄;
否则,select 函数便没有任何东西可以等待。
最后一个参数timeout对应的是一个指针,它指向一个timeval 结构,
用于决定select最多等待I / O 操作完成多久的时间。如timeout是一个空指针,
那么select 调用会无限期地“锁定”或停顿下去,直到至少有一个描述符符合
指定的条件后结束。对timeval 结构的定义如下:
struct timeval
{
   long tv_sec;
   long tv_usec;
};
其中,tv_sec字段以秒为单位指定等待时间;tv_usec字段则以毫秒为单位指定
等待时间。若将超时值设置为(0 , 0),表明select 会立即返回,允许应用
程序对select 操作进行“轮询”。出于对性能方面的考虑,应避免这样的设置。
 
select 成功完成后,会在fd_set结构中,返回刚好有未完成的I/O 操作的所有
套接字句柄的总量。若超过timeval 设定的时间,便会返回0 。
不管由于什么原因,假如select 调用失败,都会返回SOCKET _ ERROR 。
用select 对套接字进行监视之前,在自己的应用程序中,
必须将套接字句柄分配给一个集合,设置好一个或全部读、写以及例外fd_ set 结构。
将一个套接字分配给任何一个集合后再来调用select ,便可知道一个套接字上是否正在发生上述的I/O 活动。

Winsock 提供了下列宏操作,可用来针对I/O 活动,对fd_set 进行处理与检查:
 *FD_CLR(s, *set):从set 中删除套接字s 。
 *FD_ISSET(s, *set):检查s 是否s e t 集合的一名成员;如答案是肯定的是,
  则返回T R U E 。
 *FD_SET(s, *set):将套接字s 加入集合s e t 。
 *FD _ ZERO (*set):将set 初始化成空集合。
例如,假定我们想知道是否可从一个套接字中安全地读取数据,同时不会
陷于无休止的“锁定”状态,便可使用FD_SET 宏,将自己的套接字分配给
fd_read 集合,再来调用select 。要想检测自己的套接字是否仍属fd_read
集合的一部分,可使用FD_ISSET 宏。

采用下述步骤,便可完成用select 操作一个或多个套接字句柄的全过程:
 1) 使用FD_ZERO 宏,初始化自己感兴趣的每一个fd_set 。
 2) 使用FD_SET 宏,将套接字句柄分配给自己感兴趣的每个fd_set。
 3) 调用select 函数,然后等待在指定的fd_set 集合中,I/O 活动设置好一个或多个
    套接字句柄。select 完成后,会返回在所有fd_set 集合中设置的套接字句柄总数
,并对每个集合进行相应的更新。
 4) 根据select 的返回值,我们的应用程序便可判断出哪些套接字存在着尚未完成(待
决) 的I / O 操作—具体的方法是使用FD_ ISSET 宏,对每个fd_set 集合进行检查。
 5) 知道了每个集合中“待决”的I / O 操作之后,对I / O 进行处理,然后返回步骤
1 ), 继续进行select 处理。
    select 返回后,它会修改每个fd_ set 结构,删除那些不存在待决I / O
操作的套接字句柄。这正是我们在上述的步骤( 4 )中,为何要使用FD _ ISSET宏
来判断一个特定的套接字是否仍在集合中的原因。

关于SELECT模型的使用例子,可访问下面的地址:
http://www.codeproject.com/KB/IP/ScalableClientServer.aspx