单文件多ReadStream遇EBADF错误,如何维持文件描述符开启
问题
我需要处理一个包含头部信息与多段二进制数据的大文件,其中头部信息定义了各二进制数据段的偏移量和长度。我计划采用以下处理流程:
- 使用
fs.openSync()获取文件描述符 - 创建
ReadStream读取头部信息(以获取二进制数据段的偏移量),该流配置autoClose: false - 关闭该“头部”
ReadStream - 创建多个
ReadStream分别读取各二进制数据段,每个流设置独立的start和end参数,且均配置autoClose: false - 所有处理完成后手动关闭文件描述符
但在测试该方案时,关闭头部流后创建第二个流时触发了如下错误:Uncaught Error Error: EBADF: bad file descriptor, read
这表明文件描述符已无法用于读取操作,尽管所有流都设置了autoClose: false。
请问如何确保文件描述符保持开启状态,直到手动执行关闭操作?
测试代码如下:
(async function () { // open the file for reading const fd = fs.openSync("c:\\some\\large\\file.txt", "r"); // initial stream that will extract the header info const initStream = fs.createReadStream(null, { fd, autoClose: false, }); // header info data const headerContent = await extractHeaderContent(initStream); // for test purpose // try and extract the header again const testSecondStream1 = fs.createReadStream(null, { fd, autoClose: false, }); const testHeaderContent = await extractHeaderContent(initStream); fs.closeSync(fd); })(); // stream the data until the header data is retrieved async function extractHeaderContent(initStream) { return new Promise((resolve, reject) => { let content = ""; initStream.on("data", (chunk) => { if (chunk.indexOf("EndHeader") > -1) { content += chunk.toString(); let d = content.split("EndHeader")[0] + "EndHeader"; // once the header info is extracted - close the stream initStream.close(() => resolve(d)); } content += chunk.toString(); }); }); }
解决方案
问题根源
核心问题在于:即便设置了autoClose: false,调用ReadStream.close()方法时,Node.js仍会销毁关联的文件描述符。这是因为当你传入自定义fd时,流默认会认为自己拥有该fd的管理权,autoClose: false仅阻止流在正常结束/出错时自动关闭fd,但close()方法本身的逻辑就是关闭fd并销毁流。
解决步骤
要完全掌控fd的生命周期,需同时配置两个参数,并调整流的终止方式:
- 给所有基于自定义fd创建的
ReadStream同时添加autoClose: false和emitClose: false配置autoClose: false:阻止流在正常结束/出错时自动关闭fdemitClose: false:阻止流在销毁时触发fd的关闭操作
- 不要调用
stream.close(),改用stream.destroy()终止流,避免触发fd关闭 - 修正测试代码中的逻辑错误:第二次提取头部时要传入新创建的流,而非已终止的旧流
修正后的测试代码
const fs = require('fs'); (async function () { // 打开文件获取fd const fd = fs.openSync("c:\\some\\large\\file.txt", "r"); // 同时配置autoClose和emitClose为false const initStream = fs.createReadStream(null, { fd, autoClose: false, emitClose: false }); const headerContent = await extractHeaderContent(initStream); console.log('首次提取头部:', headerContent); // 第二个流同样配置两个false const testSecondStream1 = fs.createReadStream(null, { fd, autoClose: false, emitClose: false }); // 传入新创建的流,而非旧的initStream const testHeaderContent = await extractHeaderContent(testSecondStream1); console.log('二次提取头部:', testHeaderContent); // 所有流处理完成后手动关闭fd fs.closeSync(fd); })(); async function extractHeaderContent(stream) { return new Promise((resolve, reject) => { let content = ""; stream.on("data", (chunk) => { content += chunk.toString(); // 检查是否已读取到头部结束标记 if (content.includes("EndHeader")) { const header = content.split("EndHeader")[0] + "EndHeader"; // 销毁流但不关闭fd stream.destroy(); resolve(header); } }); // 处理流读取错误 stream.on("error", (err) => { reject(err); }); }); }
额外说明
- 所有基于同一个fd创建的流,都必须保持
autoClose: false和emitClose: false的配置,否则任意一个流的关闭操作都会导致fd失效 - 务必在所有流处理完成后手动调用
fs.closeSync()或fs.close()关闭fd,避免资源泄漏
内容的提问来源于stack exchange,提问作者Stefan Stoychev
相关产品推荐
相关产品推荐

