Network Programming For Microsoft Windows Notes:Winsock I/O

1 套接字模式

Windows 套接字在两种模式下执行I/O 操作:阻塞模式非阻塞模式 。在阻塞模式 下,I/O 操作完成之前,执行操作的Winsock 调用(例如sendrecv )会一直等候下去,不会立即返回到程序中(将控制权交还给程序)。在非阻塞模式 下,Winsock 函数无论如何都会立即返回。

注意

(1)     阻塞模式。

对阻塞套接字来说,它的一个缺点 在于:应用程序很难同时通过多个建好连接的套接字通信。一种方案可对其修改:为每一个套接字都分配一个读线程,以及一个数据处理线程,虽然这样会增大一些开销,但的确是一种可行的方案。问题是伸缩性不好,以后想同时处理大量套接字时就不大方便了。

 

(2)     非阻塞模式。

非阻塞模式套接字在使用上存在一些难度,但是只要排除了这些困难,它在功能上还是非常强大。除具备阻塞套接字已有的各种优点之外,它还进行了少许扩充,功能更强。下面的代码是创建一个套接字,并将其置为非阻塞模式。

SOCKET s ;

unsigned long ul =1;

int nRet ;

 

s =socket (AF_INET ,SOCK_STREAM ,0);

nRet =ioctlsocket (s ,FIONBIO ,(unsigned long *)&ul );

if (nRet ==SOCKET_ERROR )

{

      // error

}

将一个套接字置为非阻塞模式之后,处理收发数据或处理连接的Winsock API 调用会立即返回。大多数情况下,这些调用在失败时会返回一个WSAEWOULDBLOCK 错误,这意味着请求的操作在调用期间没有足够时间来完成。

注意

(1)     由于非阻塞调用会频繁返回WSAEWOULDBLOCK 错误,所以在任何时候,都应仔细检查所有返回代码,并做好失败的准备。

(2)     许多程序员易犯的一个错误便是持续不停地调用一个函数,直到它返回成功的消息为止。例如,假定在一个紧凑的循环中不断地调用recv ,以读入200 个字节的数据,那么与使用MSG_PEEK 标志来轮询一个阻塞套接字相比,前一种做法并没有优势可言。为此,Winsock 套接字I/O 模型可帮助应用程序判断一个套接字何时可供读写

(3)     阻塞和非阻塞套接字模式都存在着优点和缺点。

 

2 套接字I/O 模型

一共有6 种类型的套接字I/O 模型,可让Winsock 应用程序对I/O 进行管理,它们包括:

(1)    blocking( 阻塞)

(2)    select( 选择)

(3)    WSAAsyncSelect( 异步选择)

(4)    WSAEventSelect( 事件选择)

(5)    overlapped( 重叠)

(6)    completionport( 完成端口)

 

(1)    blocking 模型

因为阻塞模型最简单,也最直接,所以大多数Winsock 编程人员都从这种模型开始。对于采用这个模型的应用程序,在处理I/O 时,每个套接字连接通常使用一个或两个线程,之后每个线程都将发出阻塞操作,如sendrecv

阻塞模型的优势是其简洁性。但是创建多线程会消耗宝贵的系统资源,而且很难将它扩展到有很多连接的情况。

(2)    select 模型

select 模型是Winsock 中另一个应用广泛的I/O 模型。之所以称其为“select 模型”,是由于它的工作原理是利用select 函数,实现对I/O 的管理。它使那些想避免在套接字调用过程中被阻塞的应用程序,能够采取一种有序的方式,同时进行对多个套接字的管理

select 函数可用于判断套接字上是否存在数据,或者能否向一个套接字写入数据。之所以要设计这个函数,其目的是 防止应用程序在套接字处于阻塞模式中时,在I/O 绑定调用(如sendrecv )过程中进入阻塞状态;同时也防止在套接字处于非阻塞模式中时,产生WSAEWOULDBLOCK 错误。其函数原型为:

int select (

              IN int nfds ,

              IN OUT fd_set FAR *readfds ,

              IN OUT fd_set FAR *writefds ,

              IN OUT fd_set FAR *exceptfds ,

              IN const struct timeval FAR *timeout

              );

注意

(1)    nfds 会被忽略。提供这个参数的目的是为了保持与早期的Berkeley 套接字应用程序的兼容。

(2)    这里有3fd_set 参数:一个用于检查可读性(readfds) ,一个用于检查可写性(writefds) ,另一个用于带外数据(exceptsfds) 。从根本上说,fd_set 数据类型代表着一系列特定套接字的集合。

(3)    假定要测试一个套接字是否可读,必须将这个套接字增添到readfds 集合中,再等待select 函数完成。当select 调用完成之后,必须判断这个套接字是否仍为readfds 集合的一部分。如果是,便表明该套接字可读,可立即着手从它上面读取数据。

(4)    readfdswritefdsexceptfds3 个参数中,任何两个都可以是NULL ,但是,至少有一个不能为空值。在任何不为空的集合中,必须包含至少一个套接字句柄,否者,select 函数便没有任何东西可以等待。

(5)    timeout 是一个指向timeval 结构的指针,用于决定select 等待I/O 操作完成时,最多等待多长的时间。如果timeout 是一个空指针,那么select 调用会无限期处于阻塞状态,直到至少有一个描述符与指定的条件相符后才结束。

 

