第八十期:Node中的streams流(双工流和I/O解耦)

298 阅读4分钟

这里记录工作中遇到的技术点,以及自己对生活的一些思考,周三或周五发布。

封面图

Node中的streams流

streams流是Node中的最好的特性之一。它在我们的开发过程当中可以帮助我们做很多事情。比如通过流的方式梳理大量数据,或者帮我们分离应用程序。

和streams流相关的内容有哪些呢?大致有这么几点:

  • 处理大量数据
  • 使用管道方法
  • 转换流
  • 读写流
  • 双工流
  • 解耦I/O

双工流

双工流(duplex streams)既有可读接口又有可写接口。

我们可以使用duplexify模块将可读流和可写流作为双工流连接起来,当然这需要我们先install一下 duplexify 模块儿。

看个例子:

const from = require('from2')
const to = require('to2')
const duplexify = require('duplexify')

const rs = from(() => {
  rs.push(Buffer.from('晚上好:terrence'))
  rs.push(null)
})
const ws = to((data, enc, cb) => {
  console.log('说的是:' + data.toString)
  cb()
})
const stream = duplexify(ws, rs)
stream.pipe(stream)

执行上面的代码实际上打印出来的是toSting函数的函数体,因为我们的toString没加括号。

我们可以大致看一下这个过程,这也很正常,在学习一个问题的时候,顺带着可以理解另外一个问题。

我们给toString加上括号,可以看到,可以正常打印出我们push进去的数据。

代码中我们用到了之前提到过的from模块和to模块创建了可读流和可写流,但是和之前不同的是,我们声明了一个stream,并且用duplexify将rs和ws链接后的值赋给了stream。

这样,stream就可以用管道pipe到它自己。

这个场景非常有用,当我们想要返回或导出两个以某种方式相互关联的流时,这将会是一个非常有用的API。

I/O 解耦

stream流有两个主要的优势:一是通过增量处理可以对内存以及CPU进行比较精细的控制。二是这种简洁有力的API接口,可以在数据输入者,转换者,以及数据接收者,三者之间提供清晰的逻辑。

假设我们想要实现自定义的一个协议,最有可能的情况是它可能会需要或者跟TCP的服务一起使用。我们就可以从net模块儿的TCP服务中重新抽象出来新的内容,当然我们也可以提供一个和socket相关联的流。

看个例子:

这是tcp.js

const net = require('net')
const pump = require('pump')
const ping = require('../piping')
console.log('ping---', ping)

const server = net.createServer((socket) => {
  const protocol = ping()
  pump(socket, protocol, socket, closed)
})

function closed(err) {
  if (err) {
    console.log('链接出错----')
  } else {
    console.log('成功')
  }
}

我们用net模块儿起了一个服务,然后接收到socket数据以后通过pump(泵)一层一层往下传递,给客户端返回数据,中间我们用protocol方法对数据做了处理。

然后我们看ping.js

const through = require('through2')
const split = require('split2')
const pumpify = require('pumpify')

function pingProtocal() {
  const ping = /Ping:\s+(.*)/
  const protocal = through(each)

  function each(line, enc, cb) {
    console.log('line--->', line.toString())
    if (ping.test(line)) {
      cb(null, `Pong:${line.toString().match(ping)[1]}\n`)
      return
    }
    cb(null, '不符合规则')
  }

  return pumpify(split(), protocal)
}

module.exports = pingProtocal

ping.js中我们定义了pingProtocal方法,当我们检测到数据是以Ping:开头的时候,我们会将它转化成Pong。然后通过pumpify返回。

接下来我们在终端上输入:

node tcp.js

启动我们的服务。

然后开启另外一个终端,输入:

node -e "process.stdin.pipe(net.connect(4001)).pipe(process.stdout)"

和服务建立链接。

理论上当我们们在新的终端输入以Ping:开头的字符时,会收到服务端返回的以Pong开头的转化后的数据。

但是我这里失败了,报错如下:

起初我以为是我本地的端口被占用了,但是用

netstat -a | grep 3000

看了一下,好像也没问题,这就很奇怪。我们先不管这个问题,接着往下说。

这个例子的主要目的是作为一个演示解耦I/O的一个demo。

我们在处理输入数据的时候,往往在服务端会写一些接收到数据然后在进行处理的方法,这里主要是在TCP.js中,我们需要对接收到的socket进行处理,但是通过这种方式,我们可以将这个处理的过程单独拿出来进行处理,tcp.js中只要通过pump泵送数据即可。

同样的道理,假如我们需要对I/O进行压缩,或者加密,I/O我们可以理解为输入的数据或者输出的数据。我们就可以将它转换成流添加到管道中进行泵送。

道理都是相通的。

最后

  • 公众号《JavaScript高级程序设计》
  • 公众号内回复”vue-router“ 或 ”router“即可收到 VueRouter源码分析的文档。
  • 回复”vuex“ 或 ”Vuex“即可收到 Vuex 源码分析的文档。

全文完,如果喜欢。

请点赞和"在看"吧,最好也加个"关注",或者分享到朋友圈。