Windows Socket 网络编程——第五章 操作模式

来源:互联网 发布:linux系统性能监控 编辑:程序博客网 时间:2024/06/05 16:44
第五章操作模式
5.1 什么是操作模式
Windows Sockets将操作模式分为3种完全不同的类型,它们分别是:阻塞模式、非阻塞模式和异步模式。我们可以通过将它们与打电话时所遇到的3种“操作模式”做比较来形象的了解这3种操作模式。我们在打电话时拨号并得到响应信号,但是我们想要与之童话的人这会儿不再电话旁,在这种情况下,可选操作如下:
1. 不挂机,等待,直到对方过来接电话。
2. 挂机,之后再打电话过去。
3. 留言,让对方打过来。
5.1.1不挂机,等待:阻塞
在等待对方来接电话的过程中,我们可能做不了多少别的事,最多可以和别人聊聊天或者做点手头工作,但是无法使用电话。这与阻塞模式非常类似:可以腾出手来做点别的事,但是你进行的网络操作不能超过一个。
在阻塞模式中,Windows Sockets 函数只有当操作完成时才返回,结果可能成功,也可能失败,但是无论是哪一种情况,你都在函数返回前知道操作完成了。
在非抢占式任务系统中,WinSock DLL正在处理一个阻塞式函数调用时,它会让位于其他应用程序。由于在一个阻塞式函数调用没有完成时,是不能调用任何其他WinSock函数,这就使得接收消息成为一个大问题,因为这通常要在阻塞操作正在进行时发起网络操作。在阻塞情境(blocking context)中,代码需要恰当地处理这种重入消息。
5.1.2挂机后再拨:非阻塞
如果你挂机,稍后再拨,那么需要确定什么时候再拨以多长时间拨一次。如果不断地拨号,那是在浪费生命。你希望在呼叫之间做点其他的事情。这与非阻塞式操作相似:在操作完成过程中需要“探询”,但是你不想探询得太频繁,导致浪费系统资源。
在非阻塞操作模式中,Windows Sockets函数调用立即返回,有些情况下返回值表明成功,但是有些却表明失败。不过,非阻塞操作模式中的函数调用失败并不一定就是坏事。错误值(函数失败后调用WSAGetLastError()返回)有可能是WSAEWOULDBLOCK,其字面上的意思是“如果必须等待操作完成才能返回,那么函数应该阻塞才对”。
根据函数的不同,WSAEWOULDBLOCK的含义可能代表两种情况之一:或者表示WinSock DLL已经开始了操作,但是目前还没有完成(及操作还在等待之中);或者表示WinSock作了尝试,但在当下无法满足操作请求。在前一种情况下,操作最后总要完成,并且有若干种方法来检测操作是否完成;在后一种情况下,WSAEWOULDBLOCK错误信息表明用户需要再次调用函数。
非阻塞操作模式存在的问题是:为了完成一个操作,应用程序需要反复地去调用函数(探询),或者需要去检测完成情况。如果探询得太频繁会带来不容忽视的系统开销。但是相反地,如果探询得不够及时,则会给系统的性能(如数据吞吐量)带来不利的影响。除此之外,非阻塞模式也带来了编码的复杂性。
5.1.3请求对方回拨:异步
如果你请求对方回拨过来,那么完成任务的责任便转移给了被呼叫的那个人。在等待他回拨的时候,你可以做任何你想做的事,包括使用电话呼叫其他人。如果对方打过来你占线,对方还会再拨,知道你接到回拨的电话。这与异步式操作模式相似:当你请求回拨的事件发生时,WinSock DLL会费你发送消息。
异步操作模式是非阻塞的,因为它在操作完成之前就从调用返回了。但是和常规的非阻塞操作模式不同,WSAEWOULDBLOCK的含义是:当一个等待处理的操作完成时,或者当重新发起一个操作时,WinSock DLL将发送一个消息(即“对方回拨你”)来通知你。
综合以上,在编码是使用异步操作模式可以带来更”友好“和高效的Windows操作及快速的数据吞吐。
5.2 阻塞模式
5.2.1阻塞socket
从socket()函数调用所返回的socket默认以阻塞模式进行操作。这就意味着,使用这个socket描述符的有些WinSock函数调用也会阻塞,知道操作完成。这些函数如下表所示:
函数名 阻塞至……
accept() 应用程序收到一个连接请求……
closesocket() 关闭操作完成(知识流socket阻塞,并且只在setsockopt()的SO_LINGER设置了非0超时值时)
connect() 连接操作完成(只对流socket)
recv()、recvfrom() 网络系统在这个socket上接收到数据
send()、sendto() 网络系统中有输出数据可用的缓存空间(只对流socket)
5.2.2 阻塞函数
还有一些函数总是阻塞的,不管你输入的是否是一个阻塞的socket。大多数这种函数也没有把socket作为输入参数,如下表所示:
函数名 描述
select() 检测socket集合的可读性、科协性和错误
gethostbyname() 通过主机名查询主机信息
gethostbyaddr() 通过主机地址查询主机信息
getprotobyname() 通过协议名查询协议号
getprotobynumber() 通过协议号查询协议名
getservbyname() 通过服务名查询服务信息
getservbyport() 通过端口号查询服务信息
在上述函数中,select()函数是唯一使用了socket作为参数的函数,肩擦socket的状态,没有任何网络I/O操作。
5.2.3关于为阻塞的问题
所谓的为阻塞的含义是:虽然应用程序在等待网络操作完成,但是WinSock DLL却在等待时进行了避让(yield),在等待操作完成时,由于WinSock DLL调用了阻塞钩子函数,因此阻塞变成了为阻塞。
通常,默认的阻塞钩子程序处理Windows的消息,并隐式地避让系统中的其他进程,目的是在等待操作完成的过程中,检测中断(来自WSACancelBlockingCall())并进行避让。通过避让,默认的阻塞钩子函数允许有其他应用程序同时进行操作,它有它有益的一面,但同时它允许应用程序在等在阻塞造作完成过程中接收消息。
应用程序在等在阻塞造作完成过程中接收消息称为重入消息。如果应用程序在处理消息时不考虑可能的阻塞情境,就会带来很大的破坏。
5.2.4阻塞情境
阻塞操作没有完成时,不能进行其他的网络操作,这种情况被称为阻塞情境。在这期间,除了WSACancelBlockingCall()外,任何其他WinSock调用都将失败并返回WSAEINPROGRESS错误消息。可以通过以下星空避免这种阻塞情境带来的问题。
1. 正确应对WSAEINPROGRESS
WSAEINPROGRESS不应该对程序造成致命的威胁。对于每一个WinSock函数调用,都需要问一下自己,如果碰到这种错误怎么办。有时希望稍后再次尝试发起为成功的操作,有时希望跳过这种错误。至于如何做,完全取决于应用程序的设计,理想情况下是能够避免这个错误的。
2. 用为WSAIsBlocking()进行检测
WSAIsBlocking()函数是专门为监测阻塞情境而设计的。
BOOL PASCAL FAR WSAIsBlocking(void); // TRUE if blocking
WSAIsBlocking()函数没有任何输入参数,当前任务或线程就是隐含的输入参数。如果存在尚未完成的阻塞操作,函数会简单地返回一个TRUE,否则返回FALSE。当返回TRUE时,就知道要避免WinSock函数调用,因为可能产生WSAEINPROGRESS错误。
3. 保持应用程序的状态信息
为了应用程序能够避免WSAEINPROGRESS错误,同时也避免干扰其他操作的情境,另一种方法就是在应用程序中维持一个代表应用程序当前状态的变量。这种方法要求设置一个表示当前所执行的操作的变量,在发起新的操作前检查该变量的状态。
5.2.5撤销阻塞操作
撤销一个等待中的阻塞操作,我们可以使用Windows的定时器来实现超时控制,也可以使用WSACancelBlockingCall()函数来强行结束一个等待的阻塞操作。WSACancelBlockingCall()函数是所有阻塞操作的应急出口。
Int WSAAPI WSACancelBlockingCall(void); // 0 on success
      这个函数没有参数,任务或线程就是隐含的参数。这个函数撤销等待中的阻塞操作,若没有等待处理的操作则失败返回。失败时,调用WSACancelBlockingCall()函数返回SOCKET_ERROR错误,WSAGetLastError()返回WSAEINVAL错误。
