每个TCP套接口都有一个发送缓冲区,我们可以用SO_SNDBUF套接口选项来改变这个缓冲区的大小。当应用程序调用write时,内核从应用进程的缓冲区中拷贝所有数据到套接口的发送缓冲区。如果套接口发送缓冲区容不下应用程序所有的程序(或者应用程序的缓冲区大于套接口发送缓冲区,或者是套接口发送缓冲区还有其他数据),应用进程将被挂起,这里假设write是阻塞的。内核将不从write系统调用返回,直到将应用进程缓冲区的所有数据都拷贝到套接口发送缓冲区。因此从写一个TCP套接口的write调用成功返回仅仅代表发送的数据到达应用进程的缓冲区。它并不告诉我们对端TCP或者应用进程已经接收到数据。
UDP套接口有发送缓冲区大小(SO_SNDBUF修改),不过它仅仅是写到套接口的UDP数据报的大小上限,注意,实际上UDP发送缓冲区并不存在。如果一个应用程序写一个大于套接口发送缓冲区大小的数据报,内核将返回EMSGSIZE错误。既然UDP不可靠,它不必保存应用程序的数据拷贝,因此无需真正的发送缓冲区(应用进程的数据在沿协议栈往下传递,以某种形式拷贝到内核缓冲区,然而数据链路层在送出数据之后将丢弃该拷贝)。
从UDP套接口write成功返回仅仅表示用户写入的数据报或者所有片段已经加入到数据链路层的输出队列。如果该队列没有足够的空间存放该数据包或者它的某个片段,内核通常返回给应用进程一个ENOBUFS错误。
TCP和UDP都拥有套接口接收缓冲区。TCP套接口接收缓冲区不可能溢出,因为TCP具有流量控制,然而对于UCP来说,当接收到的数据报装不进套接口接收缓冲区时,该数据报就丢弃。UDP是没有流量控制的:较快的发送端可以很容易淹没较慢的接收端,导致接收端的UDP丢弃数据报。
注意,正是由于UDP没有流量控制,所以其接收缓冲区满后就开始丢弃新到来的数据包,测试如下,UDP服务端程序实时打印出接收到的数据包个数:
服务端显示收到了94个UDP数据包,也就是说丢失了6个,如果一次发送的UDP数据量更大的话,丢包现象更严重。
参考资料
1、《Unix网络编程 卷一 套接字编程》UDP章节