
如果一个服务端和一个客户端建立 TCP 连接后,服务端一直 sleep,然后客户端一直发送数据,会是什么现象?
要回答这个问题,首先我们需要知道 socket 缓冲区和 TCP 发送数据流程。
每个 socket 在创建成功后,内核都会自动分配两个缓冲区:输入缓冲区和输出缓冲区。
我们需要知道,write 和 send 并不会立即向网络中传输数据,而是先将数据写入到内核中的输出缓冲区,然后由 TCP 协议将数据从缓冲区发送到目的机器。
数据写入到缓冲区后,函数就可以返回成功,不管有没有发送到目的机器,也不管它们什么时间发送到目的机器,这些都是内核 TCP 协议负责的事情。
这里说的写入缓冲区,就是 socket 自带的缓冲区,它存在于内核当中,由内核维护。往往我们应用层也会维护一个缓冲区,这里要区别开。
read 和 recv 也是一样的道理,它们都是从输入缓冲区读取数据,而不是直接从网络读取数据。
把两端串起来,一次完整的数据传递流程是下面四步:
- 应用层调用
write/send将数据由应用层的缓冲区复制到内核 socket 的发送缓冲区。(工作中经常一端发送数据、日志也没任何报错,但是另一端却收不到,这就是因为write/send成功仅仅表示数据从应用层复制到内核 socket 缓冲区成功) - 内核协议栈将输出缓冲区的数据发送到对端主机,这个过程是不受应用程序控制的,由发送端内核协议栈完成。
- 数据到达接收端主机的内核输入缓冲区,由接收端的内核协议栈完成。
- 接收端的应用程序调用
read/recv函数,把数据从内核输入缓冲区复制到应用层的缓冲区。
服务端一直 sleep,客户端一直发数据,这个要分两种情况:
1:阻塞模式
阻塞模式下,就是先填满服务端的接收缓冲区,然后再填满客户端的发送缓冲区,此后,write/send 无法继续发送数据,然后阻塞在那里。
因为在阻塞模式下,write/send 发送数据时,首先会检查缓冲区,如果缓冲区的可用空间长度小于要发送的数据,那么将会被阻塞,直到缓冲区腾出足够的空间,才会唤醒 write/send。
2:非阻塞模式
非阻塞模式下,也会出现服务端接收缓冲区填满,然后客户端的发送缓冲区也被填满的情况。此后,write/send 会返回 -1,错误码为 EWOULDBLOCK(Linux 上与 EAGAIN 等价)。
因为这样,客户端必须实现某种形式的轮询或事件驱动机制,如使用 select()、poll() 或 epoll 等系统调用,来检查何时可以再次尝试写入数据。