Node.js의 QUIC 프로토콜에 대한 자세한 설명

青灯夜游
풀어 주다: 2021-03-26 10:49:56
앞으로
4146명이 탐색했습니다.

Node.js의 QUIC 프로토콜에 대한 자세한 설명

2019년 3월 NearForm 및 Protocol Labs의 지원을 받아 Node.js에 대한 QUIC 프로토콜 지원을 구현하기 시작했습니다. 이 새로운 UDP 기반 전송 프로토콜은 결국 TCP를 사용하는 모든 HTTP 통신을 대체하기 위한 것입니다.

Node.js의 QUIC 프로토콜에 대한 자세한 설명

UDP에 익숙한 사람들은 의구심을 가질 수 있습니다. UDP는 신뢰할 수 없으며 데이터 패킷이 종종 손실되고, 순서가 어긋나고, 중복된다는 것은 잘 알려져 있습니다. UDP는 HTTP와 같은 상위 수준 프로토콜에서 엄격히 요구하는 TCP에서 지원하는 안정성과 순서를 보장하지 않습니다. QUIC이 등장하는 곳입니다.

QUIC 프로토콜은 오류 처리, 안정성, 흐름 제어 및 내장 보안(TLS 1.3을 통해)을 UDP에 도입하는 UDP 위에 레이어를 정의합니다. 실제로 UDP 위에 TCP의 특수 효과 대부분을 다시 구현하지만 한 가지 주요 차이점이 있습니다. TCP와 달리 패킷은 여전히 ​​순서 없이 전송될 수 있습니다. 이를 이해하는 것은 QUIC가 TCP보다 우수한 이유를 이해하는 데 중요합니다.

[관련 추천: "nodejs tutorial"]

QUIC는 HOL 차단의 근본 원인을 제거합니다

HTTP 1에서는 클라이언트와 서버 간에 교환되는 모든 메시지가 연속적이고 중단되지 않는 데이터 블록입니다. 형태. 단일 TCP 연결을 통해 여러 요청이나 응답을 보낼 수 있지만 다음 완전한 메시지를 보내기 전에 이전 메시지가 완전히 전송될 때까지 기다려야 합니다. 즉, 10MB 파일을 보낸 다음 2MB 파일을 보내려면 전자를 완전히 전송해야 후자를 시작할 수 있습니다. 이를 헤드 오브 라인 차단이라고 하며 대기 시간이 길어지고 네트워크 대역폭이 제대로 사용되지 않는 원인이 됩니다.

HTTP 2는 멀티플렉싱을 도입하여 이 문제를 해결하려고 시도합니다. HTTP 2는 요청과 응답을 연속적인 스트림으로 전송하는 대신 요청과 응답을 프레임이라는 개별 청크로 나누어 다른 프레임과 인터리브할 수 있습니다. TCP 연결은 이론적으로 동시 요청 및 응답 스트림을 무제한으로 처리할 수 있습니다. 이론적으로는 가능하지만 HTTP 2는 TCP 계층에서 HOL(head-of-line) 차단 가능성을 고려하도록 설계되지 않았습니다.

TCP 자체는 엄격하게 주문된 프로토콜입니다. 패킷은 직렬화되어 고정된 순서로 네트워크를 통해 전송됩니다. 패킷이 대상에 도달하지 못하면 손실된 패킷을 재전송할 수 있을 때까지 전체 패킷 흐름이 차단됩니다. 유효한 순서는 다음과 같습니다. 패킷 1 전송, 확인 대기, 패킷 2 전송, 확인 대기, 패킷 3 전송... HTTP 1을 사용하면 주어진 시간에 하나의 HTTP 메시지만 전송할 수 있으며 단일 TCP 패킷이 손실된 경우 재전송은 단일 HTTP 요청/응답 스트림에만 영향을 미칩니다. 그러나 HTTP 2를 사용하면 단일 TCP 패킷 손실 없이 무제한의 동시 HTTP 요청/응답 스트림이 차단됩니다. 대기 시간이 길고 안정성이 낮은 네트워크에서 HTTP 2를 통해 통신할 때 전체 성능과 네트워크 처리량은 HTTP 1에 비해 크게 감소합니다.

