操作模式

WSA的3种操作模式:阻塞的、非阻塞的、异步的。我们将解释这些术语,强调其优势和缺点,并从最终代码的复杂度、应用程序的灵活性以及系统的开销等方面来了解他们

什么是操作模式

Windows Socket具有3种完全不同的操作模式,描述这些模式的最好方法是类比。设想我们给某某打电话,但是某某没拿电话,这种情况下,我们可以选择的是:

1、不挂机,等待,直到对方过来接电话  这种在网络编程中属于阻塞模式,服务端在等客户端连接,如果客户端一直不连接,那么它就在那里死等,一年不连接,它就等一年什么都不干,往下什么都干不了

2、挂机,等会再打。在非阻塞操作模式中,Windows Socket函数立即返回,有些情况下表明返回成功,但是有些却表明失败。不过非阻塞操作模式中的函数调用失败并不一定是坏事,错误值有可能是WSAEWOULDBLOCK,字面意思就是如果必须等待操作完成才能返回,那么函数应该阻塞才对

根据函数的不同,WSAEWOULDBLOCK的含义可能代表两种情况之一

或者表示WinSock DLL已经开始操作了,但目前还没有完成,还在等待中

或者表示WinSock 做了尝试,但在当下无法满足操作请求,表明用户还需求再次调用函数

3、留言,让某某打过来。异步模式是非阻塞的,因为它在操作完成之前就从调用返回了,它与常规非阻塞不同,这里WSAEWOULDBLOCK的含义是:当一个等待处理的操作完成时,或者当重新发起一个操作时,WinSock DLL将发送一个消息 (就是某某回拨你)来通知你

2.png

那么我们首先来学习阻塞模式相关内容

 --阻塞socket

 --阻塞函数

--伪阻塞的问题

--阻塞钩子函数

--阻塞情景

--撤销阻塞操作

--阻塞操作中的超时

--无最少接收限制值

阻塞socket

阻塞式函数要等待请求操作完成后才会返回,操作完成后,函数的返回值表明了操作成功或失败。当通过socket()函数调用得到了一个socket描述符时,默认该socket采取阻塞模式。系统中若存在一个阻塞的socket,将会影响很多WinSock函数的操作方式,另外,有一些WinSock总是以阻塞模式进行操作。

从socket()函数调用所返回的socket默认以阻塞模式进行操作,这意味着引用这个socket句柄的某些WinSock函数调用将会阻塞,直到操作完成,当应用程序在socket句柄输入参数中引用了阻塞socket时,很多函数的调用都是阻塞的,表5-1给出了所有这类函数

2.png

阻塞函数

还有一些函数总是阻塞的,无论传递给它的是否是一个阻塞的socket,大多数这类函数甚至并没有把一个socket作为输入参数。

52.png

如图 select()函数是唯一一个引用了socket的函数,它实际上同时引用了多个socket,因为它采用了3组socket作为输入参数,无论3组中任何一个socket是否为阻塞模式,函数都是以阻塞模式进行的。它不执行任何网络I/0,但是它确实会检测每组中各个socket的当前状态

图中只有gethostbyname()和gethostbyaddr()有可能产生网络I/O,这些函数通常利用远程服务器来生成主机名或主机地址,如对于TCP/IP协议是用DNS,因此,可能需要一段时间才能从阻塞式操作返回,不过由于Windows Sockets规范没有规定WinSock Dll应该如何解析主机名和地址,因此,也可以引用本地系统的宿主文件快速返回。

在大多数WinSock的实现中,都是通过引用本地服务和协议的“数据库”文件来执行协议和服务的解析,所以通常能够很快返回,这也是伯克利套接字的操作方式

阻塞情景

Windows Socket规范中规定,当每一个任务或线程中的阻塞操作尚未完成时,不能执行其他网络操作,这种情况被称作阻塞情景。在这期间,除了WSACancelBlockingCall() WSAIsBlocking()外,任何其他WinSock都将调用失败并返回WSAEINPROGRESS错误

如果你在应用程序的任何地方使用阻塞模式,当你试图进行其他WinSock调用时,应用程序总是有可能遭遇阻塞情景,可以采取下列手段避免这种阻塞情景带来的问题

1、正确应对WSAEINPROGRESS

无论何时在采用阻塞模式的应用程序中发起网络操作,你都希望WinSock函数能处理WSAEINPROGRESS错误,当存在一个等待处理的阻塞操作时,就会出现这种情况。

只要存在一个未完成的阻塞函数,任何WinSock都可能失败并产生WSAEINPROGRESS错误,两个除外WSACancelBlockingCall() WSAIsBlocking()

WSAEINPROGRESS不会对程序造成致命威胁,程序应该总是对此有所准备并能够正确的处理这种情况 即不会使应用程序终端或退出 对于任何一个WinSock函数调用,都需要问下自己,如果碰到这种错误怎么办。

在WinSock函数出现WSAEINPROGRESS错误后,采取什么行动完全取决于应用程序设计,理想情况下是能够避免这种错误的。

下面是两种阻塞情景的策略来规避这一错误

2、用WSAIsBlocking()进行检测

WSAIsBlocking() 专门为检测阻塞情景而设计的

