手把手教你SOCKET模型之select模型

手把手教你SOCKET模型之select模型 

时间: 2008.12.23 13:48:00 
标签:  
本文版权属于《Visual C++网络游戏建模与实现》作者苏羽和http://www.wantsoft.com共同所有,引用者请务必保留本行版权信息,否则作者苏羽和http://www.wantsoft.com保留追究的权利。

Select 模型是最常见的I/O模型。注意,并没有说它是WinSock中最常见的模型,这是因为,select()来源于Unix系统,而WinSock是借鉴 Unix下的Socket套接字而来,因此理所当然地包含了select()。然而,也是因为它是从Unix借鉴而来,所以必然不能成为WinSock I/O模型的代表(Microsoft一向如此)。不过尽管这样,select的使用还是很广泛的,而且使用它对Socket程序的移植也是有一定好处的。

现在select模型已集成到WinSock 1.1中,它使不想在套接字调用中被阻塞的那些应用程序采取一种有序的方式,同时对多个套接字进行管理。由于WinSock1.1向后兼容于 Berkeley套接字实施方案,所以假如有一个Berkeley套接字应用使用了select()函数,那么从理论角度讲,毋需对其进行任何修改,便可正常运行。当然,由于Windows和Unix操作系统的不同,程序的头文件还必须修改。

可以利用select( )函数来判断套接字上是否存在数据,或者能否向一个套接字写入数据。之所以要设计这个函数,惟一的目的便是防止应用程序在套接字处于锁定模式中时,在一次 I/O绑定调用(如send或recv)过程中,被迫进入“锁定”状态;同时防止在套接字处于非锁定模式中时,产生WSAEWOULDBLOCK错误。除非满足事先用参数规定的条件,否则select( )函数会在进行I/O操作时锁定。

有select( )函数可以帮助你同时监视很多套接字,它可以告诉你哪一个套接字已经可以读取数据,哪个套接字已经可以写入数据,如果需要,甚至可以告诉你哪个套接字出现了错误。
select( )的函数原型如下:
int select(
    int nfds,
    fd_set FAR * readfds,
    fd_set FAR * writefds,
    fd_set FAR * exceptfds,
    const struct timeval FAR * timeout
);

其中,第一个参数nfds会被忽略,之所以仍然要提供这个参数,只是为了保持与早期的Berkeley套接字应用程序的兼容。在Berkeley套接字中该项用于表示readfds、writefds、exceptfds中的fd集合文件描述符里最大的数字加1。
有3个fd _set参数:一个用于检查可读性(readfds),一个用于检查可写性(writefds),另一个用于例外数据(exceptfds)。从根本上说,fd_set数据类型代表着一系列特定套接字的集合。
其中,readfds集合包括符合下述任何一个条件的套接字:
• 有数据可以读入。
• 连接已经关闭、重设或中止。
• 假如已调用listen,而且一个连接正在建立,那么accept函数调用会成功。
writefds集合包括符合下述任何一个条件的套接字:
• 有数据可以发出。
• 如果已完成了对一个非锁定连接调用的处理,连接就会成功。
exceptfds集合包括符合下述任何一个条件的套接字:
• 假如已完成了对一个非锁定连接调用的处理,连接尝试就会失败。
• 有带外(Out-of-band,OOB)数据可供读取。
要测试一个套接字是否“可读”,必须将自己的套接字增添到readfds集合,再等待select()函数完成。select完成之后,必须判断自己的套接字是否仍为readfds集合的一部分。当select( )函数返回时,readfds会被修改用来告诉你哪个文件描述符可以用来读取数据。若当前套接字为readfds集合的一部分,便表明该套接字“可读”,可立即着手从它上面读取数据。在3个参数readfds、writefds和exceptfds中,任何两个都可以是空值(NULL),但不能全为空值。在任何不为空的集合中,必须包含至少一个套接字句柄;否则,select()函数便没有任何东西可以等待。
最后一个参数timeout对应的是一个指针,它指向一个timeval结构,用于决定select()最多等待I/O操作完成多久的时间。
timeval结构的定义如下:
struct timeval
{
    long tv_sec;   /*秒数*/
    long tv_usec;  /*微秒*/
};
其中,tv_sec字段以秒为单位指定等待时间;tv_usec字段则以毫秒为单位指定等待时间。

select( )函数返回后,timeval中的时间会被设置为执行当select( )函数剩下的时间。select( )函数返回时,readfds成功完成后,会在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是否为set集合的一名成员;如果是,则返回TRUE。
• FD_SET(s,*set)  将套接字s加入集合set。
• FD_ZERO (* set )  将set集合清零。
为了检查是否可从一个套接字中安全地读取数据,同时不会陷于“锁定”状态,可使用FD_SET宏,将自己的套接字分配给fd_read集合,再来调用 select( )。可以使用FD_ISSET宏来检测套接字是否属于fd_read集合的一部分。用select( )操作一个或多个套接字句柄的全过程步骤如下:
(1)使用FD_ZERO宏,初始化自己感兴趣的每一个fd _ set。
(2)使用FD_SET宏,将套接字句柄分配给自己感兴趣的每个fd_set。
(3)调用select( )函数,然后等待在指定的fd_set集合中,设置一个或多个套接字句柄。Select( )返回所有fd_set集合中设置的套接字句柄总数,并对每个集合进行相应的更新。
(4)根据select( )的返回值,我们的应用程序便可判断出哪些套接字存在着尚未完成的操作,具体的方法是使用FD_ISSET宏,对每个fd _set集合进行检查。
(5)知道了每个集合中“待解决”的I/O操作之后,对I/O进行处理,然后返回步骤(1),继续进行select( )处理。
select( )函数返回后,它会修改每个fd_set结构,删除那些不存在待解决I/O操作的套接字句柄。

select()函数的例程如下:

你可能感兴趣的:(手把手教你SOCKET模型之select模型)