Node.js의 QUIC 프로토콜에 대한 자세한 설명

HTTP 1에서는 한 번에 하나의 완전한 메시지만 보낼 수 있으므로 이 요청이 차단됩니다.

Node.js의 QUIC 프로토콜에 대한 자세한 설명

HTTP 2에서는 단일 TCP 패킷이 손실되거나 손상되면 요청이 차단됩니다.

Node.js의 QUIC 프로토콜에 대한 자세한 설명

QUIC에서 패킷은 서로 독립적이며 어떤 순서로든 전송(또는 재전송)될 수 있습니다.

다행히 QUIC를 사용하면 상황이 달라집니다. 데이터 스트림이 전송을 위해 개별 UDP 패킷으로 패키징되면 전송된 다른 패킷에 영향을 주지 않고 개별 패킷을 순서에 관계없이 전송(또는 재전송)할 수 있습니다. 즉, 회선 혼잡 문제가 대폭 해결된다.

QUIC는 유연성, 보안 및 낮은 대기 시간을 도입합니다.

QUIC는 또한 다른 많은 훌륭한 기능을 도입합니다.

  • QUIC 연결은 네트워크 토폴로지와 독립적으로 작동합니다. QUIC 연결이 설정된 후에는 연결을 다시 설정하지 않고도 소스 및 대상 IP 주소와 포트를 모두 변경할 수 있습니다. 이는 네트워크를 자주 전환하는 모바일 장치(예: LTE에서 WiFi로)에 특히 유용합니다.
  • QUIC 연결은 기본적으로 안전하고 암호화되어 있습니다. TLS 1.3 지원은 프로토콜에 직접 포함되며 모든 QUIC 통신은 암호화됩니다.
  • QUIC는 UDP에 중요한 흐름 제어 및 오류 처리를 추가하고 다양한 서비스 거부 공격을 방지하는 중요한 보안 메커니즘을 포함합니다.
  • QUIC는 HTTP 요청 데이터가 전송되기 전에 TLS 세션을 설정하기 위해 클라이언트와 서버 간에 여러 데이터 교환이 필요한 TCP 기반 TLS를 통한 HTTP와 달리 제로 트립 HTTP 요청에 대한 지원을 추가합니다. QUIC는 HTTP 요청 헤더가 TLS 핸드셰이크의 일부로 전송되므로 새 연결의 초기 대기 시간이 크게 줄어듭니다.

Node.js의 QUIC 프로토콜에 대한 자세한 설명

Node.js 커널용 QUIC 구현

Node.js 커널용 QUIC 구현 작업은 2019년 3월에 시작되었으며 NearForm과 Protocol Labs가 공동 후원합니다. 우리는 우수한 ngtcp2 라이브러리를 활용하여 광범위한 하위 수준 구현을 제공합니다. QUIC는 많은 TCP 기능을 다시 구현하고 Node.js의 현재 TCP 및 HTTP보다 더 많은 기능을 지원할 수 있기 때문에 Node.js에 매우 적합합니다. 사용자에게 많은 복잡성을 숨기면서.

“quic” 모듈

새로운 QUIC 지원을 구현하는 동안 우리는 API를 노출하기 위해 새로운 최상위 내장 quic 모듈을 사용했습니다. Node.js 코어에 기능이 구현될 때 이 최상위 모듈이 계속 사용될지 여부는 나중에 결정될 것입니다. 그러나 개발 시 실험적 지원을 사용할 때는 require('quic')를 통해 이 API를 사용할 수 있습니다. quic 模块来公开 API。当该功能在 Node.js 核心中落地时,是否仍将使用这个顶级模块,将在以后确定。不过当在开发中使用实验性支持时,你可以通过 require('quic') 使用这个 API。

const { createSocket } = require('quic')
로그인 후 복사

quic 模块公开了一个导出:createSocket 函数。这个函数用来创建 QuicSocket 对象实例,该对象可用于 QUIC 服务器和客户端。