WSAIsBlocking() 不具有任何参数,当前任务或线程就是隐含的输入参数。如果存在尚未完成的阻塞操作,函数会简单的返回一个TRUE,否则返回FALSE 当返回TRUE时,你就需要知道避免WinSock函数调用,因为可能产生WSAEINPROGRESS错误

WSAIsBlocking() 很好的承担了报告阻塞情景的任务,免除WSAEINPROGRESS错误的麻烦,但是应用程序可能存在其他不愿被干扰的事情,可能是所执行的一系列网络操作或者非网络操作,因而可能希望避免这些情景的重入,另一种更通用的办法是让应用程序跟踪自己的操作轨迹

3、保持应用程序的状态信息

为了避免WSAEINPROGRESS错误,同时也避免干扰其他操作的情景,另一种方法就是在应用程序中维持一个代表应用程序当前状态的变量。这种方法要求设置一个表示当前执行操作的变量,在发起新的操作前检查该变量的状态。这样,打算执行一个新的操作前能够确定应用程序处于恰当的状态

撤销阻塞操作

撤销一个等待中的阻塞操作有很大原因。如允许用户从其错误中退出来,或者用户不愿再等待。允许用户在应用程序的任何一点上撤销这类或其他类的阻塞操作是不可能的,也可以使用一个windows的定时器来实现自己的超时控制

函数WSACancelBlockingCall()允许通过强制其失败来强行结束一个等待的阻塞操作,WSACancelBlockingCall()是所有阻塞操作的应急出口

WSACancelBlockingCall()没有参数,任务或线程就是隐含的参数。WSACancelBlockingCall()撤销等待中的阻塞操作,若没有等待处理的阻塞操作则失败返回,返回SOCKET_ERROR WSAGetLastError()返回WSAEINVAL错误

但是它并不像看起来那么简单。

1、撤销作用滞后生效

WSACancelBlockingCall()总是立即返回,并不等待处理中的阻塞操作撤销完成,所以阻塞操作在WSACancelBlockingCall()返回时仍处于待决状态

当被中断的函数以WASEINTR错误返回时,阻塞操作的撤销操作才能完成。有时,由于Windows调度程序收回了对阻塞例程的控制,则WSACancelBlockingCall()函数调用和返回WASEINTR错误的时间间隔很长,当然在16为Windows系统中,在等待撤销生效时,需要高明的进行避让 注意,即使你在调用之前进行资源的避让,也不可用WSAIsBlocking()循环以等待撤销否则撤销将不能完成

为了能支持撤销操作,所有阻塞函数的调用必须合理的应对WASEINTR错误,由应用程序的设计决定什么是合理的处理。在大多数情况下,特别是对流socket,合理的处理意味着关闭socket,因为该socket的继续使用已经受限

2、限制socket的继续使用

WinSock规范中告诫:对某些阻塞socket调用的撤销可能会导致一个socket处于中间状态。函数accept()和select()是规范所提到的仅有的例外,所以任何撤销操作它们是没有问题的。而对其他的一些阻塞函数的撤销则可能损害一个数据流的完整性 如果发生这种情况,其后的函数调用将失败并返回WSACONNABORTED错误

除了accept()和select()外,在阻塞函数撤销后,唯一还能保证正常工作的网络函数是closesocket(),并且只有在你终止连接设置 setsockopt()的SO_LINGER超时值0而非正常关闭连接的情况下,closest()才是仅有的保证,这种可能性限制了WSACancelBlockingCall()的通用性

3、撤销可能操作失败

阻塞操作中的超时

阻塞函数直到操作完成才会返回,如果操作永远不返回会怎样?WinSock DLL和协议栈会自动判定一些操作为超时,也可以为另外一些操作设置超时,对于阻塞操作,有一些普遍适用的超时策略

1、自动超时

一些诸如connect() send() gethostbyname() 的函数将会自动超时 connect() send() 既影响阻塞操作,又影响非阻塞操作,不过应用程序对这些函数的超时时间没有任何控制能力 由网络系统单独决定何时发生超时 这些网络系统的超时与其所采用的协议的超时机制有关如 ARP超时,TCP SYN或ACK超时或DNS超时 WSA没有为检测或改变这些网络系统的超时值提供任何途径

2、用户可设置的超时

一些如select() closesocket()函数允许应用程序来决定超时值

至于closesocket()函数,默认情况下,它不是阻塞模式的,即使对阻塞socket也是如此。SO_DONTLINGER是closesocket()的一个默认选项,只有对一个阻塞socket,并且setsockopt()设置了非0的超时值来使能SO_LINGER选项时,它才是阻塞式的 一般来说,应用程序不应该为closesocket()设置非0的超时值。对于TCP连接,协议栈为其设置默认的超时值,一般不需要覆盖这个值

3、应用程序超时

一些如recv() recvfrom() accept()的函数能一直阻塞下去,这些函数没有时间限制,所以没数据可接收时,recv() recvfrom()将一直不返回 而accept()一直没有收到连接请求也一直不返回 应尽量避免对这些阻塞函数调用的撤销操作,可以代之以另一种策略,即等待函数的操作条件满足后再调用它,这样对应用程序会更好一些,我们后面会介绍怎样实现上述任务

本博客所有文章如无特别注明均为原创。作者:odaycaogen复制或转载请以超链接形式注明转自 123``blog
原文地址《操作模式

相关推荐

发表评论

路人甲 表情
Ctrl+Enter快速提交

网友评论(0)