NodeJS从内核TCP缓冲区读取数据的机制及相关问题咨询
背景知识
当TCP连接建立且接收端收到数据包时,会执行两项核心操作:
- 向发送端发送确认(acknowledge),让发送端可以释放其发送内核缓冲区中的内存;
- 将数据存入接收端内核缓冲区(TCP接收缓冲区),Node.js必须读取此处的数据,才能将其从接收端内核内存中释放/刷新,转移到Node.js内存/缓冲区。
核心问题
问题1
Node.js是如何从接收端内核缓冲区读取数据的?这些数据最终能通过response.body.on("readable", ()=>{response.body.read()})这类可读流获取,数据是怎么流转到这里的?是什么触发了从接收端内核内存到Node.js可读流(response.body)的数据推送?
问题2
接收端内核缓冲区或负责推送的组件,会等待Node.js可读流(response.body)的drain事件再推送数据,还是会无条件推送?
问题解答
针对问题1:数据流转与触发机制
Node.js的底层基于libuv(跨平台异步I/O库)实现网络操作,具体流程如下:
- 当接收端内核缓冲区收到数据后,libuv会通过操作系统的I/O通知机制(比如Linux下的epoll、Windows下的IOCP)感知到缓冲区有可读数据;
- libuv触发对应的回调函数,将内核缓冲区中的数据拷贝到Node.js的内部缓冲区(用户态内存);
- Node.js的可读流(比如
response.body,本质是http.IncomingMessage实例,继承自stream.Readable)会监听内部缓冲区的变化,当数据达到可读阈值时,触发readable事件; - 调用
response.body.read()时,就是从Node.js的内部缓冲区读取数据,当内部缓冲区数据不足时,libuv会再次触发从内核缓冲区到用户态的拷贝。
简单来说,操作系统的I/O事件通知是触发数据从内核到Node.js可读流推送的核心,整个过程是异步非阻塞的,由libuv接管底层操作。
针对问题2:推送与drain事件的关系
接收端内核缓冲区和负责推送的组件不会等待Node.js可读流的drain事件,原因如下:
- 内核缓冲区和Node.js的用户态缓冲区是分层的:内核缓冲区的写入由TCP协议栈控制,只要发送端的TCP窗口允许,就会持续接收数据并存入内核缓冲区;
- 只有当Node.js的用户态内部缓冲区被写满时,才会停止从内核缓冲区拷贝数据,此时如果内核缓冲区持续被写入,可能会触发TCP的窗口收缩(告诉发送端降低发送速率);
drain事件是Node.js可读流的上层机制,仅作用于用户态的流数据处理,和内核缓冲区的推送逻辑无关。当用户态缓冲区有空闲后,libuv会自动恢复从内核缓冲区拷贝数据,无需等待drain事件。
完整数据流转流程
发送端Node.js应用 -> 发送端TCP缓冲区 -> 接收端TCP缓冲区 -> 接收端Node.js应用的完整流程:
- 发送端Node.js应用:调用
write()等API发送数据,数据先写入Node.js的用户态发送缓冲区; - 发送端TCP缓冲区:libuv将用户态缓冲区的数据拷贝到内核发送缓冲区,TCP协议栈负责将数据分片、加TCP头后通过网络发送;
- 网络传输:数据包通过网络到达接收端,接收端TCP协议栈校验数据包合法性;
- 接收端TCP缓冲区:合法数据包被存入内核接收缓冲区,同时发送ACK给发送端,让发送端释放对应内核缓冲区内存;
- 接收端Node.js应用:libuv通过I/O通知感知内核缓冲区有数据,将数据拷贝到Node.js用户态内部缓冲区,触发可读流的
readable事件,应用通过read()读取数据到自身内存,完成整个流转。
内容的提问来源于stack exchange,提问作者tcp_guides708
相关产品推荐
相关产品推荐