QUIC 的所有工作都在一个单独的 GitHub 存储库 中进行,该库 fork 于 Node.js master 分支并与之并行开发。如果你想使用新模块,或者贡献自己的代码,可以从那里获取源代码,请参阅 Node.js 构建说明。不过它现在仍然是一项尚在进行中的工作,你一定会遇到 bug 的。

创建QUIC服务器

QUIC 服务器是一个 QuicSocket 实例,被配置为等待远程客户端启动新的 QUIC 连接。这是通过绑定到本地 UDP 端口并等待从对等方接收初始 QUIC 数据包来完成的。在收到 QUIC 数据包后,QuicSocket 将会检查是否存在能够用于处理该数据包的服务器 QuicSession 对象,如果不存在将会创建一个新的对象。一旦服务器的 QuicSession 对象可用,则该数据包将被处理,并调用用户提供的回调。这里有一点很重要,处理 QUIC 协议的所有细节都由 Node.js 在其内部处理。

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 上下文与 QuicSession 相关联,而不是 QuicSocket。如果你熟悉 Node.js 中 TLSSocket 的用法,那么你一定注意到这里的区别。

QuicSocket(和 QuicSession)的另一个关键区别是,与 Node.js 公开的现有 net.Sockettls.TLSSocket 对象不同,QuicSocketQuicSession 都不是 ReadableWritable的流。即不能用一个对象直接向连接的对等方发送数据或从其接收数据,所以必须使用 QuicStream 对象。

在上面的例子中创建了一个 QuicSocket 并将其绑定到本地 UDP 的 5678 端口。然后告诉这个 QuicSocket 侦听要启动的新 QUIC 连接。一旦 QuicSocket 开始侦听,将会发出 ready 事件。

当启动新的 QUIC 连接并创建了对应服务器的 QuicSession 对象后,将会发出 session 事件。创建的 QuicSession 对象可用于侦听新的客户端服务器端所启动的 QuicStream

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) => { /.../ })
})
로그인 후 복사
로그인 후 복사
quic 모듈은 createSocket 함수 내보내기를 노출합니다. 이 함수는 QUIC 서버와 클라이언트에서 사용할 수 있는 QuicSocket 개체의 인스턴스를 생성하는 데 사용됩니다.

QUIC의 모든 작업은 Node.js 마스터 브랜치에서 분기되어 동시에 개발되는 별도의 GitHub 저장소에서 이루어집니다. 새 모듈을 사용하거나 자신의 코드를 제공하려면 거기에서 소스 코드를 얻을 수 있습니다. Node.js 빌드 지침을 참조하세요. 하지만 아직 진행 중인 작업이므로 버그가 발생할 수 있습니다.

QUIC 서버 만들기

QUIC 서버는 원격 클라이언트가 새로운 QUIC 연결을 시작할 때까지 기다리도록 구성된 QuicSocket 인스턴스입니다. 이는 로컬 UDP 포트에 바인딩하고 피어로부터 초기 QUIC 패킷 수신을 기다리는 방식으로 수행됩니다. QUIC 패킷을 수신한 후 QuicSocket은 패킷을 처리하는 데 사용할 수 있는 서버 QuicSession 개체가 있는지 확인하고, 존재하지 않으면 개체를 생성합니다. 새로운 개체. 서버의 QuicSession 개체를 사용할 수 있게 되면 패킷이 처리되고 사용자가 제공한 콜백이 호출됩니다. 여기서 QUIC 프로토콜 처리에 대한 모든 세부 사항은 Node.js에 의해 내부적으로 처리된다는 점을 기억하는 것이 중요합니다. 🎜
// 创建双向流
const stream = req.openStream()

