使用socket编程实现数据传输的过程,通常的默认设置假设套接字是阻塞的。
每个TCP套接字有一个发送缓冲区,当应用进程调用write操作时,内核从应用进程的缓冲区中复制数据到套接字的发送缓冲区。如果套接字的发送缓冲区无法容纳应用程序的所有数据,则应用程序会被挂起,内核不会从write系统调用中返回,直到应用进程缓冲区中所有数据都复制到套接字发送缓冲区。因此,TCP套接字的write调用返回仅仅表示可以重新使用应用进程的缓冲区,它并不表示对端的应用进程已经接收到了数据。
下面从发送和接收两方面说明阻塞模式和非阻塞模式的区别。
(1)发送操作:write、writev、send、sendto、sendmsg
对于一个TCP套接字,内核将从应用进程的缓冲区向该套接字的发送缓冲区复制数据。对于阻塞套接字,如果其发送缓冲区中没有空间,进程将挂起,直到有空间为止。
对于一个非阻塞TCP套接字,如果其发送缓冲区中根本没有空间,发送函数调用将立即返回一个EWOULDBLOCK错误。如果其发送缓冲区中有一些空间,返回值为内核复制到该缓冲区中的字节数。
UDP套接字不存在真正的发送缓冲区。内核只是复制应用进程数据并把它沿协议栈向下传送,以此加上UDP头部和IP头部。因此,对一个阻塞的UDP套接字,发送函数调用将不会因为TCP套接字一样的原因而阻塞,不过可能会因为其他原因而阻塞。
(2)接收操作:read、readv、recv、recvfrom、recvmsg
如果某个进程对一个阻塞的TCP套接字调用这些函数之一,而且该套接字的接收缓冲区中没有数据可读,该进程将被挂起,直到到达一些数据。TCP是字节流协议,只要到达一些数据,该进程就会被唤醒:这些数据既可能是单个字节,也可以是一个完整的TCP字节中的数据。
对于非阻塞的套接字,如果接收操作不能被满足(对于UDP套接字即有一个完整的数据报可读),相应的调用将立即返回一个EWOULDBLOCK错误。
UDP是数据报协议,如果一个阻塞的UDP套接字的接收缓冲区为空,对它调用接收函数的进程将被挂起,知道到达一个UDP数据报。