Winsock 套接字的两种模式阻塞和非阻塞

Windows 套接字在两种模式下执行I/O 操作:锁定和非锁定。 
在锁定模式下,在I/O 操作完成前,执行操作的Winsock函数 
比如send和recv)会一直等候下去,不会立即返回程序(将控 
制权交还给程序)。而在非锁定模式下,Winsock 函数无论如何 
都会立即返回。 
 
锁定模式 
对于处在锁定模式的套接字,我们必须多加留意,因为在一个锁 
定套接字上调用任何一个Winsock API 函数,都会产生相同的后果 
—耗费或长或短的时间“等待”。大多数Winsock应用都是遵照一种 
“生产者-消费者”模型来编制的。在这种模型中,应用程序需要 
读取(或写入)指定数量的字节,然后以它为基础执行一些计算。 
例如使用recv函数的时候,假如没有数据处于“待决”状态,那么 
recv 函数可能永远都无法返回。只有从系统的输入缓冲区中读回点 
什么东西,才允许返回!有些程序员可能会在recv中使用MSG_ PEEK 
 标志,或者调用ioctlsocket(设置FIONREAD选项),在系统的缓冲 
区中,事先“偷看”是否存在足够的字节数量。然而,在不实际读入 
数据的前提下,仅仅“偷看”数据(如实际读入数据,便会将其从系 
统缓冲区中将其删除),可不是一件光彩的事情。我们认为,这是一种 
非常不好的编程习惯,应尽全力避免。在“偷看”的时候,对系统造成 
的开销是极大的,因为仅仅为了检查有多少个字节可用,便发出一个或 
者更多的系统调用。以后,理所当然地,还需要牵涉到进行实际recv 
调用,将数据从系统缓冲区内删除的开销。那么,如何避免这一情况呢? 
在此,我们的目标是防止由于数据的缺乏(这可能是网络出了故障,也 
可能是客户机出了问题),造成应用程序完全陷于“凝固”状态,同时 
不必连续性地检视系统网络缓冲!为达此目的,一个办法是将应用程序 
划分为一个读线程,以及一个计算线程。两个线程都共享同一个数据缓 
冲区。对这个缓冲区的访问需要受到一定的限制,这是用一个同步对象 
来实现的,比如一个事件或者Mutex (互斥体)。“读线程”的职 
责是从网络连续地读入数据,并将其置入共享缓冲区内。读线程将计算 
线程开始工作至少需要的数据量拿到手后,便会触发一个事件,通知计算 
线程:你老兄可以开始干活了!随后,计算线程从缓冲区取走(删除) 
一个数据块,然后进行要求的计算。 
 
非锁定模式 
将一个套接字置为非锁定模式之后,Winsock API 调用会立即返回。大多数 
情况下,这些调用都会“失败”,并返回一个WSAEWOULDBLOCK 错误。什么 
意思呢?它意味着请求的操作在调用期间没有时间完成。举个例子来说,假 
如在系统的输入缓冲区中,尚不存在“待决”的数据,那么recv (接收数据) 
调用就会返回WSAEWOULDBLOCK 错误。通常,我们需要重复调用同一个函数, 
直至获得一个成功返回代码。由于非锁定调用会频繁返回WSAEWOULDBLOCK 错误, 
所以在任何时候,都应仔细检查所有返回代码,并作好“失败”的准备。许多 
程序员易犯的一个错误便是连续不停地调用一个函数,直到它返回成功的消息 
为止。例如,假定在一个紧凑的循环中不断地调用recv,以读入200 个字节的 
数据,那么与使用MSG_PEEK标志来“轮询”一个锁定套接字相比,前一种做法 
根本没有任何优势可言。为此,Winsock 的套接字I/O 模型可帮助应用程序判断一 
个套接字何时可供读写。 
 
锁定和非锁定套接字模式都存在着优点和缺点。其中,从概念的角度说, 
锁定套接字更易使用。但在应付建立连接的多个套接字时,或在数据的 
收发量不均,时间不定时,却显得极难管理。而另一方面,假如需要编 
写更多的代码,以便在每个Winsock调用中,对收到一个WSAEWOULDBLOCK 
错误的可能性加以应付,那么非锁定套接字便显得有些难于操作。在这些情况下,可考 
虑使用“套接字I / O 模型”,它有助于应用程序通过一种异步方式,同时对 
一个或多个套接字上进行的通信加以管理。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章