// 创建单向流
const stream = req.openStream({ halfOpen: true })
로그인 후 복사
로그인 후 복사
🎜앞서 언급했듯이 QUIC 프로토콜에는 TLS 1.3에 대한 필수 지원이 내장되어 있습니다. 이는 각 QUIC 연결에 TLS 키와 인증서가 연결되어 있어야 함을 의미합니다. QUIC는 QUIC의 TLS 컨텍스트가 QuicSocket이 아닌 QuicSession과 연결된다는 점에서 기존 TCP 기반 TLS 연결과 비교할 때 독특합니다. Node.js의 TLSSocket 사용법에 익숙하다면 여기서 차이점을 발견했을 것입니다. 🎜🎜 QuicSocket(및 QuicSession)의 또 다른 주요 차이점은 Node.js에서 노출되는 기존 net.Socket와 다르다는 것입니다. js tls.TLSSocket 개체는 QuicSocketQuicSession 모두 읽기 가능 또는 쓰기 가능' 스트림이 아닙니다. 코드>. 즉, 개체를 사용하여 연결된 피어에 직접 데이터를 보내거나 데이터를 받을 수 없으므로 QuicStream 개체를 사용해야 합니다. 🎜🎜위의 예에서는 QuicSocket이 생성되어 로컬 UDP 포트 5678에 바인딩됩니다. 그런 다음 이 QuicSocket에 새로운 QUIC 연결이 시작되는지 수신하도록 지시하세요. QuicSocket이 수신을 시작하면 ready 이벤트가 발생합니다. 🎜🎜새로운 QUIC 연결이 시작되고 해당 서버의 QuicSession 객체가 생성되면 session 이벤트가 발생합니다. 생성된 QuicSession 개체는 클라이언트-서버에서 시작된 새로운 QuicStream 인스턴스를 수신하는 데 사용될 수 있습니다. 🎜🎜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) => { /.../ })
})
로그인 후 복사
로그인 후 복사

对于服务器和客户端,createSocket() 函数用于创建绑定到本地 UDP 端口的 QuicSocket 实例。对于 QUIC 客户端来说,仅在使用客户端身份验证时才需要提供 TLS 密钥和证书。

QuicSocket 上调用 connect() 方法将新创建一个客户端 QuicSession 对象,并与对应地址和端口的服务器创建新的 QUIC 连接。启动连接后进行 TLS 1.3 握手。握手完成后,客户端 QuicSession 对象会发出 secure 事件,表明现在可以使用了。

与服务器端类似,一旦创建了客户端 QuicSession 对象,就可以用 stream 事件监听服务器启动的新 QuicStream 实例,并可以调用 openStream()  方法来启动新的流。

单向流和双向流

所有的 QuicStream 实例都是双工流对象,这意味着它们都实现了 ReadableWritable 流 Node.js API。但是,在 QUIC 中,每个流都可以是双向的,也可以是单向的。

双向流在两个方向上都是可读写的,而不管该流是由客户端还是由服务器启动的。单向流只能在一个方向上读写。客户端发起的单向流只能由客户端写入,并且只能由服务器读取;客户端上不会发出任何数据事件。服务器发起的单向流只能由服务器写入,并且只能由客户端读取;服务器上不会发出任何数据事件。

// 创建双向流
const stream = req.openStream()

// 创建单向流
const stream = req.openStream({ halfOpen: true })
로그인 후 복사
로그인 후 복사

每当远程对等方启动流时,无论是服务器还是客户端的 QuicSession 对象都会发出提供 QuicStream 对象的 stream 事件。可以用来检查这个对象确定其来源(客户端或服务器)及其方向(单向或双向)

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')
})
로그인 후 복사

由本地发起的单向 QuicStreamReadable 端在创建 QuicStream 对象时总会立即关闭,所以永远不会发出数据事件。同样,远程发起的单向 QuicStreamWritable 端将在创建后立即关闭,因此对 write() 的调用也会始终失败。

就是这样

从上面的例子可以清楚地看出,从用户的角度来看,创建和使用 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 董事会任职。

原文地址:https://www.nearform.com/blog/a-quic-update-for-node-js/

作者:James Snell

译文地址:https://segmentfault.com/a/1190000039308474

翻译:疯狂的技术宅

更多编程相关知识,请访问:编程视频!!

위 내용은 Node.js의 QUIC 프로토콜에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:segmentfault.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!