You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// lib/internal/streams/writable.js
function finish(stream, state) {
state.pendingcb--;
// TODO (ronag): Unify with needFinish.
if (state.errorEmitted || state.closeEmitted)
return;
state.finished = true;
const onfinishCallbacks = state[kOnFinished].splice(0);
for (let i = 0; i < onfinishCallbacks.length; i++) {
onfinishCallbacks[i]();
}
stream.emit('finish');
if (state.autoDestroy) {
// In case of duplex streams we need a way to detect
// if the readable side is ready for autoDestroy as well.
const rState = stream._readableState;
const autoDestroy = !rState || (
rState.autoDestroy &&
// We don't expect the readable to ever 'end'
// if readable is explicitly set to false.
(rState.endEmitted || rState.readable === false)
);
if (autoDestroy) {
stream.destroy();
}
}
}
3. 小结
本文主要讲了可写流基类 Writable 的实现以及 fs.WriteStream 可写流的实现。
The text was updated successfully, but these errors were encountered:
Table of Contents
1. 前言
stream 流是许多 nodejs 核心模块的基类, 在讲解它们之前还是要认真说一下 nodejs stream 的实现。其实在 【libuv 源码学习笔记】网络与流 中 BSD 套接字 中就开始提到 c 中的流, nodejs 的 c++ 代码的实现更多的是作为一个胶水层, 实际调用的 js 层面的 stream 实例的方法。
涉及的知识点
2. 可写流
可写流的例子包括:
所有的 Writable 流都实现了 stream.Writable 类定义的接口。
2.1. Writable
实现一个可写流的核心是继承 Writable, 并至少实现一个 _write 或者 _writev 方法。
options: 传入一些配置参数, 因为 Writable 是不能直接使用的, 你可以继承于 Writable 传入 options 实现自己的可写流
2.2. 可写流的实现之 myWritable
2.3. 可写流的实现之 fs.WriteStream
总体上和上一篇 【node 源码学习笔记】stream 可读流 类似, 其中的 _destroy, construct 参数就不在这篇重复讲了。
WriteStream 是继承于 Writable, 其中的 options 参数可以传入, 也可以在 WriteStream 中自己实现 options 需要的 _write, _writev, _construct, _final, _destroy 方法
2.3.1. _write
可以看见对于一个文件的可读流的 _write 方法, 当有数据传入时, 会把当前数据就是不断写入 fd, 每写入一次 this.pos += data.length 偏移量加上本次写入的数据的长度, 保证数据都被写入到了正确的位置。
2.3.2. _writev
与 _write 方法不同的是每次写的是一组数据(如 chunk[], _write 仅为一个 chunk), 其来源为内存中 state.buffered 的数据
doWrite 方法中可以看见如果同时实现了 _write 与 _writev 方法, 会调用 _writev 方法。
writev 在 c 中的使用如下, writev以顺序iov[0],iov[1]至iov[iovcnt-1]从缓冲区中聚集输出数据。writev返回输出的字节总数,通常,它应等于所有缓冲区长度之和。
在 writeOrBuffer 方法中可以看见, 满足如下条件数据讲会先写入内存 state.buffered 中
实现自己的可写流时一定要注意 writeOrBuffer 的返回值, 因为此时可能出现了积压的问题, 详见上一篇 【node 源码学习笔记】stream 可读流
从下面的 afterWrite 函数发现,在每次 write 后,如果当前流没有结束 & 没有摧毁 & 内存中的数据清空后才会触发 drain 事件,即自从积压问题出现后第一次释放出的可以继续开始流动的信号。其实也好理解,如果一出现积压,内存中的数据刚下降一点就触发 drain 事件的话,短时间内会不断触发积压机制。
2.3.3. _final
fs.WriteStream 没有实现该方法, 在 lib/internal/streams/writable.js 在 write, end 等方法后, 会在 finishMaybe > needFinish > prefinish 中调用 _final 方法。
从 state 的属性可知道只会被调用一次, 并且是 destroyed 之前。
从 callFinal 方法中发现, 实现的 _final 会传入一个 callback, 在 _final 方法的最后必须调用一次 callback, 因为该 callback 调用 finish 方法开始走接下来的结束流程。
总体看上去 _final 更像是一个结束前的勾子, 没有像 _destroy 直接关闭 fd 那么"沉重", 其实现该接口的流有 net 模块
这里 js 里面 Socket 对象实际操作的是 【libuv 源码学习笔记】网络与流 中提到到 accept 返回的一个连接的 acceptFd, 如下 _final 方法主要是调用了 shutdown 方法。
shutdown 追溯下去是的调用是在 libuv 中的流的 i/o 观察者回调函数 uv__stream_io 中, 如下当写入队列未空时调用 uv__drain 方法
uv__drain 主要是调用了 shutdown(2) - Linux man page 方法, 用于关闭部分全双工连接, 如当前传入 SHUT_WR 即关闭写端, 表示服务端降不会再发送数据。
以及实现 _final 接口的 【node 源码学习笔记】stream 双工流、转换流、eos、pipeline 中提到的 Transform 流,其 _final 方法是主要调用了 flush 方法将缓冲区中的数据强制写出
2.4. end 流结束
通常可写流会调用 end 方法表示流写入工作完成, 如 http server 的 res.end() 调用,end 方法可以传入数据进行最后一次的数据写入工作,后开始结束流程。如果你只有一份数据,其实也可仅调用一次 end 方法,即不用单独调用 write 方法
Writable.prototype.end 方法的结束流程主要是调用了如下的 finish 方法
3. 小结
本文主要讲了可写流基类 Writable 的实现以及 fs.WriteStream 可写流的实现。
The text was updated successfully, but these errors were encountered: