Winsock 套接字I/O模型 之 select 模型

“select模型”是利用select函数来管理I/O。select函数可用于判断套接字上是否存在数据,或者能否向一个套接字写入数据。比如可以先调用select函数判断能否从某个套接字读数据,以避免直接调用recv进入阻塞状态(或者在非阻塞模式时产生WSAEWOULDBLOCK错误)。

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

);

参数nfds忽略(它是为兼容早期Berkeley套接字),置0即可。接着是3个fd_set参数:readfds用于检查可读性,writefds用于检查可写性,exceptfds用于带外数据。最后一个参数timeout控制超时时间。在timeout时间内,3个套接字集合中有一个或多个套接字上有事件发生时,select函数返回(返回值为已就绪的套接字个数),系统会把满足条件的套接字放到相应的集合中。

可以检测套接字集合来确定哪些套接字上发生了感兴趣的事件(如读、写)。如果timeout超时,集合中任何套接字都没有事件发生,select返回0。如果timeout为空指针,select将一直等待。如果timeout超时值为0,select检测集合中的套接字是否有事件发生后会立即返回,这样允许应用程序对select操作进行轮询,效率较低,通常不这样使用。

3个fd_set参数都代表着一系列特定套接字的集合。

readfds集合包含符合下述任何一个条件的套接字:

1. 有数据可读

2. 连接已被关闭、重启或终止

3. 套接字正在监听(已调用listen),而且有一个连接正处于搁置状态,accept调用将会成功,不发生阻塞

(备注:刚开始对于2、3点不理解,后来由csdn网友帮忙并结合msdn,终于理解了。其实这里的“可读性”,并不单单指recv不阻塞,比如也可指accpet会立即成功不阻塞。msdn的原文解释如下:

The parameter readfds identifies the sockets that are to be checked for readability. If the socket is currently in the listen state, it will be marked as readable if an incoming connection request has been received such that an accept is guaranteed to complete without blocking. For other sockets, readability means that queued data is available for reading such that a call to recv, WSARecv, WSARecvFrom, or recvfrom is guaranteed not to block.

For connection-oriented sockets, readability can also indicate that a request to close the socket has been received from the peer. If the virtual circuit was closed gracefully, and all data was received, then a recv will return immediately with zero bytes read. If the virtual circuit was reset, then a recv will complete immediately with an error code such as WSAECONNRESET. The presence of OOB data will be checked if the socket option SO_OOBINLINE has been enabled (see setsockopt).)

 

writefds集合包含符合下述任何一个条件的套接字:

1. 可以写入数据

2. 如果正在处理一个connect调用(非阻塞),连接将成功

 

exceptfds集合包含符合下述任何一个条件的套接字:

1. 如果正在处理一个connect调用(非阻塞),连接将失败

2. 有OOB(out-of-band,带外)数据可读

(备注:看如下代码,之前对于这里的errorSet检测不理解,现在结合exceptfds的第1条理解了。

fd_set writeSet, errorSet;

FD_ZERO(&writeSet);

FD_ZERO(&errorSet);

FD_SET(sock, & writeSet);

FD_SET(sock, & errorSet);

 

// check if the socket is ready

select(0, NULL, &writeSet, &errorSet, &timeout);)

假如想检测一个套接字是否可读,需要将该套接字添加到readfds集合中,当select调用完成之后,检测该套接字是否仍在readfds集合中。如果在,则表明该套接字可读。

readfd、writefds、exceptfds至少有一个不为空,在不为空的集合中,至少包含一个套接字。否则select函数便无任何东西可等待。如果select调用失败,会返回SOCKET_ERROR。

下面总结一下典型的select操作的全过程:

1. 使用FD_ZERO宏,初始化感兴趣的fd_set。

fd_set readSet;

FD_ZERO(&readSet);

 

2. 使用FD_SET宏,将套接字句柄加入到fd_set集合中。

FD_SET(sock, & readSet);

 

3. 调用select函数,select完成后,系统会对每个集合进行更新,并返回所有fd_set集合中的套接字句柄总数。

int returnValue = select(0, & readSet, NULL, NULL, &timeout);

 

4. 根据select的返回值,并结合FD_ISSET宏,可判断出套接字的状态(是否可读、可写等),并进行相应处理。FD_ISSET用于检查套接字是否在某个集合中。

if (returnValue == SOCKET_ERROR)

       // 发生错误

else if (returnValue == 0)

       // 超时

else if (returnValue > 0)

{

       if (FD_ISSET(sock, &readSet))

              // sock可读

}

 

5. 返回步骤1,继续处理。

使用select的优势是,可以在单个线程中,对多个套接字进行I/O管理。

注意:可以加到fd_set集合中的套接字数量有一个限制,在winsock2.h文件中由FD_SETSIZE定义。

#ifndef FD_SETSIZE
#define FD_SETSIZE      64
#endif /* FD_SETSIZE */

可以加大FD_SETSIZE的值,比如在包含winsock2.h头文件之前,将FD_SETSIZE定义为1000(由于底层限制,通常不能超过1024),不过要实际测试一下效果。

【windows网络编程-读书笔记】

posted on 2013-01-29 14:11  zhuyf87  阅读(528)  评论(0编辑  收藏  举报

导航