Maison  >  Article  >  interface Web  >  Comment communiquer en temps réel dans Node et React ?

Comment communiquer en temps réel dans Node et React ?

青灯夜游
青灯夜游avant
2020-09-12 10:55:064308parcourir

Comment communiquer en temps réel dans Node et React ?

Recommandations du tutoriel : Tutoriel node js, Tutoriel React, Tutoriel WebSocket

Le Web a parcouru un long chemin pour prendre en charge la communication full-duplex (ou bidirectionnelle) entre les clients et les serveurs. C'est l'objectif principal du protocole WebSocket : fournir une communication persistante en temps réel entre un client et un serveur via une seule connexion socket TCP.

Le protocole WebSocket n'a que deux objectifs : 1) Ouvrir la poignée de main, 2) Faciliter le transfert de données. Une fois la négociation réussie entre le serveur et le client, ils sont libres de s'envoyer des données avec peu de frais généraux.

La communication WebSocket s'effectue sur un seul socket TCP en utilisant le protocole WS (port 80) ou WSS (port 443). Selon Can I Use, au moment de la rédaction, presque tous les navigateurs, à l'exception d'Opera Mini, prennent en charge les WebSockets.

Statu quo

Historiquement, la création d'applications Web nécessitant une communication de données en temps réel (telles que des jeux ou des applications de chat) nécessitait un abus du protocole HTTP pour établir des données bidirectionnelles transfert. Bien qu'il existe de nombreuses façons d'implémenter des fonctionnalités en temps réel, aucune n'est aussi efficace que les WebSockets. Sondage HTTP, streaming HTTP, Comet, SSE : ils ont tous leurs inconvénients.

Interrogation HTTP

La première tentative pour résoudre le problème consiste à interroger périodiquement le serveur. Le cycle de vie de l'interrogation HTTP longue est le suivant :

  1. Le client fait une requête et attend une réponse.
  2. Le serveur tarde à répondre jusqu'à ce qu'un changement, une mise à jour ou un délai d'attente se produise. La demande reste "en attente" jusqu'à ce que le serveur renvoie quelque chose au client.
  3. Lorsqu'il y a des modifications ou des mises à jour côté serveur, il renvoie la réponse au client.
  4. Le client envoie une nouvelle longue demande d'interrogation pour écouter la prochaine série de modifications.

Il existe de nombreuses vulnérabilités dans les interrogations longues : surcharge d'en-tête, latence, délais d'attente, mise en cache, etc.

Diffusion HTTP

Ce mécanisme réduit les problèmes de latence du réseau car la requête initiale reste ouverte indéfiniment. Même une fois que le serveur a envoyé les données, la requête ne se termine jamais. Les trois premières étapes de la méthode du cycle de vie dans le flux HTTP sont les mêmes que dans l'interrogation HTTP.

Cependant, lorsque la réponse est renvoyée au client, la requête ne se termine jamais, le serveur maintient la connexion ouverte et envoie de nouvelles mises à jour lorsque des changements se produisent.

Événements envoyés par le serveur (SSE)

À l'aide de SSE, le serveur transmet les données au client. Les applications de chat ou de jeux ne peuvent pas s’appuyer entièrement sur SSE. Le cas d'utilisation parfait pour SSE est quelque chose comme le fil d'actualité de Facebook : chaque fois que de nouveaux messages sont publiés, le serveur les pousse vers la chronologie. SSE est envoyé via HTTP traditionnel et a une limite sur le nombre de connexions ouvertes.

Non seulement ces méthodes sont inefficaces, mais le code pour les maintenir est également fatiguant pour les développeurs.

WebSocket

Les WebSockets sont conçus pour remplacer les technologies de communication bidirectionnelles existantes. Les méthodes existantes décrites ci-dessus ne sont ni fiables ni efficaces en matière de communications en temps réel en duplex intégral.

Les WebSockets sont similaires à SSE mais sont également excellents pour transmettre les messages du client au serveur. Étant donné que les données sont fournies via une seule connexion socket TCP, les limitations de connexion ne sont plus un problème.


Tutoriel pratique

Comme mentionné en introduction, le protocole WebSocket n'a que deux agendas. Voyons comment WebSockets implémente ces agendas. Pour ce faire, je vais profiler un serveur Node.js et le connecter à un client construit avec React.js.

