Il s'agit du cinquième article d'une série consacrée à l'exploration de JavaScript et des composants sur lesquels il est construit.
Recommandations d'apprentissage gratuites associées : javascript(vidéo)
Cette fois, nous allons plonger dans le domaine des protocoles de communication, cartographier et explorer leurs propriétés, et construire certains composants en cours de route. Comparaison rapide entre WebSockets et HTTP/2. Enfin, nous partageons quelques conseils sur la façon de choisir un protocole réseau.
Introduction
Aujourd'hui, les applications Web complexes dotées d'une interface utilisateur riche en fonctionnalités et dynamique sont considérées comme allant de soi. Ce n’est pas une surprise : Internet a parcouru un long chemin depuis sa création.
À l'origine, Internet n'a pas été conçu pour prendre en charge des applications Web aussi dynamiques et complexes. Il est considéré comme un ensemble de pages HTML liées les unes aux autres pour former un concept « Web » contenant des informations. Tout est construit autour du paradigme dit de requête/réponse de HTTP. Le client charge une page, puis rien ne se passe jusqu'à ce que l'utilisateur clique et navigue vers la page suivante.
Vers 2005, AJAX a été introduit et de nombreuses personnes ont commencé à explorer la possibilité d'établir une connexion bidirectionnelle entre le client et le serveur. Néanmoins, toutes les communications HTTP sont dirigées par le client, ce qui nécessite une interaction de l'utilisateur ou une interrogation périodique pour charger de nouvelles données depuis le serveur.
Faire de HTTP une interaction « bidirectionnelle »
Les technologies qui permettent aux serveurs d'envoyer « activement » des données aux clients existent depuis un certain temps. Par exemple « Push » et « Comet ».
L'un des hacks les plus courants consiste à donner au serveur l'illusion qu'il doit envoyer des données au client, c'est ce qu'on appelle un long sondage. Avec une interrogation longue, le client ouvre une connexion HTTP au serveur et la maintient ouverte jusqu'à ce qu'une réponse soit envoyée. Ceci est envoyé en réponse chaque fois que le serveur a de nouvelles données à envoyer.
Regardez à quoi ressemble un long extrait de code d'interrogation très simple :
(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); })();
Il s'agit essentiellement d'une fonction auto-exécutable qui définit 10 secondes la première fois qu'elle s'exécute immédiatement. Intervalle, après chaque Ajax asynchrone appelez le serveur, le rappel appellera à nouveau Ajax.
D'autres techniques impliquent des requêtes multipart Flash ou XHR et des fichiers dits html.
Cependant, tous ces espaces de travail ont le même problème : ils sont tous accompagnés de la surcharge de HTTP, ce qui les rend inadaptés aux applications à faible latence. Pensez à un jeu de tir à la première personne multijoueur dans le navigateur ou à tout autre jeu en ligne avec un composant en temps réel.
Introduction des WebSockets
La spécification WebSocket définit une API permettant d'établir une connexion "socket" entre un navigateur Web et un serveur. En termes simples : il existe une connexion durable entre le client et le serveur, et les deux parties peuvent commencer à envoyer des données à tout moment.
Le client établit une connexion WebSocket via le processus WebSocket handshake. Le processus commence par l'envoi par le client d'une requête HTTP régulière au serveur, qui contient un en-tête Upgrade qui informe le serveur que le client souhaite établir une connexion WebSocket.
Le client établit une connexion WebSocket comme suit :
// Create a new WebSocket with an encrypted connection. var socket = new WebSocket('ws://websocket.example.com')
L'URL WebSocket utilise le schéma ws. Il existe également wss pour les connexions WebSocket sécurisées, équivalentes au HTTPS.
Cette solution n'est que le début de l'ouverture d'une connexion WebSocket à websocket.example.com.
Voici un exemple simplifié de l'en-tête de requête initial :
Si le serveur prend en charge le protocole WebSocket, il acceptera la mise à niveau et réussira la mise à niveau. en-tête dans la réponse communiquer.
Implémentation de Node.js :
Une fois la connexion établie, le serveur répond en mettant à niveau le contenu dans l'en-tête :
Une fois la connexion établie, l'événement open sera déclenché sur l'instance client WebSocket :
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.'); };
Maintenant que la poignée de main est terminée, la connexion HTTP initiale est connectée au WebSocket en utilisant la même connexion TCP/IP sous-jacente est remplacée. À ce stade, les deux parties peuvent commencer à envoyer des données.
使用 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 版本中,每个包前面只有一个报头。然而,这是一个相当复杂的报头。以下是它的构建模块:
FIN
:1bit ,表示是消息的最后一帧,如果消息只有一帧那么第一帧也就是最后一帧,Firefox 在 32K 之后创建了第二个帧。RSV1,RSV2,RSV3
:每个1bit,必须是0,除非扩展定义为非零。如果接受到的是非零值但是扩展没有定义,则需要关闭连接。Opcode
:4bit,解释 Payload 数据,规定有以下不同的状态,如果是未知的,接收方必须马上关闭连接。状态如下:
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 连接中每个帧中的数据:
消息分片
有效载荷数据可以分成多个单独的帧。接收端应该对它们进行缓冲,直到设置好 fin
位。因此,可以将字符串“Hello World”发送到11个包中,每个包的长度为6(报头长度)+ 1字节。控件包不允许分片。但是,规范希望能够处理交错的控制帧。这是TCP包以任意顺序到达的情况。
连接帧的逻辑大致如下:
分片目的是发送长度未知的消息。如果不分片发送,即一帧,就需要缓存整个消息,计算其长度,构建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 连接可以包含多个并发打开的流,任何一个端点都可以从多个流中交错帧。
SSE est basé sur HTTP, ce qui signifie que dans HTTP/2, non seulement plusieurs flux SSE peuvent être entrelacés sur une seule connexion TCP, mais également plusieurs flux SSE (push serveur à client) et plusieurs clients peuvent être côte à côte. requête (client vers serveur). Grâce à HTTP/2 et SSE, il existe désormais une connexion bidirectionnelle HTTP pure et une API simple permettant au code d'application de s'enregistrer auprès du service push du serveur. Lorsque l'on compare SSE et WebSocket, le manque de capacités bidirectionnelles est souvent cité comme un inconvénient majeur. Avec HTTP/2, ce n'est plus le cas. Cela vous permet d'ignorer WebSocket et de vous en tenir au mécanisme de signalisation basé sur HTTP.
Comment choisir WebSocket et HTTP/2 ?
Les WebSockets survivront dans le domaine de HTTP/2 + SSE, principalement parce que c'est une technologie qui a été largement used Une technologie bien implémentée, et dans des cas d'utilisation très spécifiques, elle présente des avantages par rapport à HTTP/2 car elle a été conçue pour une fonctionnalité bidirectionnelle avec moins de surcharge (comme les en-têtes).
Supposons que vous construisiez un jeu en ligne massivement multijoueur qui nécessite un grand nombre de messages des deux extrémités de la connexion. Dans ce cas, les WebSockets fonctionnent bien mieux.
En général, utilisez WebSockets chaque fois que vous avez besoin d'une connexion vraiment à faible latence, quasiment en temps réel, entre le client et le serveur, ce qui peut nécessiter de repenser la façon dont vous créez des applications côté serveur, ainsi que se concentrer sur des technologies telles que les événements en file d’attente.
Si vous utilisez une solution qui doit afficher des actualités du marché en temps réel, des données de marché, des applications de chat, etc., s'appuyer sur HTTP/2 + SSE vous fournira un canal de communication bidirectionnel efficace tout en obtenant les différents avantages de rester dans le domaine HTTP :
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!