目前就职于视听行业相关的公司,故而需要接触流媒体传输相关技术。

今天介绍一种基于 MIME 类型的服务端流媒体推送实现。

multipart

MIME 类型由类型和子类型组成:Content-Type: [type]/[subType],前者是数据大类别,后者是具体的种类。

multipart 用于连接消息体的多个部分构成一个消息,这些部分可以是不同类型的数据。也就是一个消息可以包含多个消息体。

我们常见的一般是表单相关的 multipart/form-data,当我们需要在提交表单时上传文件,就需要设置这个类型。
电子邮件中使用的类型是 multipart/alternative,包含纯文本(text/plain)和 HTML(text/html)两种数据。
其他相关的可以看看 维基百科 MIME#Content-Type

x-mixed-replace

multipart/x-mixed-replace 是 Netscape 在 1995 年引入的。与其他的 multipart 子类型相比,新的消息体会覆盖前一个消息体,而不是累加。

报文格式如:

Content-type: multipart/x-mixed-replace; boundary=--myBoundary
--myBoundary
Content-Type: image/png
Content-length: 100
stream1...
--myBoundary
Content-Type: image/png
Content-length: 100
stream2...
--myBoundary

boundary 用来指定一个边界符,用于分割多个消息体,多个消息体之间隔一行。可以指定为任意字符,但为了避免消息体中包含了该字符,尽量复杂一些。

要实现服务器推送,就是不发送完整的报文,始终发送新的消息体。这样会使 HTTP 连接得到保持,服务器就可以按一定的频率持续推送数据。

multipart/x-mixed-replace 类型的报文是没有结尾的,一旦客户端连接,将可以永远保持连接。

客户端的运行策略为:客户端连接后,获取到第一段消息体并显示。之后发现下一段消息体的边界符,就认为第一段消息体已结束,开始显示第二段消息体的内容。往复替换,自动更新。

栗子

一个不超过 20 行的 NodeJS Demo:yscoder/multipart-x-mixed-replace-example

const http = require('http')
const fs = require('fs')
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'multipart/x-mixed-replace; boundary=--test' })
let i = 1
function sendData() {
const content = fs.readFileSync(`./imgs/run${i}.png`)
res.write(`--test\nContent-Type: image/png\nContent-length: ${content.length}\n\n`)
res.write(content)
i = i === 20 ? 1 : i + 1
setTimeout(sendData, 50)
}
setTimeout(sendData, 50)
})
server.listen(8090)
console.log('Server running!')

网上找了一组包含 20 张图片的人物帧序列图,逐个发送到客户端。浏览器打开本地端口地址后可以看到动画的效果。

实际运用中一般是连接媒体视频设备获取媒体流然后向客户端发送。客户端只需要使用 img 标签引用接口就可以持续接收到推送数据,监听 onload 事件把每次推送的数据绘制到 canvas 上。