ホームページ  >  記事  >  ウェブフロントエンド  >  Node.jsにおけるQUICプロトコルの詳細説明

Node.jsにおけるQUICプロトコルの詳細説明

青灯夜游
青灯夜游転載
2021-03-26 10:49:564172ブラウズ

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 の上層を定義します。実際には、TCP の特殊効果のほとんどを UDP 上で再実装していますが、重要な違いが 1 つあります。それは、TCP とは異なり、パケットが依然として順序どおりに送信されないことです。これを理解することは、QUIC が TCP よりも優れている理由を理解するために重要です。

#[関連する推奨事項: 「

nodejs チュートリアル 」]

QUIC は行頭ブロックの根本原因を排除します# #In HTTP 1 では、クライアントとサーバー間で交換されるすべてのメッセージは、連続した中断のないデータ ブロックの形式です。単一の TCP 接続で複数の要求または応答を送信できますが、前のメッセージが完全に送信されるまで待ってから、次の完全なメッセージを送信する必要があります。これは、10 メガバイトのファイルを送信してから 2 メガバイトのファイルを送信する場合、後者のファイルを開始する前に、前者のファイルの転送が完了する必要があることを意味します。これは行頭ブロッキングとして知られており、大量の遅延とネットワーク帯域幅の使用効率の低下の原因になります。

HTTP 2 は、多重化を導入することでこの問題を解決しようとしています。 HTTP 2 では、リクエストと応答を連続ストリームとして送信するのではなく、フレームと呼ばれる個別のチャンクに分割し、他のフレームとインターリーブすることができます。 TCP 接続は理論的には、無制限の数の同時要求および応答ストリームを処理できます。これは理論的には可能ですが、HTTP 2 は TCP 層での行頭ブロックの可能性を考慮して設計されていません。

TCP 自体は厳密に順序付けされたプロトコルです。パケットはシリアル化され、固定された順序でネットワーク上に送信されます。パケットが宛先に到達できない場合、失われたパケットが再送信されるまで、パケット フロー全体がブロックされます。有効なシーケンスは次のとおりです: パケット 1 を送信、確認を待機、パケット 2 を送信、確認を待機、パケット 3 を送信... HTTP 1 では、一度に送信できる HTTP メッセージは 1 つだけであり、1 つの TCP パケットが失われた場合、再送信は 1 つの HTTP 要求/応答ストリームにのみ影響します。しかし、HTTP 2 では、TCP パケットを 1 つも失うことなく、無制限の同時 HTTP 要求/応答ストリームがブロックされます。待ち時間が長く、信頼性が低いネットワーク上で HTTP 2 経由で通信すると、HTTP 1 に比べて全体的なパフォーマンスとネットワーク スループットが大幅に低下します。

Node.jsにおけるQUICプロトコルの詳細説明

HTTP 1 では、一度に 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 は、TLS 経由の TCP 経由の HTTP とは異なり、ゼロトリップ HTTP リクエストのサポートを追加します。TLS セッションを確立する前に、クライアントとサーバーの間で複数のデータ交換が必要です。HTTP リクエスト データを転送するために、QUIC は HTTP を許可します。リクエスト ヘッダーが TLS ハンドシェイクの一部として送信されるため、新しい接続の初期待ち時間が大幅に短縮されます。

Node.jsにおけるQUICプロトコルの詳細説明

Node.js カーネル用の QUIC の実装

Node.js カーネル用の QUIC の実装作業が開始されました2019 年 3 月 3 月に開始され、NearForm と Protocol Labs が共催します。優れた ngtcp2 ライブラリを活用して、広範な低レベル実装を提供します。 QUIC は、多くの TCP 機能の再実装であり、Node.js の現在の TCP および HTTP よりも多くの機能をサポートできるため、Node.js にとって非常に意味があります。一方で、ユーザーからは非常に複雑な部分が隠されています。

「quic」モジュール

新しい QUIC サポートを実装する際、新しいトップレベルの組み込み quic モジュールを使用して公開しましたAPI。この機能が Node.js コアに実装されるときにこのトップレベル モジュールが引き続き使用されるかどうかは、後日決定されます。ただし、開発で実験的サポートを使用する場合は、require('quic') 経由でこの API を使用できます。

const { createSocket } = require('quic')

quic このモジュールは、export: createSocket 関数を公開します。この関数は、QUIC サーバーとクライアントが使用できる QuicSocket オブジェクト インスタンスを作成するために使用されます。

QUIC でのすべての作業は、Node.js マスター ブランチからフォークされ、それと並行して開発された別の GitHub リポジトリで行われます。新しいモジュールを使用する場合、またはソース コードを入手できる独自のコードを提供する場合は、Node.js のビルド手順を参照してください。ただし、まだ開発中であるため、必ずバグが発生する可能性があります。

QUIC サーバーの作成

QUIC サーバーは、リモート クライアントが新しい QUIC 接続を開始するのを待機するように構成された QuicSocket インスタンスです。これは、ローカル 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 キーと証明書が関連付けられている必要があることを意味します。 QUIC は、QUIC の TLS コンテキストが QuicSocket ではなく QuicSession に関連付けられているという点で、従来の TCP ベースの TLS 接続と比較して独特です。 Node.js での TLSSocket の使用法に慣れている場合は、ここでの違いに気づいたはずです。

QuicSocket (および QuicSession) のもう 1 つの重要な違いは、Node によって公開される既存の net.Socket および ## とは異なることです。 js #tls.TLSSocket オブジェクトは異なります。QuicSocketQuicSession も、Readable または Writable のストリームではありません。つまり、オブジェクトを使用して接続されたピアと直接データを送受信することはできないため、QuicStream オブジェクトを使用する必要があります。

上記の例では、

QuicSocket が作成され、ローカル UDP ポート 5678 にバインドされます。次に、この QuicSocket に、開始される新しい QUIC 接続をリッスンするように指示します。 QuicSocket がリッスンを開始すると、ready イベントが発行されます。

新しい QUIC 接続が開始され、サーバーに対応する

QuicSession オブジェクトが作成されると、session イベントが発行されます。作成された QuicSession オブジェクトは、新しいクライアント サーバーによって開始された QuicStream インスタンスをリッスンするために使用できます。

QUIC プロトコルのより重要な機能の 1 つは、クライアントが初期ストリームを開かずにサーバーへの新しい接続を開始でき、サーバーはクライアントからの初期ストリームを待たずに最初の接続を開始できることです。 . 自分の流れ。この機能により、現在の 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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。