本篇文章带大家了解一下Node.js中的QUIC协议。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。
![]() 在2019年3月,受到 NearForm 和 Protocol Labs 的支持,我开始为 Node.js 实现 QUIC 协议 支持。这个基于 UDP 的新传输协议旨在最终替代所有使用 TCP 的 HTTP 通信。
熟悉 UDP 的人可能会产生质疑。众所周知 UDP 是不可靠的,数据包经常会有丢失、乱序、重复等情况。 UDP 不保证高级协议(例如 HTTP)严格要求的 TCP 所支持的可靠性和顺序。那就是 QUIC 进来的地方。 QUIC 协议在 UDP 之上定义了一层,该层为 UDP 引入了错误处理、可靠性、流控制和内置安全性(通过 TLS 1.3)。实际上它在 UDP 之上重新实现了大多数 TCP 的特效,但是有一个关键的区别:与 TCP 不同,仍然可以不按顺序传输数据包。了解这一点对于理解 QUIC 为什么优于 TCP 至关重要。 【相关推荐:《nodejs 教程》】 QUIC 消除了队首阻塞的根源在 HTTP 1 中,客户端和服务器之间所交换的所有消息都是连续的、不间断的数据块形式。虽然可以通过单个 TCP 连接发送多个请求或响应,但是在发送下一条完整消息之前,必须先等上一条消息完整的传输完毕。这意味着,如果要发送一个 10 兆字节的文件,然后发送一个 2 兆字节的文件,则前者必须完全传输完毕,然后才能启动后者。这就是所谓的队首阻塞,是造成大量延迟和不良使用网络带宽的根源。 HTTP 2 尝试通过引入多路复用来解决此问题。 HTTP 2 不是将请求和响应作为连续的流传输,而是将请求和响应分成了被称为帧的离散块,这些块可以与其他帧交织。一个 TCP 连接理论上可以处理无限数量的并发请求和响应流。尽管从理论上讲这是可行的,但是 HTTP 2 的设计没有考虑 TCP 层出现队首阻塞的可能性。 TCP 本身是严格排序的协议。数据包被序列化并按照固定顺序通过网络发送。如果数据包未能到达其目的地,则会阻止整个数据包流,直到可以重新传输丢失的数据包为止。有效的顺序是:发送数据包1,等待确认,发送数据包2,等待确认,发送数据包3……。使用 HTTP 1,在任何给定时间只能传输一个 HTTP 消息,如果单个 TCP 数据包丢失,那么重传只会影响单个 HTTP 请求/响应流。但是使用 HTTP 2,则会在丢失单个 TCP 数据包的情况下阻止无限数量的并发 HTTP 请求/响应流的传输。在通过高延迟、低可靠性网络进行 HTTP 2 通信时,与 HTTP 1 相比,整体性能和网络吞吐量会急剧下降。
在 HTTP 1 中,该请求会被阻塞,因为一次只能发送一条完整的消息。
在 HTTP 2 中,当单个 TCP 数据包丢失或损坏时,该请求将被阻塞。
在QUIC中,数据包彼此独立,能够以任何顺序发送(或重新发送)。 幸运的是有了 QUIC 情况就不同了。当数据流被打包到离散的 UDP 数据包中传输时,任何单个数据包都能够以任意顺序发送(或重新发送),而不会影响到其他已发送的数据包。换句话说,线路阻塞问题在很大程度上得到解决。 QUIC 引入了灵活性、安全性和低延迟QUIC 还引入了许多其他重要功能:
为 Node.js 内核实现 QUIC为 Node.js 内核实现 QUIC 的工作从 2019 年 3 月开始,并由 NearForm 和 Protocol Labs 共同赞助。我们利用出色的 ngtcp2 库来提供大量的低层实现。因为 QUIC 是许多 TCP 特性的重新实现,所以对 Node.js 意义重大,并且与 Node.js 中当前的 TCP 和 HTTP 相比能够支持更多特性。同时对用户隐藏了大量的复杂性。 “quic” 模块在实现新的 QUIC 支持的同时,我们用了新的顶级内置 const { createSocket } = require('quic')
QUIC 的所有工作都在一个单独的 GitHub 存储库 中进行,该库 fork 于 Node.js master 分支并与之并行开发。如果你想使用新模块,或者贡献自己的代码,可以从那里获取源代码,请参阅 Node.js 构建说明。不过它现在仍然是一项尚在进行中的工作,你一定会遇到 bug 的。 创建QUIC服务器QUIC 服务器是一个 const { createSocket } = require('quic')
const { readFileSync } = require('fs')
const key = readFileSync('./key.pem')
const cert = readFileSync('./cert.pem')
const ca = readFileSync('./ca.pem')
const requestCert = true
const alpn = 'echo'
const server = createSocket({
// 绑定到本地 UDP 5678 端口
endpoint: { port: 5678 },
// 为新的 QuicServer Session 实例创建默认配置
server: {
key,
cert,
ca,
requestCert
alpn
}
})
server.listen()
server.on('ready', () => {
console.log(`QUIC server is listening on ${server.address.port}`)
})
server.on('session', (session) => {
session.on('stream', (stream) => {
// Echo server!
stream.pipe(stream)
})
const stream = session.openStream()
stream.end('hello from the server')
})如前所述,QUIC 协议内置并要求支持 TLS 1.3。这意味着每个 QUIC 连接必须有与其关联的 TLS 密钥和证书。与传统的基于 TCP 的 TLS 连接相比,QUIC 的独特之处在于 QUIC 中的 TLS 上下文与
在上面的例子中创建了一个 当启动新的 QUIC 连接并创建了对应服务器的 QUIC 协议的更重要特征之一是客户端可以在不打开初始流的情况下启动与服务器的新连接,并且服务器可以在不等待来自客户端的初始流的情况下先启动其自己的流。这个功能提供了许多非常有趣的玩法,而这在当前 Node.js 内核中的 HTTP 1 和 HTTP 2 是不可能提供的。 创建QUIC客户端QUIC 客户端和服务器之间几乎没有什么区别: const { createSocket } = require('quic')
const fs = require('fs')
const key = readFileSync('./key.pem')
const cert = readFileSync('./cert.pem')
const ca = readFileSync('./ca.pem')
const requestCert = true
const alpn = 'echo'
const servername = 'localhost'
const socket = createSocket({
endpoint: { port: 8765 },
client: {
key,
cert,
ca,
requestCert
alpn,
servername
}
})
const req = socket.connect({
address: 'localhost',
port: 5678,
})
req.on('stream', (stream) => {
stream.on('data', (chunk) => { /.../ })
stream.on('end', () => { /.../ })
})
req.on('secure', () => {
const stream = req.openStream()
const file = fs.createReadStream(__filename)
file.pipe(stream)
stream.on('data', (chunk) => { /.../ })
stream.on('end', () => { /.../ })
stream.on('close', () => {
// Graceful shutdown
socket.close()
})
stream.on('error', (err) => { /.../ })
})对于服务器和客户端, 在 与服务器端类似,一旦创建了客户端 单向流和双向流所有的 双向流在两个方向上都是可读写的,而不管该流是由客户端还是由服务器启动的。单向流只能在一个方向上读写。客户端发起的单向流只能由客户端写入,并且只能由服务器读取;客户端上不会发出任何数据事件。服务器发起的单向流只能由服务器写入,并且只能由客户端读取;服务器上不会发出任何数据事件。 // 创建双向流
const stream = req.openStream()
// 创建单向流
const stream = req.openStream({ halfOpen: true })每当远程对等方启动流时,无论是服务器还是客户端的 session.on('stream', (stream) => {
if (stream.clientInitiated)
console.log('client initiated stream')
if (stream.serverInitiated)
console.log('server initiated stream')
if (stream.bidirectional)
console.log('bidirectional stream')
if (stream.unidirectional)
console.log(‘’unidirectional stream')
})由本地发起的单向 就是这样从上面的例子可以清楚地看出,从用户的角度来看,创建和使用 QUIC 是相对简单的。尽管协议本身很复杂,但这种复杂性几乎不会上升到面向用户的 API。实现中包含一些高级功能和配置选项,这些功能和配置项在上面的例子中没有说明,在通常情况下,它们在很大程度上是可选的。 在示例中没有对 HTTP 3 的支持进行说明。在基本 QUIC 协议实现的基础上实现 HTTP 3 语义的工作正在进行中,并将在以后的文章中介绍。 QUIC 协议的实现还远远没有完成。在撰写本文时,IETF 工作组仍在迭代 QUIC 规范,我们在 Node.js 中用于实现大多数 QUIC 的第三方依赖也在不断发展,并且我们的实现还远未完成,缺少测试、基准、文档和案例。但是作为 Node.js v14 中的一项实验性新功能,这项工作正在逐步着手进行。希望 QUIC 和 HTTP 3 支持在 Node.js v15 中能够得到完全支持。我们希望你的帮助!如果你有兴趣参与,请联系 https://www.nearform.com/cont... ! 鸣谢在结束本文时,我要感谢 NearForm 和 Protocol Labs 在财政上提供的赞助,使我能够全身心投入于对 QUIC 的实现。两家公司都对 QUIC 和 HTTP 3 将如何发展对等和传统 Web 应用开发特别感兴趣。一旦实现接近完成,我将会再写一文章来阐述 QUIC 协议的一些奇妙的用例,以及使用 QUIC 与 HTTP 1、HTTP 2、WebSockets 以及其他方法相比的优势。 James Snell( @jasnell)是 NearForm Research 的负责人,该团队致力于研究和开发 Node.js 在性能和安全性方面的主要新功能,以及物联网和机器学习的进步。 James 在软件行业拥有 20 多年的经验,并且是 Node.js 社区中的知名人物。他曾是多个 W3C 语义 web 和 IETF 互联网标准的作者、合著者、撰稿人和编辑。他是 Node.js 项目的核心贡献者,是 Node.js 技术指导委员会(TSC)的成员,并曾作为 TSC 代表在 Node.js Foundation 董事会任职。
更多编程相关知识,请访问:编程视频!! 以上就是详解Node.js中的QUIC协议的详细内容,更多请关注模板之家(www.mb5.com.cn)其它相关文章! |