Agenda 1 : WebSocket établit une poignée de main entre le serveur et le client

Création d'une poignée de main au niveau du serveur

Nous pouvons utiliser un seul port pour fournir respectivement le service HTTP et le service WebSocket. Le code ci-dessous montre la création d'un simple serveur HTTP. Une fois créé, nous allons lier le serveur WebSocket au port HTTP :

const webSocketsServerPort = 8000;
const webSocketServer = require('websocket').server;
const http = require('http');
// Spinning the http server and the websocket server.
const server = http.createServer();
server.listen(webSocketsServerPort);
const wsServer = new webSocketServer({
  httpServer: server
});

Après avoir créé le serveur WebSocket, nous devons accepter la poignée de main lors de la réception d'une requête d'un client. J'enregistre tous les clients connectés en tant qu'objets dans le code et j'utilise l'ID utilisateur unique lors de la réception des demandes du navigateur.

// I'm maintaining all active connections in this object
const clients = {};

// This code generates unique userid for everyuser.
const getUniqueID = () => {
  const s4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
  return s4() + s4() + '-' + s4();
};

wsServer.on('request', function(request) {
  var userID = getUniqueID();
  console.log((new Date()) + ' Recieved a new connection from origin ' + request.origin + '.');
  // You can rewrite this part of the code to accept only the requests from allowed origin
  const connection = request.accept(null, request.origin);
  clients[userID] = connection;
  console.log('connected: ' + userID + ' in ' + Object.getOwnPropertyNames(clients))
});

Alors, que se passe-t-il lorsqu'une connexion est acceptée ?

Lors de l'envoi d'une requête HTTP régulière pour établir une connexion, dans l'en-tête de la requête, le client envoie *Sec-WebSocket-Key*. Le serveur code et hache cette valeur et ajoute le GUID prédéfini. Il répond à la valeur générée dans *Sec-WebSocket-Accept* dans la poignée de main envoyée par le serveur.

Une fois la demande acceptée dans le serveur (après validation nécessaire), la poignée de main est complétée par le code d'état 101. Si vous voyez autre chose que le code d'état 101 dans votre navigateur, cela signifie que la mise à niveau de WebSocket a échoué et que la sémantique HTTP normale sera suivie.

*Sec-WebSocket-Accept* 头字段指示服务器是否愿意接受连接。此外如果响应缺少 *Upgrade* 头字段,或者 *Upgrade* 不等于 websocket,则表示 WebSocket 连接失败。

成功的服务器握手如下所示:

HTTP GET ws://127.0.0.1:8000/ 101 Switching Protocols
Connection: Upgrade
Sec-WebSocket-Accept: Nn/XHq0wK1oO5RTtriEWwR4F7Zw=
Upgrade: websocket

在客户端级别创建握手

在客户端,我使用与服务器中的相同 WebSocket 包来建立与服务器的连接(Web IDL 中的 WebSocket API 正在由W3C 进行标准化)。一旦服务器接受请求,我们将会在浏览器控制台上看到 WebSocket Client Connected

这是创建与服务器的连接的初始脚手架:

import React, { Component } from 'react';
import { w3cwebsocket as W3CWebSocket } from "websocket";

const client = new W3CWebSocket('ws://127.0.0.1:8000');

class App extends Component {
  componentWillMount() {
    client.onopen = () => {
      console.log('WebSocket Client Connected');
    };
    client.onmessage = (message) => {
      console.log(message);
    };
  }
  
  render() {
    return (
      <div>
        Practical Intro To WebSockets.
      </div>
    );
  }
}

export default App;

客户端发送以下标头来建立握手:

HTTP GET ws://127.0.0.1:8000/ 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: vISxbQhM64Vzcr/CD7WHnw==
Origin: http://localhost:3000
Sec-WebSocket-Version: 13

现在客户端和服务器通过相互握手进行了连接,WebSocket 连接可以在接收消息时传输消息,从而实现 WebSocket 协议的第二个议程。

议程2:实时信息传输

Comment communiquer en temps réel dans Node et React ?

我将编写一个基本的实时文档编辑器,用户可以将它们连接在一起并编辑文档。我跟踪了两个事件:

  1. 用户活动:每次用户加入或离开时,我都会将消息广播给所有连接其他的客户端。
  2. 内容更改:每次修改编辑器中的内容时,都会向所有连接的其他客户端广播。

该协议允许我们用二进制数据或 UTF-8 发送和接收消息(注意:传输和转换 UTF-8 的开销较小)。

只要我们对套接字事件onopenoncloseonmessage有了充分的了解,理解和实现 WebSockets 就非常简单。客户端和服务器端的术语相同。

在客户端发送和接收消息

在客户端,当新用户加入或内容更改时,我们用 client.send 向服务器发消息,以将新信息提供给服务器。

/* When a user joins, I notify the
server that a new user has joined to edit the document. */
logInUser = () => {
  const username = this.username.value;
  if (username.trim()) {
    const data = {
      username
    };
    this.setState({
      ...data
    }, () => {
      client.send(JSON.stringify({
        ...data,
        type: "userevent"
      }));
    });
  }
}

/* When content changes, we send the
current content of the editor to the server. */
onEditorStateChange = (text) => {
 client.send(JSON.stringify({
   type: "contentchange",
   username: this.state.username,
   content: text
 }));
};

我们跟踪的事件是:用户加入和内容更改。

从服务器接收消息非常简单:

componentWillMount() {
  client.onopen = () => {
   console.log(&#39;WebSocket Client Connected&#39;);
  };
  client.onmessage = (message) => {
    const dataFromServer = JSON.parse(message.data);
    const stateToChange = {};
    if (dataFromServer.type === "userevent") {
      stateToChange.currentUsers = Object.values(dataFromServer.data.users);
    } else if (dataFromServer.type === "contentchange") {
      stateToChange.text = dataFromServer.data.editorContent || contentDefaultMessage;
    }
    stateToChange.userActivity = dataFromServer.data.userActivity;
    this.setState({
      ...stateToChange
    });
  };
}

在服务器端发送和侦听消息

在服务器中,我们只需捕获传入的消息并将其广播到连接到 WebSocket 的所有客户端。这是臭名昭着的 Socket.IO 和 WebSocket 之间的差异之一:当我们使用 WebSockets 时,我们需要手动将消息发送给所有客户端。  Socket.IO 是一个成熟的库,所以它自己来处理。

const sendMessage = (json) => {
  // We are sending the current data to all connected clients
  Object.keys(clients).map((client) => {
    clients[client].sendUTF(json);
  });
}

connection.on(&#39;message&#39;, function(message) {
    if (message.type === &#39;utf8&#39;) {
      const dataFromClient = JSON.parse(message.utf8Data);
      const json = { type: dataFromClient.type };
      if (dataFromClient.type === typesDef.USER_EVENT) {
        users[userID] = dataFromClient;
        userActivity.push(`${dataFromClient.username} joined to edit the document`);
        json.data = { users, userActivity };
      } else if (dataFromClient.type === typesDef.CONTENT_CHANGE) {
        editorContent = dataFromClient.content;
        json.data = { editorContent, userActivity };
      }
      sendMessage(JSON.stringify(json));
    }
  });

将消息广播到所有连接的客户端。

Comment communiquer en temps réel dans Node et React ?

浏览器关闭后会发生什么?

在这种情况下,WebSocket调用 close 事件,它允许我们编写终止当前用户连接的逻辑。在我的代码中,当用户离开文档时,会向其余用户广播消息:

connection.on(&#39;close&#39;, function(connection) {
    console.log((new Date()) + " Peer " + userID + " disconnected.");
    const json = { type: typesDef.USER_EVENT };
    userActivity.push(`${users[userID].username} left the document`);
    json.data = { users, userActivity };
    delete clients[userID];
    delete users[userID];
    sendMessage(JSON.stringify(json));
  });

该应用程序的源代码位于GitHub上的 repo 中。

结论

WebSockets 是在应用中实现实时功能的最有趣和最方便的方法之一。它为我们提供了能够充分利用全双工通信的灵活性。我强烈建议在尝试使用 Socket.IO 和其他可用库之前先试试 WebSockets。

编码快乐!

更多编程相关知识,请访问:编程教学!!

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