Node.js原始碼解析-Readable實現
歡迎來我的部落格閱讀:《Node.js原始碼解析-Readable實現》
想要了解 Readable 的實現,最好的方法是順著 Readable 的 Birth-Death 走一遍
Base
在瞭解 Readable 的 Birth-Death 之前,先看看 Readable 的建構函式
// lib/_stream_readable.js
function Readable(options) {
if (!(this instanceof Readable))
return new Readable(options);
// Readable 流的狀態集
this._readableState = new ReadableState(options, this);
// legacy
this.readable = true;
if (options) {
if (typeof options.read === `function`)
// 真實資料來源,Readable.prototyoe._read() 函式會丟擲異常,因此必須有options.read
this._read = options.read;
if (typeof options.destroy === `function`)
this._destroy = options.destroy;
}
Stream.call(this);
}
function ReadableState(options, stream) {
options = options || {};
// object 模式標識
this.objectMode = !!options.objectMode;
if (stream instanceof Stream.Duplex)
this.objectMode = this.objectMode || !!options.readableObjectMode;
var hwm = options.highWaterMark;
var defaultHwm = this.objectMode ? 16 : 16 * 1024;
this.highWaterMark = (hwm || hwm === 0) ? hwm : defaultHwm;
// highWaterMark 高水位標識
// 內部快取高於 highWaterMark 時,會停止呼叫 _read() 獲取資料
// 預設 16k
this.highWaterMark = Math.floor(this.highWaterMark);
// Readable 流內部緩衝池,是一個 buffer 連結串列
// 之所以不用陣列實現,是因為連結串列增刪頭尾元素更快
this.buffer = new BufferList();
// 快取大小
this.length = 0;
// pipe 的流
this.pipes = null;
this.pipesCount = 0;
// flow 模式標識
this.flowing = null;
// Readable 狀態標識,為 true 表示資料來源已讀取完畢
// 此時 Readable 中可能還有資料,不能再向緩衝池中 push() 資料
this.ended = false;
// Readable 狀態標識,為 true 表示 end 事件已觸發
// 此時 Readable 中資料已讀取完畢,不能再向緩衝池中 push() 或 unshift() 資料
this.endEmitted = false;
// Readable 狀態標識,為 true 表示正在呼叫 _read() 讀取資料
this.reading = false;
this.sync = true;
// 標識需要觸發 readable 事件
this.needReadable = false;
// 標識已觸發 readable 事件
this.emittedReadable = false;
this.readableListening = false;
this.resumeScheduled = false;
this.destroyed = false;
this.defaultEncoding = options.defaultEncoding || `utf8`;
this.awaitDrain = 0;
this.readingMore = false;
// 解碼器
this.decoder = null;
this.encoding = null;
if (options.encoding) {
if (!StringDecoder)
StringDecoder = require(`string_decoder`).StringDecoder;
this.decoder = new StringDecoder(options.encoding);
this.encoding = options.encoding;
}
}
在 Readable 的建構函式中,可通過 options 傳入引數,其中 options.read
函式是必需的
readable._readableState
中儲存了 Readable 的各種狀態與屬性
Birth-Death
在這裡將 Readable 的 Birth-Death 分為五個狀態:
表中為
this._readableSate
的屬性
-
start: 初始狀態,Readable 剛剛被建立,還未呼叫
readable.read()
|length|reading|ended|endEmitted|
|–|–|–|–|
|0|false|false|false|
-
reading: 代表正在從資料來源中讀取資料,此時快取大小
this._readableSate.length
小於highWaterMark
,應讀取資料使快取達到highWaterMark
|length|reading|ended|endEmitted|
|–|–|–|–|
|< highWaterMark|true|false|false|
-
read: Readable 從資料來源讀取資料後的相對穩定狀態
|length|reading|ended|endEmitted|
|–|–|–|–|
|>= highWaterMark|false|false|false|
-
ended: 資料已經全部讀取完成(
push(null)
),此時push(chunk)
會報stream.push() after EOF
錯誤
|length|reading|ended|endEmitted|
|–|–|–|–|
|>= 0|false|true|false|
-
endEmitted: end 事件觸發完成,此時
unshift(chunk)
會報stream.unshift() after end event
錯誤
|length|reading|ended|endEmitted|
|–|–|–|–|
|0|false|true|true|
它們之間的關係如下:
1 4 5
start ==> reading ==> ended ==> endEmitted
|| /
2 / || 3
read
1. start ==> reading
start 狀態變為 reading 狀態,發生在第一次呼叫 read()
時
// lib/_stream_readable.js
Readable.prototype.read = function(n) {
debug(`read`, n);
n = parseInt(n, 10);
var state = this._readableState;
var nOrig = n;
if (n !== 0)
state.emittedReadable = false;
// 呼叫 read(0)時,如果快取大於 highWaterMark 則直接觸發 readable 事件
if (n === 0 &&
state.needReadable &&
(state.length >= state.highWaterMark || state.ended)) {
debug(`read: emitReadable`, state.length, state.ended);
if (state.length === 0 && state.ended)
endReadable(this);
else
emitReadable(this);
return null;
}
// 計算可讀資料量
// n = NaN ==> 讀取全部
// n <= state.length ==> 讀取 n
// n > state.length ==> 讀取 0,並使 Readable 從資料來源讀取資料
//
// n > state.highWaterMark ==> 重新計算 highWaterMark,大小是大於 n 的最小 2^x
n = howMuchToRead(n, state);
// 當 Readable 已經讀完時,呼叫 endReadable() ,結束 Readable
if (n === 0 && state.ended) {
if (state.length === 0)
endReadable(this);
return null;
}
// 判斷是否應該從資料來源讀取資料
// BEGIN
var doRead = state.needReadable;
debug(`need readable`, doRead);
if (state.length === 0 || state.length - n < state.highWaterMark) {
doRead = true;
debug(`length less than watermark`, doRead);
}
// END
if (state.ended || state.reading) {
// 對於 ended 或 reading 狀態的 Readable 是不需要讀取資料的
doRead = false;
debug(`reading or ended`, doRead);
} else if (doRead) {
// 讀取資料
debug(`do read`);
state.reading = true;
state.sync = true;
if (state.length === 0)
state.needReadable = true;
// 從資料來源讀取資料,可能是非同步,也可能是同步
this._read(state.highWaterMark);
state.sync = false;
// 因為 _read() 函式可能是非同步的,也可能是同步的
// 在同步情況下,需要重新確認可讀長度
if (!state.reading)
n = howMuchToRead(nOrig, state);
}
// 獲取資料
var ret;
if (n > 0) ret = fromList(n, state); // 從緩衝池中讀取資料
else ret = null;
if (ret === null) {
state.needReadable = true;
n = 0;
} else {
state.length -= n;
}
// ...
if (ret !== null)
this.emit(`data`, ret);
return ret;
};
// 必須實現的方法
Readable.prototype._read = function(n) {
this.emit(`error`, new Error(`_read() is not implemented`));
};
// 計算可讀長度
function howMuchToRead(n, state) {
if (n <= 0 || (state.length === 0 && state.ended))
return 0;
if (state.objectMode)
return 1;
if (n !== n) { // NaN
if (state.flowing && state.length)
return state.buffer.head.data.length;
else
return state.length;
}
if (n > state.highWaterMark)
// 當需要資料大於 highWaterMark 時,調整 highWaterMark 大小到大於 n 的最小 2^x
state.highWaterMark = computeNewHighWaterMark(n);
if (n <= state.length)
return n;
// 緩衝池中資料不夠
if (!state.ended) {
state.needReadable = true;
return 0;
}
return state.length;
}
呼叫 read()
後,如果緩衝池中資料不夠或讀取後低於 highWaterMark,則呼叫 _read()
來讀取更多的資料,否則直接返回讀取的資料
當期望資料量大於 highWaterMark 時,重新計算 highWaterMark,大小是大於期望資料量的最小 2^x
2. reading ==> read
呼叫 _read()
後,會非同步或同步地將呼叫 push(chunk)
,將資料放入緩衝池,並使 Readable 從 reading 狀態變為 read 狀態
// lib/_stream_readable.js
Readable.prototype.push = function(chunk, encoding) {
var state = this._readableState;
var skipChunkCheck;
if (!state.objectMode) {
if (typeof chunk === `string`) {
encoding = encoding || state.defaultEncoding;
// 如果指定編碼與 Readable 編碼不同,則將 chunk 使用指定編碼解碼為 Buffer
if (encoding !== state.encoding) {
chunk = Buffer.from(chunk, encoding);
encoding = ``;
}
// string 不需要檢查
skipChunkCheck = true;
}
} else {
// object mode 的 Readable 也不需要檢查
skipChunkCheck = true;
}
return readableAddChunk(this, chunk, encoding, false, skipChunkCheck);
};
function readableAddChunk(stream, chunk, encoding, addToFront, skipChunkCheck) {
var state = stream._readableState;
if (chunk === null) { // 結束訊號
state.reading = false;
onEofChunk(stream, state);
} else {
var er;
if (!skipChunkCheck) // 檢查 chunk 格式
er = chunkInvalid(state, chunk);
if (er) {
stream.emit(`error`, er);
} else if (state.objectMode || chunk && chunk.length > 0) {
if (typeof chunk !== `string` &&
Object.getPrototypeOf(chunk) !== Buffer.prototype &&
!state.objectMode) {
chunk = Stream._uint8ArrayToBuffer(chunk);
}
if (addToFront) { // unshift() 的 hook
if (state.endEmitted)
stream.emit(`error`, new Error(`stream.unshift() after end event`));
else
addChunk(stream, state, chunk, true); // 將資料新增到緩衝池中
} else if (state.ended) {
stream.emit(`error`, new Error(`stream.push() after EOF`));
} else {
state.reading = false;
if (state.decoder && !encoding) {
chunk = state.decoder.write(chunk);
if (state.objectMode || chunk.length !== 0)
addChunk(stream, state, chunk, false); // 將資料新增到緩衝池中
else
maybeReadMore(stream, state); // 會在 addChunk() 函式內部呼叫
} else {
addChunk(stream, state, chunk, false); // 將資料新增到緩衝池中
}
}
} else if (!addToFront) {
state.reading = false;
}
}
return needMoreData(state);
// return !state.ended && 資料來源還有資料
// (state.needReadable || 需要更多資料
// state.length < state.highWaterMark || 快取小於 highWaterMark
// state.length === 0)
}
function addChunk(stream, state, chunk, addToFront) {
if (state.flowing && state.length === 0 && !state.sync) {
// 對於 flow 模式的 Readable,直接觸發 data 事件,並繼續讀取資料就行
stream.emit(`data`, chunk);
stream.read(0);
} else {
state.length += state.objectMode ? 1 : chunk.length;
if (addToFront)
state.buffer.unshift(chunk);
else
state.buffer.push(chunk);
if (state.needReadable)
emitReadable(stream);
}
// 在允許的情況下,讀取資料直到 highWaterMark
maybeReadMore(stream, state);
}
呼叫 push(chunk)
時,會將 chunk 放入緩衝池內,並改變 Readable 的狀態。如果 Readable 處於 ended 狀態,會報 stream.push() after EOF
錯誤
如果快取小於 highWaterMark,返回 true,意味著需要寫入更多的資料
3. read ==> reading
從 read 到 reading 狀態,意味著需要讀取更多的資料,即快取小於 highWaterMark
快取與 highWaterMark 的關係可以根據 push(chunk)
的返回值來判斷,但是需要使用者手動處理。因此,為了方便使用,addChunk()
函式會自動呼叫 maybeReadMore()
來非同步讀取資料。這樣,即使單次 _read()
無法達到 highWaterMark,也可以通過多次非同步讀取,使資料流動起來
// lib/_stream_readable.js
function maybeReadMore(stream, state) {
if (!state.readingMore) {
state.readingMore = true;
process.nextTick(maybeReadMore_, stream, state);
}
}
function maybeReadMore_(stream, state) {
var len = state.length;
while (!state.reading && !state.flowing && !state.ended &&
state.length < state.highWaterMark) {
debug(`maybeReadMore read 0`);
stream.read(0);
if (len === state.length) // 取不到資料就放棄
break;
else
len = state.length;
}
state.readingMore = false;
}
在 maybeReadMore()
函式內,通過非同步讀取資料,直到 highWaterMark
那麼為什麼是非同步讀取資料呢?
因為,在 _read()
函式內,可能不止一次呼叫 push(chunk)
如果是同步,push(chunk)
後,因為沒有達到 highWaterMark,會繼續呼叫 read(0)
,發生第二次 _read()
。第二次 _read()
也可能導致第三次 _read()
,直到 highWaterMark
待整個呼叫完畢後,緩衝池內會有 highWaterMark * n( _read()
內呼叫 push(chunk)
次數 )的資料,而這與 highWaterMark 的設計是不符的
如果是非同步,則可以等 _read()
執行完畢後,在 process.nextTick()
內再次呼叫 _read()
讀取資料,不會發生上面的問題
4. reading ==> ended
當資料來源讀取完畢時,需要呼叫 push(null)
來通知 Rreadable 資料來源已經讀取完畢。push(null)
函式內部會呼叫 onEofChunk()
// lib/_stream_readable.js
function onEofChunk(stream, state) {
if (state.ended) return;
if (state.decoder) {
var chunk = state.decoder.end();
if (chunk && chunk.length) {
state.buffer.push(chunk);
state.length += state.objectMode ? 1 : chunk.length;
}
}
state.ended = true;
// 觸發 readable 事件,通知監聽者來處理剩餘資料
emitReadable(stream);
}
onEofChunk()
函式將 readable 標記為 ended 狀態後,禁止再向緩衝池內 push 資料。此時,緩衝池內可能還有資料
5. ended ==> endEmitted
ended 狀態的 Readable 內可能還有資料。因此,當資料全部被讀取後,需要呼叫 endReadable()
來結束 Readable
// lib/_stream_readable.js
function endReadable(stream) {
var state = stream._readableState;
// state.length 一定是 0
if (state.length > 0)
throw new Error(`"endReadable()" called on non-empty stream`);
if (!state.endEmitted) {
state.ended = true;
process.nextTick(endReadableNT, state, stream);
}
}
function endReadableNT(state, stream) {
// 防止中間呼叫 unshift(chunk),向緩衝池中放入資料
if (!state.endEmitted && state.length === 0) {
state.endEmitted = true;
stream.readable = false;
stream.emit(`end`);
}
}
呼叫 endReadable()
時,緩衝池一定為空。整個呼叫完成後,觸發 end 事件,Readable 將不能再讀取或寫入( push()
/ unshift()
)資料
End
到這裡,已經走完了 Readable 的整個 Birth-Death 過程
整個過程就如下面這個圖:
1 4 5
start ==> reading ==> ended ==> endEmitted
|| /
2 / || 3
read
1. read()
2. push(chunk)
3. maybeReadMore() ==> read(0)
4. push(null)
5. endReadable()
根據這個圖還有程式碼,在腦袋裡面,把 Readable 的模型執行一遍,就能瞭解它的實現了
參考: