Maison  >  Article  >  interface Web  >  Explication détaillée du protocole QUIC dans Node.js

Explication détaillée du protocole QUIC dans Node.js

青灯夜游
青灯夜游avant
2021-03-26 10:49:564103parcourir

Explication détaillée du protocole QUIC dans Node.js

En mars 2019, avec le soutien de NearForm et Protocol Labs, j'ai commencé à implémenter la prise en charge du protocole QUIC pour Node.js. Ce nouveau protocole de transport basé sur UDP a vocation à remplacer à terme toutes les communications HTTP utilisant TCP.

Explication détaillée du protocole QUIC dans Node.js

Les personnes familières avec UDP peuvent être sceptiques. Il est bien connu qu’UDP n’est pas fiable et que les paquets de données sont souvent perdus, dans le désordre et dupliqués. UDP ne garantit pas la fiabilité et l'ordre pris en charge par TCP qui sont strictement requis par les protocoles de niveau supérieur tels que HTTP. C'est là qu'intervient QUIC.

Le protocole QUIC définit une couche au-dessus d'UDP qui introduit la gestion des erreurs, la fiabilité, le contrôle de flux et la sécurité intégrée (via TLS 1.3) à UDP. Il réimplémente en fait la plupart des effets spéciaux de TCP en plus d'UDP, mais avec une différence clé : contrairement à TCP, les paquets peuvent toujours être transmis dans le désordre. Comprendre cela est crucial pour comprendre pourquoi QUIC est supérieur à TCP.

[Recommandation associée : "Tutoriel Nodejs"]

QUIC élimine la cause première du blocage de tête de ligne

En HTTP 1 In , tous les messages échangés entre client et serveur se présentent sous la forme de blocs de données continus et ininterrompus. Bien que vous puissiez envoyer plusieurs requêtes ou réponses via une seule connexion TCP, vous devez attendre que le message précédent soit entièrement transmis avant d'envoyer le prochain message complet. Cela signifie que si vous souhaitez envoyer un fichier de 10 Mo, puis un fichier de 2 Mo, le premier doit être entièrement transféré avant de pouvoir démarrer le second. Ceci est connu sous le nom de blocage de tête de ligne et est à l’origine d’une latence importante et d’une mauvaise utilisation de la bande passante du réseau.

HTTP 2 tente de résoudre ce problème en introduisant le multiplexage. Plutôt que de transmettre les requêtes et les réponses sous forme de flux continu, HTTP 2 divise les requêtes et les réponses en morceaux discrets appelés trames, qui peuvent être entrelacées avec d'autres trames. Une connexion TCP peut théoriquement gérer un nombre illimité de flux de requêtes et de réponses simultanés. Bien que cela soit théoriquement possible, HTTP 2 n'a pas été conçu pour tenir compte de la possibilité d'un blocage en tête de ligne au niveau de la couche TCP.

TCP lui-même est un protocole strictement ordonné. Les paquets sont sérialisés et envoyés sur le réseau dans un ordre fixe. Si un paquet ne parvient pas à atteindre sa destination, tout le flux de paquets est bloqué jusqu'à ce que le paquet perdu puisse être retransmis. La séquence valide est : envoyer le paquet 1, attendre la confirmation, envoyer le paquet 2, attendre la confirmation, envoyer le paquet 3... Avec HTTP 1, un seul message HTTP peut être transmis à un moment donné, et si un seul paquet TCP est perdu, les retransmissions n'affectent qu'un seul flux de requête/réponse HTTP. Mais avec HTTP 2, un nombre illimité de flux de requêtes/réponses HTTP simultanés sont bloqués sans perdre un seul paquet TCP. Lors de la communication via HTTP 2 sur un réseau à latence élevée et à faible fiabilité, les performances globales et le débit du réseau diminuent considérablement par rapport à HTTP 1.

Explication détaillée du protocole QUIC dans Node.js

En HTTP 1, cette requête bloquerait car un seul message complet pourrait être envoyé à la fois.

Explication détaillée du protocole QUIC dans Node.js

En HTTP 2, lorsqu'un seul paquet TCP est perdu ou corrompu, la requête sera bloquée.

Explication détaillée du protocole QUIC dans Node.js

Dans QUIC, les paquets sont indépendants les uns des autres et peuvent être envoyés (ou renvoyés) dans n'importe quel ordre.

Heureusement avec QUIC les choses sont différentes. Lorsqu'un flux de données est regroupé en paquets UDP discrets pour la transmission, n'importe quel paquet individuel peut être envoyé (ou renvoyé) dans n'importe quel ordre sans affecter les autres paquets envoyés. En d’autres termes, le problème de la congestion des lignes est en grande partie résolu.

QUIC introduit la flexibilité, la sécurité et une faible latence

