ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript について WebSocket と SSE を使用した HTTP/2 を詳しく掘り下げ、正しいパスを選択する方法を説明します。

JavaScript について WebSocket と SSE を使用した HTTP/2 を詳しく掘り下げ、正しいパスを選択する方法を説明します。

coldplay.xixi
coldplay.xixi転載
2020-12-21 17:33:453053ブラウズ

これは、JavaScript とその基盤となるコンポーネントを詳しく説明するシリーズの 5 番目の記事です。

JavaScript について WebSocket と SSE を使用した HTTP/2 を詳しく掘り下げ、正しいパスを選択する方法を説明します。

# 関連する無料学習の推奨事項:

javascript (ビデオ)

今回は、通信プロトコルの領域を掘り下げ、そのプロパティをマッピングして調査し、そのプロセスでいくつかのコンポーネントを構築します。 WebSocket と HTTP/2 の簡単な比較。最後に、ネットワーク プロトコルを選択する方法に関するヒントをいくつか紹介します。

はじめに

今日、豊富な機能を備えた動的な UI を備えた複雑な Web アプリケーションは当然のことと考えられています。それは驚くべきことではありません。インターネットはその誕生以来、長い道のりを歩んできました。

元々、インターネットは、このような動的で複雑な Web アプリケーションをサポートするために構築されたものではありませんでした。これは、情報を含む「Web」概念を形成するために相互にリンクされている HTML ページのコレクションであると考えられます。すべては、HTTP のいわゆるリクエスト/レスポンス パラダイムを中心に構築されています。クライアントがページをロードすると、ユーザーがクリックして次のページに移動するまで何も起こりません。

2005 年頃、AJAX が導入され、多くの人々がクライアントとサーバー間の双方向接続を確立する可能性を模索し始めました。それにもかかわらず、すべての HTTP 通信はクライアントによって指示されるため、サーバーから新しいデータをロードするにはユーザーの操作または定期的なポーリングが必要です。

HTTP を「双方向」対話にする

サーバーがクライアントにデータを「アクティブに」送信できるようにするテクノロジーは、かなり前から存在しています。例えば「プッシュ」や「コメット」など。

最も一般的なハッキング手法の 1 つは、サーバーにデータをクライアントに送信する必要があると錯覚させることであり、これは

ロング ポーリング と呼ばれます。ロングポーリングでは、クライアントはサーバーへの HTTP 接続を開き、応答が送信されるまで開いたままにします。これは、サーバーに送信する新しいデータがあるたびに、応答として送信されます。

非常に単純なロング ポーリングのコード スニペットがどのようなものかを見てください:

(function poll(){
   setTimeout(function(){
      $.ajax({ 
        url: 'https://api.example.com/endpoint', 
        success: function(data) {
          // Do something with `data`
          // ...

          //Setup the next poll recursively
          poll();
        }, 
        dataType: 'json'
      });
  }, 10000);
})();
これは基本的に、初回実行時に 10 秒を設定する自動実行関数です。すぐに実行される間隔、Ajax が非同期になるたびに実行されます。サーバーを呼び出すと、コールバックは再び Ajax を呼び出します。

他のテクノロジーには、Flash または XHR マルチパート リクエスト、およびいわゆる html ファイルが含まれます。

ただし、これらのワークスペースにはすべて同じ問題があります。つまり、HTTP のオーバーヘッドが伴うため、低遅延アプリケーションには適していません。ブラウザ内マルチプレイヤーの一人称シューティング ゲームや、リアルタイム コンポーネントを備えたその他のオンライン ゲームを考えてください。

WebSocket の概要

WebSocket 仕様は、Web ブラウザとサーバーの間に「ソケット」接続を確立するための API を定義します。簡単に言うと、クライアントとサーバーの間には長期間の接続があり、双方がいつでもデータの送信を開始できます。

JavaScript について WebSocket と SSE を使用した HTTP/2 を詳しく掘り下げ、正しいパスを選択する方法を説明します。

クライアントは、WebSocket

Handshake プロセスを通じて WebSocket 接続を確立します。このプロセスは、クライアントが通常の HTTP リクエストをサーバーに送信することから始まります。このリクエストには、クライアントが WebSocket 接続の確立を希望していることをサーバーに通知する Upgrade ヘッダーが含まれています。

クライアントは次のように WebSocket 接続を確立します。