1. 撤销作用滞后生效
注意:WSACancelBlockingCall()函数总是立即返回,并不等待处理中的阻塞函数撤销完成,所以阻塞操作在WSACancelBlockingCall()返回时仍然处于待决状态。只有当中断的函数以WSAINTR错误(“被中断的系统调用”)返回时,阻塞操作的撤销方才完成。有时候WSACancelBlockingCall()函数的调用和返回WSAEINTR错误的时间会很长。所以为了能够支持撤销操作,所有阻塞函数的调用必须考虑合理地应对WSAEINTER错误,由应用程序的设计决定什么是合理的处理。
2. 限制socket的继续使用
我们对某些socket调用的撤销操作可能会导致某个socket处于中间状态,可能损害一个数据流的完整性(一个已连接的TCP socket)。如果发生这种情况,其后的函数调用都将失败并返回WSAECONNABORTED错误。除了accept()和select()外,在阻塞撤销后还能保证正常工作的网络函数是closesocket()。
3. 撤销操作可能失败
    由于调用WSACancelBlockingCall()函数和撤销操作之间的延迟,待决的阻塞操作可能在撤销生效之前实际上已经完成了。这种情况下,函数返回值指示成功,而非预计之中的WSAEINTR失败。如果我们在撤销操作之前记录了状态值,即使撤销操作失败(即如果阻塞函数成功返回),仍可以让应用程序继续执行撤销操作;否则,若让应用程序像没有发生过撤销操作一样继续往下执行,就有可能产生潜在的灾难性的后果,从而可能需要再次发起撤销操作。
