Home > Article > Web Front-end > How to communicate in real time in Node and React?
Tutorial recommendations: node js tutorial, React tutorial, WebSocket tutorial
The Web has come a long way to support full-duplex (or bidirectional) communication between clients and servers. This is the main purpose of the WebSocket protocol: to provide persistent, real-time communication between a client and a server over a single TCP socket connection.
The WebSocket protocol has only two agendas: 1) opening the handshake, and 2) helping with data transfer. Once the server and client handshake successfully, they are free to send data to each other with little overhead.
WebSocket communication occurs over a single TCP socket using the WS (port 80) or WSS (port 443) protocol. According to Can I Use, at the time of writing almost all browsers except Opera Mini support WebSockets.
Historically, creating web applications that require real-time data communication (such as games or chat applications) has required abusing the HTTP protocol to establish two-way data transfer. While there are many ways to implement real-time functionality, none are as efficient as WebSockets. HTTP polling, HTTP streaming, Comet, SSE - they all have their drawbacks.
The first attempt to solve the problem is to poll the server periodically. The HTTP long polling life cycle is as follows:
There are many vulnerabilities in long polling - header overhead, latency, timeouts, caching, and more.
This mechanism reduces the pain of network latency because the initial request remains open indefinitely. Even after the server pushes the data, the request never terminates. The first three steps of the lifecycle method in the HTTP flow are the same as in HTTP polling.
However, when the response is sent back to the client, the request never terminates, the server keeps the connection open and sends new updates when changes occur.
Using SSE, the server pushes data to the client. Chat or gaming applications cannot rely entirely on SSE. The perfect use case for SSE is something like Facebook's news feed: whenever new posts are published, the server pushes them to the timeline. SSE is sent over traditional HTTP and has a limit on the number of open connections.
Not only are these methods inefficient, the code to maintain them is also tiring for developers.
WebSockets are designed to replace existing two-way communication technologies. The existing methods described above are neither reliable nor efficient when it comes to full-duplex real-time communications.
WebSockets are similar to SSE but are also excellent at passing messages from the client back to the server. Since data is provided over a single TCP socket connection, connection limitations are no longer an issue.
As mentioned in the introduction, the WebSocket protocol has only two agendas. Let’s see how WebSockets implements these agendas. To do this I will profile a Node.js server and connect it to a client built with React.js.
We can use a single port to provide HTTP service and WebSocket service respectively. The code below shows the creation of a simple HTTP server. Once created, we will bind the WebSocket server to the HTTP port:
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 });
After creating the WebSocket server, we need to accept the handshake when receiving requests from clients. I save all connected clients as objects in the code and use the unique user ID when receiving requests from the browser.
// 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)) });
So, what happens when a connection is accepted?
When sending a regular HTTP request to establish a connection, in the request header, the client sends *Sec-WebSocket-Key*
. The server encodes and hashes this value and adds the predefined GUID. It responds with the value generated in *Sec-WebSocket-Accept*
in the handshake sent by the server.
Once the request is accepted in the server (after necessary validation), the handshake is completed with status code 101
. If you see anything other than status code 101
in your browser, it means the WebSocket upgrade failed and normal HTTP semantics will be followed.
*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 协议的第二个议程。
我将编写一个基本的实时文档编辑器,用户可以将它们连接在一起并编辑文档。我跟踪了两个事件:
该协议允许我们用二进制数据或 UTF-8 发送和接收消息(注意:传输和转换 UTF-8 的开销较小)。
只要我们对套接字事件onopen
、onclose
和 onmessage
有了充分的了解,理解和实现 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('WebSocket Client Connected'); }; 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('message', function(message) { if (message.type === 'utf8') { 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)); } });
将消息广播到所有连接的客户端。
在这种情况下,WebSocket调用 close
事件,它允许我们编写终止当前用户连接的逻辑。在我的代码中,当用户离开文档时,会向其余用户广播消息:
connection.on('close', 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。
编码快乐!
更多编程相关知识,请访问:编程教学!!
The above is the detailed content of How to communicate in real time in Node and React?. For more information, please follow other related articles on the PHP Chinese website!