// Create a new WebSocket with an encrypted connection.
var socket = new WebSocket('ws://websocket.example.com')
WebSocket URL は ws スキームを使用します。 HTTPS と同等の安全な WebSocket 接続用の wss もあります。
このソリューションは、websocket.example.com への WebSocket 接続を開くほんの始まりにすぎません。

これは、最初のリクエスト ヘッダーの簡略化された例です:

JavaScript について WebSocket と SSE を使用した HTTP/2 を詳しく掘り下げ、正しいパスを選択する方法を説明します。

サーバーが WebSocket プロトコルをサポートしている場合、サーバーはアップグレードに同意し、アップグレードを渡します。レスポンスのヘッダーで通信します。

Node.js 実装:

JavaScript について WebSocket と SSE を使用した HTTP/2 を詳しく掘り下げ、正しいパスを選択する方法を説明します。

接続が確立されると、サーバーはヘッダーのコンテンツを更新して応答します:

JavaScript について WebSocket と SSE を使用した HTTP/2 を詳しく掘り下げ、正しいパスを選択する方法を説明します。

接続が確立されると、クライアント WebSocket インスタンスで open イベントが発生します:

var socket = new WebSocket('ws://websocket.example.com');

// Show a connected message when the WebSocket is opened.
socket.onopen = function(event) {
  console.log('WebSocket is connected.');
};
ハンドシェイクが完了したので、最初の HTTP 接続は次を使用して WebSocket に接続されます。同じ基礎となる TCP/IP 接続が置き換えられます。この時点で、双方がデータの送信を開始できます。

使用 WebSockets,可以传输任意数量的数据,而不会产生与传统 HTTP 请求相关的开销。数据作为消息通过 WebSocket 传输,每个消息由一个或多个帧组成,其中包含正在发送的数据(有效负载)。为了确保消息在到达客户端时能够正确地进行重构,每一帧都以负载的4-12字节数据为前缀, 使用这种基于帧的消息传递系统有助于减少传输的非有效负载数据量,从而大大的减少延迟。

注意:值得注意的是,只有在接收到所有帧并重构了原始消息负载之后,客户机才会收到关于新消息的通知。

WebSocket URLs

之前简要提到过 WebSockets 引入了一个新的URL方案。实际上,他们引入了两个新的方案:ws:// 和wss://。