select 对套接字进行监听之前,应用程序必须将套接字句柄分配给一个集合,设置好一个或所有的读、写以及例外的fd_set 结构。将一个套接字分配给任何一个集合后,再来调用select ,便可知道某个套接字上是否正在发生I/O 活动。可以使用下面的方法来针对I/O 活动,对fd_set 集合进行处理与检查:

l          FD_ZERO(*set) :将set 初始化成空集合。集合在使用前都要清空。

l          FD_CLR(s, *set) :从set 中删除套接字s

l          FD_ISSET(s, *set) :检查s 是否为set 集合的一名成员,是则返回TRUE

l          FD_SET(s, *set) :将套接字s 加入集合set 中。

下面是完成用select 操作一个或多个套接字句柄的全过程

    使用FD_ZERO 宏,初始化自己感兴趣的每一个fd_set

    使用FD_SET 宏,将套接字句柄分配给自己感兴趣的每个fd_set

    调用select 函数,然后等待直到I/O 活动在指定的fd_set 集合中设置好了一个或多个套接字句柄。select 完成后,会返回在所有fd_set 集合中设置的套接字句柄总数,并对每个集合进行相应的更新。

    根据select 的返回值,应用程序便可判断出哪些套接字存在着被搁置的I/O 操作。具体的方法是使用FD_ISSET 宏,对每个fd_set 集合进行检查。

     知道了每个集合中被挂起的I/O 操作之后,对I/O 进行处理,然后返回步骤 ,继续处理select

 

下面显示为单个套接字设置select 模型的基本步骤:(若想添加更多的套接字,只需为需要添加的套接字保留一个列表,或保留它们的一个数组)

SOCKET s ;

fd_set fdread ;

int ret ;

 

// create socket and a connect

// ...

 

// manage the operation of I/O

while (true )

{

      // firstly, clear fdread

      FD_ZERO (&fdread );

 

      // add s to fdread

      FD_SET (s ,&fdread );

      if ((ret =select (0,&fdread ,NULL ,NULL ,NULL )) == SOCKET_ERROR )

      {

           // error

      }

      if (ret >0)

      {

           // if manage many sockets, the return value may be larger than 1

           // check

           if (FD_ISSET (s ,&fdread ))

           {

                 // something happened to the s

           }

      }

}

使用select 的优势是,能够从单个线程的多个套接字上进行多重连接及I/O 。这可以避免伴随阻塞套接字和多重连接的线程剧增。但是可以加到fd_set 结构中的最大套接字数量是一个不好的地方。默认情况下,最大数量由FD_SETSIZE 定义,该值在winsock2.h 中定义为64

 

(3)    WSAAsyncSelect 模型

Winsock 提供了一个有用的异步I/O 模型。利用这个模型,应用程序可在一个套接字上,接收以Windows 消息为基础的网络事件通知。具体的做法是在建好一个套接字后,调用WSAAsyncSelect 函数。

WSAAsyncSelect 模型有许多优点 ,最突出的一个方面是,它可以在系统开销不大的情况下同时处理许多连接,而select 模型需要建立fd_set 结构。但缺点 是,即使应用程序不需要窗口(例如服务或控制台应用程序),它也不得不额外使用一个窗口 。同时,用一个单窗口程序来处理成千上万的套接字中的所有事件,很可能成为性能瓶颈(意味着这个模型伸缩性不太好)。

 

(4)    WSAEventSelect 模型

Winsock 提供了另一个有用的异步事件通知I/O 模型。和WSAAsyncSelect 模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基础 的网络事件通知。该模型最主要的差别在于:网络事件通知是由事件对象句柄 完成的,而不是通过窗口例程 完成。

WSAEventSelect 模型有几个方面的优势。它概念简单,不需要窗口环境。但惟一的缺点是,它每次只等待64 个事件 ,这一限制使得在处理多个套接字时,有必要组织一个线程池。同时,因为需要许多线程去处理大量套接字连接,这个模型的伸缩性不如重叠模型

 

注意 WSAAsyncSelectWSAEventSelect 模型提供了读写数据能力的异步通知,但是它们不提供异步数据传送。而重叠模型和完成端口模型却提供异步数据传送。

 

(5)    重叠模型

Winsock 中,重叠IOOverlapped I/O )模型可以使应用程序达到更佳的系统性能。

       重叠模型的基本设计原理 是:让应用程序使用重叠的数据结构,一次投递一个或多个Winsock I/O 请求。针对那些提交的请求,在它们完成之后,应用程序可为它们提供服务。这种机制可通过ReadFileWriteFile 两个函数,在设备上执行I/O 操作。注意,自Winsock 2 发布以后,重叠I/O 便已集成到新的Winsock 函数中,例如WSARecvWSASend

 

(6)    完成端口模型

因为需要做出大量的工作以便将套接字添加到一个完成端口,而其他I/O 方法的初始化步骤则省事了很多。但是弄明白是怎么回事后,就会发现步骤其实并非那么复杂。

假如,一个应用程序同时需要管理为数众多的套接字,那么采用这种模型,往往可以达到最佳的系统性能。但是,该模型只适用于Windows NTWindows 2000Windows XP 操作系统。完成端口模型提供了最好的伸缩性,因此,这个模型非常适合用来处理数百乃至上千个套接字

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值