5.2.6阻塞操作中的超时
1. 自动超时
    connect()、send()、和gethostbyname()等函数会自动超时。函数connect()和send()的超时控制既影响阻塞操作,也影响非阻塞操作。但是,应用程序对这些函数的超时时间没有任何控制能力,由网络系统单独决定何时超时。
2. 用户可设置的超时
    如select()、closesocket()的函数允许应用程序决定超时值。
    3.应用程序超时
    如recv()、recvfrom()或accept()等函数能够永远阻塞下去。这些函数没有时间限制,所以当没有数据可接收时,recv()或者recvfrom()函数将一直不返回。
    4.TCP存活超时
    为流socket提供超时控制功能的另一种方法是设置setsockopt()选项SO_KEEPALIVE。这将是协议栈周期性地发送TCP“存活”(keep-alive)数据包,接收方必须将数据包反馈给发送方作为对接收的确认。如果没有收到反馈,则之后的任何I/O操作或者等待处理的I/O操作都将失败并返回WSAETIMEOUT错误。
5.2.7 无最少接收限制
      函数recv()或者recvfrom()中的输入参数len定义了输入数据缓存区的长度。实际上,len规定了recv()或者recvfrom()能够复制到输入缓存区的字节数的上限。
阻塞的recv()或者recvfrom()在网络系统想输入缓存中复制了任何字节的数据后就会返回,所以复制的数据不会多于len参数定义的数量,但却可能少于此数。
5.3 非阻塞模式
非阻塞式的函数都是立即返回的,无论操作是否完成。
调用WSAAsyncSelect()能够自动地使socket变为非阻塞的。但是,WSAAsyncSelect()等同于采用异步操作模式,这种模式下,与socket交互的方式和传统的非阻塞操作模式下是不同的。
调用设置了FIONBIO命令的ioctlsocket()函数可以显示地将socket设为非阻塞的。
5.4 异步模式
      异步函数及其描述
函数名 描述
WSAAsyncGetHostByAddr() 根据主机地址检索主机信息
WSAAsyncGetHostByName() 根据主机名检索主机信息
WSAAsyncGetProtoByName() 根据协议名检索协议号
WSAAsyncGetProtoByNumber() 根据协议号检索协议名
WSAAsyncGetServByName() 根据服务名检索服务信息
WSAAsyncGetServByPort() 根据端口号检索服务信息
WSAAsyncSelect() 请求WinSock DLL通知应用程序一个或多个“事件”,诸如连接完成、输出缓存区可用,以及数据到达。
      所有由WSAAsyncGetXByY()函数发起的异步操作,都可以用WSACancelAsyncRequest()函数来撤销,但这种方法不能够撤销WSAAsyncSelect()函数发起的异步操作。
int PASCAL FAR WSACancelAsyncRequest( HANDLE  hAsyncTaskHandle);
      参数hAsyncTaskHandle是由WSAAsyncGetXByY()调用返回的。
5.5 性能比较
      非阻塞模式是最快的,但是它开销最大,所以也是最不“友好的”。
异步操作能够很快地说传输数据并且同时保持“友好型”。
阻塞模式是“最友好的”,然而相对较慢。
0 0
原创粉丝点击