原文:http://www.developerfusion.com/article/28/introduction-to-tcpip/8/
当你开发Windows 套接字应用程序时,你会遇到的第一个问题就是,阻塞式和非阻塞式套接字的区别。当你在套接字上执行某一操作时,有可能不能马上得完成该操作并且返回到主程序。例如,当你在套接字上读取时数据时,直到数据被远程主机发送回来才能完成这个操作。如果这没有数据需要被读取时,又两种情况可能发生:函数等直到有数据写入套接字;或者马上返回并返回一个错误说明没有数据可读。
第一种方式称作阻塞式套接字。换言之,程序被阻塞了,直到读取的请求被满足。当远程端确实将数据写入套接字,读取操作完成并且恢复主程序。第二种情况被称作非阻塞式套接字,需要程序能够辨认出错误的情况并做出相应的适当的处理。典型非阻塞式套接字程序使用两种方法去发送和读取数据。第一种方法称作轮流检测(Polling),使用该方法程序会周期的从套接字上读取数据或者写入数据(典型的方式是使用计时器)。第二种方法,也是首选,这种方法称作“异步告知”。当有事件发生时,程序会被通知,程序会对事件做出相应。例如:当远程程序向套接字写入数据时,一个“读事件”会产生,这样程序会知道这时候可以从套接字中读取数据。
由于一些历史原因,默认的行为是使用阻塞的方式,操作不会返回直到完成。然而阻塞式套接字给Windows引入了一些 特殊的问题。对于16-位的应用程序,阻塞式函数会进入一个被叫做“消息循环”的循环处理中。由于信息是一直被处理中的,这意味着程序会进入消息循环不同的位置,阻塞操作会停在不同的堆栈区。例如,一个程序当按一下按钮时会去读取从套接字上数据。因为此时没有数据,因此程序会被阻塞,程序进入了消息循环中,此时用户按下一个不同的按钮,这件事件使得代码被继续执行,依次从套接字中取读取数据等等。
阻塞式套接字函数给32-位应用程序引入了另一个问题,因为阻塞函数会阻止调用线程去处理任何的发送给它的消息。由于许多应用程序都是单线程的,这可能导致应用程序没有反应。为了解决这个问题,Windows Sockets 设置了标准状态,即每一个执行的线程只能有一个显著的阻塞调用。这就意味着,无论什么时候,当16-位应用程序(上面的例子)有一个阻塞的函数已经存在时,当再次进入消息循环试图采取什么行动时会发生错误。在一个32-位程序中,创建一个“工作线程”去执行阻塞式套接字方法是一个普遍的使用的方法,尽管这样会增加应用程序的实现复杂度。
套接字扳手控制让使用非阻塞的套接字,通过出发事件这种方式变得容易。例如,一个读取事件会产生,当远程主机向套接字中写入数据,事件出发后会告诉应用程序这里有数据等待这被读入。
概括来说,这里有3种通用的方法去考虑使用阻塞或者非阻塞的方式套接字来建立应用程序:
-
使用阻塞的方式套接字(同步的I/O)。在这种模式中,程序不回继续执行直到套接字操作完成。16-位应用程序使用阻塞式套接字,允许应用程序在不同的点被调用;32-位应用程序将会停止对用户任何操作的响应。如果应用程序中有多个激活的控制在使用,这会导致复杂的交互(更难的调试)。
-
使用非阻塞的套接字(异步方式),这种方式允许应用程序响应事件。例如当远程系统向套接字写入数据时,读事件就会产生。应用程序可以读取套接字中的数据从而响应该事件,又或者返回些数据,这要根据接收数据的上下文。
-
使用两者的混合方式。
如果你决定使用非阻塞的套接字在你的应用程序中,你一定要记住,一定要检查每一个读取或者发送的操作的返回值,因为很有可能你不能将所有的指定的数据都发送或者都接收。当程序员认定一些数据时一定可以被读取或者发送时,此时常常会发生问题。在许多例子中,程序在本地局域网中开发或测试的结果实正确的,但是会在发布给用户后,当用户使用的很慢的网络连接(例如,拨号上网的网络)就会出现无征兆失败。通过检查这些操作的返回值,可以保证你的程序无论在什么样的网络配置的情况下都能够运行正常。