url 具有特定方案的语法。WebSocket url 的特殊之处在于它们不支持锚点(#sample_anchor)。

同样的规则适用于 WebSocket 风格的url和 HTTP 风格的 url。ws 是未加密的,默认端口为80,而 wss 需要TLS加密,默认端口为 443。

帧协议

更深入地了解帧协议,这是 RFC 为我们提供的:

在RFC 指定的 WebSocket 版本中,每个包前面只有一个报头。然而,这是一个相当复杂的报头。以下是它的构建模块:

JavaScript について WebSocket と SSE を使用した HTTP/2 を詳しく掘り下げ、正しいパスを選択する方法を説明します。

  • FIN :1bit ,表示是消息的最后一帧,如果消息只有一帧那么第一帧也就是最后一帧,Firefox 在 32K 之后创建了第二个帧。

  • RSV1,RSV2,RSV3:每个1bit,必须是0,除非扩展定义为非零。如果接受到的是非零值但是扩展没有定义,则需要关闭连接。

  • Opcode:4bit,解释 Payload 数据,规定有以下不同的状态,如果是未知的,接收方必须马上关闭连接。状态如下:

    • 0x00: 附加数据帧
    • 0x01:文本数据帧  
    • 0x02:二进制数据帧    
    • 0x3-7:保留为之后非控制帧使用
    • 0x8:关闭连接帧
    • 0x9:ping
    • 0xA:pong
    • 0xB-F(保留为后面的控制帧使用)      
  • Mask:1bit,掩码,定义payload数据是否进行了掩码处理,如果是1表示进行了掩码处理。

  • Masking-key:域的数据即是掩码密钥,用于解码PayloadData。客户端发出的数据帧需要进行掩码处理,所以此位是1。

  • Payload_len:7位,7 + 16位,7+64位,payload数据的长度,如果是0-125,就是真实的payload长度,如果是126,那么接着后面的2个字节对应的16位无符号整数就是payload数据长度;如果是127,那么接着后面的8个字节对应的64位无符号整数就是payload数据的长度。

  • Masking-key:0到4字节,如果MASK位设为1则有4个字节的掩码解密密钥,否则就没有。

  • Payload data:任意长度数据。包含有扩展定义数据和应用数据,如果没有定义扩展则没有此项,仅含有应用数据。

为什么 WebSocket 是基于帧而不是基于流?我不知道,就像你一样,我很想了解更多,所以如果你有想法,请随时在下面的回复中添加评论和资源。另外,关于这个主题的讨论可以在 HackerNews 上找到。

帧数据

如上所述,数据可以被分割成多个帧。 传输数据的第一帧有一个操作码,表示正在传输什么类型的数据。 这是必要的,因为 JavaScript 在开始规范时几乎不存在对二进制数据的支持。 0x01 表示 utf-8 编码的文本数据,0x02 是二进制数据。大多数人会发送 JSON ,在这种情况下,你可能要选择文本操作码。 当你发送二进制数据时,它将在浏览器特定的 Blob 中表示。

通过 WebSocket 发送数据的API非常简单:

var socket = new WebSocket('ws://websocket.example.com');
socket.onopen = function(event) {
  socket.send('Some message'); // Sends data to server.
};

当 WebSocket 接收数据时(在客户端),会触发一个消息事件。此事件包括一个名为data的属性,可用于访问消息的内容。

// Handle messages sent by the server.
socket.onmessage = function(event) {
  var message = event.data;
  console.log(message);
};

在Chrome开发工具:可以很容易地观察 WebSocket 连接中每个帧中的数据:

JavaScript について WebSocket と SSE を使用した HTTP/2 を詳しく掘り下げ、正しいパスを選択する方法を説明します。

消息分片

有效载荷数据可以分成多个单独的帧。接收端应该对它们进行缓冲,直到设置好 fin 位。因此,可以将字符串“Hello World”发送到11个包中,每个包的长度为6(报头长度)+ 1字节。控件包不允许分片。但是,规范希望能够处理交错的控制帧。这是TCP包以任意顺序到达的情况。

连接帧的逻辑大致如下:

  • 接收第一帧
  • 记住操作码
  • 将帧有效负载连接在一起,直到 fin 位被设置
  • 断言每个包的操作码是零

分片目的是发送长度未知的消息。如果不分片发送,即一帧,就需要缓存整个消息,计算其长度,构建frame并发送;使用分片的话,可使用一个大小合适的buffer,用消息内容填充buffer,填满即发送出去。

什么是跳动检测?

主要目的是保障客户端 websocket 与服务端连接状态,该程序有心跳检测及自动重连机制,当网络断开或者后端服务问题造成客户端websocket断开,程序会自动尝试重新连接直到再次连接成功。

在使用原生websocket的时候,如果设备网络断开,不会触发任何函数,前端程序无法得知当前连接已经断开。这个时候如果调用 websocket.send 方法,浏览器就会发现消息发不出去,便会立刻或者一定短时间后(不同浏览器或者浏览器版本可能表现不同)触发 onclose 函数。

后端 websocket 服务也可能出现异常,连接断开后前端也并没有收到通知,因此需要前端定时发送心跳消息 ping,后端收到 ping 类型的消息,立马返回 pong 消息,告知前端连接正常。如果一定时间没收到pong消息,就说明连接不正常,前端便会执行重连。

为了解决以上两个问题,以前端作为主动方,定时发送 ping 消息,用于检测网络和前后端连接问题。一旦发现异常,前端持续执行重连逻辑,直到重连成功。

错误处理

以通过监听 error 事件来处理所有错误:

var socket = new WebSocket('ws://websocket.example.com');

// Handle any error that occurs.
socket.onerror = function(error) {
  console.log('WebSocket Error: ' + error);
};

关闭连接

要关闭连接,客户机或服务器都应该发送包含操作码0x8的数据的控制帧。当接收到这样一个帧时,另一个对等点发送一个关闭帧作为响应,然后第一个对等点关闭连接,关闭连接后接收到的任何其他数据都将被丢弃:

// Close if the connection is open.
if (socket.readyState === WebSocket.OPEN) {
    socket.close();
}

另外,为了在完成关闭之后执行其他清理,可以将事件侦听器附加到关闭事件:

// Do necessary clean up.
socket.onclose = function(event) {
  console.log('Disconnected from WebSocket.');
};

服务器必须监听关闭事件以便在需要时处理它:

connection.on('close', function(reasonCode, description) {
    // The connection is getting closed.
});

WebSockets和HTTP/2 比较

虽然HTTP/2提供了很多功能,但它并没有完全满足对现有推送/流技术的需求。

关于 HTTP/2 的第一个重要的事情是它并不能替代所有的 HTTP 。verb、状态码和大部分头信息将保持与目前版本一致。HTTP/2 是意在提升数据在线路上传输的效率。

比较HTTP/2和WebSocket,可以看到很多相似之处:

正如我们在上面看到的,HTTP/2引入了 Server Push,它使服务器能够主动地将资源发送到客户机缓存。但是,它不允许将数据下推到客户机应用程序本身,服务器推送只由浏览器处理,不会在应用程序代码中弹出,这意味着应用程序没有API来获取这些事件的通知。

这就是服务器发送事件(SSE)变得非常有用的地方。SSE 是一种机制,它允许服务器在建立客户机-服务器连接之后异步地将数据推送到客户机。然后,只要有新的“数据块”可用,服务器就可以决定发送数据。它可以看作是单向发布-订阅模式。它还提供了一个名为 EventSource API 的标准JavaScript,作为W3C HTML5标准的一部分,在大多数现代浏览器中实现。不支持 EventSource API 的浏览器可以轻松地使用 polyfilled 方案来解决。

由于 SSE 基于 HTTP ,因此它与 HTTP/2 非常合适,可以结合使用以实现最佳效果:HTTP/2 处理基于多路复用流的高效传输层,SSE 将 API 提供给应用以启用数据推送。

为了理解 Streams  和 Multiplexing 是什么,首先看一下`IETF定义:“stream”是在HTTP/2 连接中客户机和服务器之间交换的独立的、双向的帧序列。它的一个主要特征是,一个HTTP/2 连接可以包含多个并发打开的流,任何一个端点都可以从多个流中交错帧。

JavaScript について WebSocket と SSE を使用した HTTP/2 を詳しく掘り下げ、正しいパスを選択する方法を説明します。

SSE は HTTP に基づいています。つまり、HTTP/2 では、複数の SSE ストリームを 1 つの TCP 接続にインターリーブできるだけでなく、複数の SSE ストリーム (サーバーからクライアントへのプッシュ) や複数のクライアントをサイドに配置することもできます。リクエスト (クライアントからサーバーへ)。 HTTP/2 と SSE により、純粋な HTTP 双方向接続と、アプリケーション コードをサーバー プッシュ サービスに登録するためのシンプルな API が使用できるようになりました。 SSE と WebSocket を比較する場合、双方向機能の欠如が大きな欠点としてよく挙げられます。 HTTP/2 では、これは当てはまりません。これにより、WebSocket をスキップして、HTTP ベースのシグナリング メカニズムを使用し続けることができます。

WebSocket と HTTP/2 の選択方法?

WebSocket は、主にすでに確立されたテクノロジであるため、HTTP/2 SSE の分野で生き残ることができます。また、非常に特殊な使用例では、オーバーヘッド (ヘッダーなど) が少ない双方向機能向けに構築されているため、HTTP/2 よりも利点があります。

接続の両端からの大量のメッセージを必要とする大規模マルチプレイヤー オンライン ゲームを構築しているとします。この場合、WebSocket のパフォーマンスが大幅に向上します。

一般に、真の 低レイテンシー、クライアントとサーバー間のほぼリアルタイムの接続が必要な場合は常に WebSocket を使用します。これには、サーバー側アプリケーションの構築方法の再考が必要になる場合があります。キューに入れられたイベントなどのテクノロジーに焦点を移します。

リアルタイムの市場ニュース、市場データ、チャット アプリケーションなどを表示する必要があるソリューションを使用している場合、HTTP/2 SSE に依存すると、効率的な双方向通信チャネルが提供され、同時にHTTP 領域にとどまることのさまざまな利点:

  • 既存の Web インフラストラクチャとの互換性を考慮する場合、WebSocket は HTTP 接続を HTTP とはまったく異なるプロトコルにアップグレードするため、多くの場合問題の原因になります。
  • スケールとセキュリティ: Web コンポーネント (ファイアウォール、侵入検知、ロード バランシング) は HTTP 上で構築、保守、構成されます。これは、復元力、セキュリティ、スケーラビリティの点で、大規模/重要なアプリケーションに最適です。良好な環境。

以上がJavaScript について WebSocket と SSE を使用した HTTP/2 を詳しく掘り下げ、正しいパスを選択する方法を説明します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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