QUIC introduit également de nombreuses autres fonctionnalités intéressantes :

  • Les connexions QUIC fonctionnent indépendamment de la topologie du réseau. Une fois qu'une connexion QUIC est établie, les adresses IP et les ports source et de destination peuvent être modifiés sans rétablir la connexion. Ceci est particulièrement utile pour les appareils mobiles qui changent fréquemment de réseau (comme le LTE vers le WiFi).
  • Les connexions QUIC par défaut sont sécurisées et cryptées. La prise en charge de TLS 1.3 est incluse directement dans le protocole et toutes les communications QUIC sont cryptées.
  • QUIC ajoute un contrôle de flux critique et une gestion des erreurs à UDP et inclut d'importants mécanismes de sécurité pour empêcher toute une série d'attaques par déni de service.
  • QUIC ajoute la prise en charge des requêtes HTTP sans trajet, contrairement à HTTP sur TCP sur TLS, qui nécessite plusieurs échanges de données entre le client et le serveur pour établir une session TLS avant de pouvoir transporter les données de requête HTTP, QUIC autorise HTTP demander que les en-têtes soient envoyés dans le cadre de la négociation TLS, réduisant ainsi considérablement la latence initiale des nouvelles connexions.

Explication détaillée du protocole QUIC dans Node.js

Implémentation de QUIC pour le noyau Node.js

Le travail d'implémentation de QUIC pour le noyau Node.js a commencé en mars 2019. Il commence en mars et est co-sponsorisé par NearForm et Protocol Labs. Nous exploitons l'excellente bibliothèque ngtcp2 pour fournir une implémentation étendue de bas niveau. QUIC est logique pour Node.js car il s'agit d'une réimplémentation de nombreuses fonctionnalités TCP et peut prendre en charge beaucoup plus de fonctionnalités que les protocoles TCP et HTTP actuels dans Node.js. tout en cachant une grande complexité à l’utilisateur.

module « quic »

Lors de la mise en œuvre du nouveau support QUIC, nous avons utilisé un nouveau module quic intégré de niveau supérieur pour exposer l'API. La question de savoir si ce module de niveau supérieur sera toujours utilisé lorsque la fonctionnalité sera implémentée dans le noyau Node.js sera déterminée ultérieurement. Cependant, lorsque vous utilisez un support expérimental en développement, vous pouvez utiliser cette API via require('quic'). Le module

const { createSocket } = require('quic')

quic expose un export : la fonction createSocket. Cette fonction est utilisée pour créer une instance de l'objet QuicSocket, qui peut être utilisée par les serveurs et clients QUIC.

Tous les travaux sur QUIC ont lieu dans un référentiel GitHub distinct, dérivé de la branche principale Node.js et développé en parallèle avec celle-ci. Si vous souhaitez utiliser le nouveau module ou contribuer avec votre propre code, où vous pourrez obtenir le code source, consultez les instructions de construction de Node.js. Cependant, c'est toujours un travail en cours et vous rencontrerez forcément des bugs.

Créer un serveur QUIC

Un serveur QUIC est une QuicSocket instance configurée pour attendre que les clients distants initient de nouvelles connexions QUIC. Cela se fait en se liant au port UDP local et en attendant de recevoir le paquet QUIC initial du homologue. Après avoir reçu un paquet QUIC, QuicSocket vérifiera s'il existe un objet serveur QuicSession qui peut être utilisé pour gérer le paquet, et créera un nouvel objet s'il n'existe pas. Une fois l'objet QuicSession du serveur disponible, le paquet est traité et le rappel fourni par l'utilisateur est appelé. Il est important de noter ici que tous les détails de gestion du protocole QUIC sont gérés en interne par 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')
})

Comme mentionné précédemment, le protocole QUIC a une prise en charge intégrée et requise pour TLS 1.3. Cela signifie que chaque connexion QUIC doit être associée à une clé TLS et à un certificat. QUIC est unique par rapport aux connexions TLS traditionnelles basées sur TCP dans la mesure où le contexte TLS dans QUIC est associé à QuicSession au lieu de QuicSocket. Si vous êtes familier avec l'utilisation de TLSSocket dans Node.js, alors vous devez avoir remarqué la différence ici. Une autre différence clé entre

QuicSocket (et QuicSession) est que, contrairement aux objets net.Socket et tls.TLSSocket existants exposés par Node.js, ni QuicSocket ni QuicSession ne sont Readable ou Writable flux. Autrement dit, vous ne pouvez pas utiliser un objet pour envoyer des données directement ou recevoir des données d'un homologue connecté, vous devez donc utiliser un objet QuicStream.

Dans l'exemple ci-dessus, un QuicSocket est créé et lié au port UDP local 5678. Dites ensuite à ceci QuicSocket d'écouter les nouvelles connexions QUIC à démarrer. Une fois que QuicSocket commence à écouter, l'événement ready sera émis.

Lorsqu'une nouvelle connexion QUIC est démarrée et que l'objet QuicSession correspondant au serveur est créé, l'événement session sera émis. L'objet QuicSession créé peut être utilisé pour écouter les nouvelles instances QuicStream initiées par le client-serveur.

L'une des fonctionnalités les plus importantes du protocole QUIC est que le client peut démarrer une nouvelle connexion au serveur sans ouvrir le flux initial, et le serveur peut démarrer son premier sans attendre le flux initial du client. propre flux. Cette fonctionnalité permet de nombreux gameplay très intéressants qui ne sont pas possibles avec HTTP 1 et HTTP 2 dans le noyau Node.js actuel.

Créer un client QUIC

Il n'y a quasiment aucune différence entre un client QUIC et un serveur :

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

翻译:疯狂的技术宅

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

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer