阻塞模式服务器
Windows套接字在阻塞和非阻塞两种模式下执行I/O操做。在阻塞模式下,在I/O操做完成前,执行的操做函数一直等候而不会当即返回,该 函数所在的线程会阻塞在这里。相反,在非阻塞模式下,套接字函数会当即返回,而无论I/O是否完成,该函数所在的线程会继续运行。网络
在阻塞模式的套接字上,调用任何一个Windows Sockets API都会耗费不肯定的等待时间。图所示,在调用recv()函数时,发生在内核中等待数据和复制数据的过程。异步
当调用recv()函数时,系统首先查是否有准备好的数据。若是数据没有准备好,那么系统就处于等待状态。当数据准备好后,将数据从系统缓冲区复制 到用户空间,而后该函数返回。在套接应用程序中,当调用recv()函数时,未必用户空间就已经存在数据,那么此时recv()函数就会处于等待状态。socket
Windows套接字程序使用“生产者-消费者”模式来解决上述问题。在程序中,“生产者”读入数据,“消费者”根据需求对读入数据进行处理。一般“生产 者”和“消费者”存在于两个线程中,当“生产者”完成读入数据时,使用线程同步机制,例如设置一个事件通知“消费者”,“消费者”接收到这个事件后对读入 的数据进行处理。函数
当使用socket()函数和WSASocket()函数建立套接字时,默认的套接字都是阻塞的。这意味着当调用Windows Sockets API不能当即完成时,线程处于等待状态,直到操做完成。spa
并非全部Windows Sockets API以阻塞套接字为参数调用都会发生阻塞。例如,以阻塞模式的套接字为参数调用bind()、listen()函数时,函数会当即返回。将可能阻塞套接字的Windows Sockets API调用分为如下四种:线程
1.输入操做事件
recv()、recvfrom()、WSARecv()和WSARecvfrom()函数。以阻塞套接字为参数调用该函数接收数据。若是此时套接字缓冲区内没有数据可读,则调用线程在数据到来前一直睡眠。资源
2.输出操做开发
send()、sendto()、WSASend()和WSASendto()函数。以阻塞套接字为参数调用该函数发送数据。若是套接字缓冲区没有可用空间,线程会一直睡眠,直到有空间。
3.接受链接
accept()和WSAAcept()函数。以阻塞套接字为参数调用该函数,等待接受对方的链接请求。若是此时没有链接请求,线程就会进入睡眠状态。
4.外出链接
connect()和WSAConnect()函数。对于TCP链接,客户端以阻塞套接字为参数,调用该函数向服务器发起链接。该函数在收到服务器的应答前,不会返回。这意味着TCP链接总会等待至少到服务器的一次往返时间。
使用阻塞模式的套接字,开发网络程序比较简单,容易实现。当但愿可以当即发送和接收数据,且处理的套接字数量比较少的状况下,使用阻塞模式来开发网络程序比较合适。
阻塞模式套接字的不足表现为,在大量创建好的套接字线程之间进行通讯时比较困难。当使用“生产者-消费者”模型开发网络程序时,为每一个套接字都分别 分配一个读线程、一个处理数据线程和一个用于同步的事件,那么这样无疑加大系统的开销。其最大的缺点是当但愿同时处理大量套接字时,将无从下手,其扩展性 不好。
非阻塞模式
把套接字设置为非阻塞模式,即通知系统内核:在调用Windows Sockets API时,不要让线程睡眠,而应该让函数当即返回。在返回时,该函数返回一个错误代码。图所示,一个非阻塞模式套接字屡次调用recv()函数的过程。前 三次调用recv()函数时,内核数据尚未准备好。所以,该函数当即返回WSAEWOULDBLOCK错误代码。第四次调用recv()函数时,数据已 经准备好,被复制到应用程序的缓冲区中,recv()函数返回成功指示,应用程序开始处理数据。
当使用socket()函数和WSASocket()函数建立套接字时,默认都是阻塞的。在建立套接字以后,经过调用ioctlsocket()函数,将该套接字设置为非阻塞模式。Linux下的函数是:fcntl().
套接字设置为非阻塞模式后,在调用Windows Sockets API函数时,调用函数会当即返回。大多数状况下,这些函数调用都会调用“失败”,并返回WSAEWOULDBLOCK错误代码。说明请求的操做在调用期 间内没有时间完成。一般,应用程序须要重复调用该函数,直到得到成功返回代码。
须要说明的是并不是全部的Windows Sockets API在非阻塞模式下调用,都会返回WSAEWOULDBLOCK错误。例如,以非阻塞模式的套接字为参数调用bind()函数时,就不会返回该错误代 码。固然,在调用WSAStartup()函数时更不会返回该错误代码,由于该函数是应用程序第一调用的函数,固然不会返回这样的错误代码。
要将套接字设置为非阻塞模式,除了使用ioctlsocket()函数以外,还可使用WSAAsyncselect()和WSAEventselect()函数。当调用该函数时,套接字会自动地设置为非阻塞方式。
因为使用非阻塞套接字在调用函数时,会常常返回WSAEWOULDBLOCK错误。因此在任什么时候候,都应仔细检查返回代码并做好对“失败”的准 备。应用程序接二连三地调用这个函数,直到它返回成功指示为止。上面的程序清单中,在While循环体内不断地调用recv()函数,以读入1024个字 节的数据。这种作法很浪费系统资源。
要完成这样的操做,有人使用MSG_PEEK标志调用recv()函数查看缓冲区中是否有数据可读。一样,这种方法也很差。由于该作法对系统形成的开销是 很大的,而且应用程序至少要调用recv()函数两次,才能实际地读入数据。较好的作法是,使用套接字的“I/O模型”来判断非阻塞套接字是否可读可写。
非阻塞模式套接字与阻塞模式套接字相比,不容易使用。使用非阻塞模式套接字,须要编写更多的代码,以便在每一个Windows Sockets API函数调用中,对收到的WSAEWOULDBLOCK错误进行处理。所以,非阻塞套接字便显得有些难于使用。
可是,非阻塞套接字在控制创建的多个链接,在数据的收发量不均,时间不定时,明显具备优点。这种套接字在使用上存在必定难度,但只要排除了这些困难,它在 功能上仍是很是强大的。一般状况下,可考虑使用套接字的“I/O模型”,它有助于应用程序经过异步方式,同时对一个或多个套接字的通讯加以管理。