0. 需求
前两天 Showman 遇到一个需求:
- 我们需要在服务器端录制视频
- 录制视频的过程主要由 node.js 控制,借助 puppeteer 操作浏览器
- 但是也会需要执行一些 shell 命令,此时为安全考虑,我们会启动一个封闭的临时环境给用户执行
- 这些封闭环境是用户进程间共用的,不会随时启动随时销毁
- 所以 node.js 就需要在其它环境里执行一些操作,返回内容,等待执行完毕后再继续下面的
于是我的同事就让我用 fifo。
1. 什么是 fifo
我以前没有用过 fifo,所以搜索了一下。
FIFO 特殊文件(同具名管道)与管道类似,只是可以用访问文件系统的方式来访问它。它可以被多个进程同时打开和读写。当进程通过 FIFO 交换数据时,内核将直接在内部交换数据,而不会写入到文件系统中。因此,FIFO 特殊文件在文件系统中没有内容;文件系统的入口(即文件)只是作为引用方式,让各进程能够使用文件名来访问管道。
原文:https://man7.org/linux/man-pages/man7/fifo.7.html
管道大家应该都知道,把 A 进程的输出直接输入到 B 进程里,加快处理速度。fifo 与管道的差别就是 fifo 可以通过文件路径直接访问,用起来更简单。
2. 在命令行里使用 fifo
创建 fifo,使用 mkfifo 命令:
mkfifo xxx.fifo
写入内容到 fifo:
echo "something" > xxx.fifo
读取 fifo:
cat xxx.fifo
因为 fifo 是管道,内容直接走内核,所以实际上硬盘上不会存储任何内容。如果我们在写入之后再 cat
fifo,就不会得到任何内容。
3. 在 node.js 里使用 fifo
在 node.js 里使用 fifo 需要用 fs.open 与 net.Socket。因为我需要在执行完毕后继续下一步,所以进行了 Promise 封装:
try {
// 为避免执行时间过长导致进程超时,不断输出些内容
const interval = setInterval(() => {
log('termlang is processing...');
}, 3E4);
await new Promise((resolve, reject) => {
// 打开名为 $basename-$lineno.sp.fifo 的管道
open('./$basename-$lineno.sp.fifo', constants.O_RDONLY | constants.O_NONBLOCK, (err, fd) => {
if (err) {
clearInterval(interval);
reject(err);
}
const pipe = new Socket({fd});
pipe.on('data', data => {
data = data.toString();
// 以输出内容包括 finished 或 errored 为结束标记
if (/(finished|errored)/.test(data)) {
resolve(data);
}
});
});
});
clearInterval(interval);
} catch (err) {
log(err);
}
4. 总结
作为半路出家的前端,我对系统、对 Linux 一直缺乏了解。所以类似管道这种东西,我一直也不太熟悉,这次算学会了一个新技能,记录分享一下。