教程推荐:node js教程、React教程、WebSocket教程
Web 为了支持客户端和服务器之间的全双工(或双向)通信已经走过了很长的路。这是 WebSocket 协议的主要目的:通过单个 TCP 套接字连接在客户端和服务器之间提供持久的实时通信。
WebSocket 协议只有两个议程:1)打开握手,2)帮助数据传输。一旦服务器和客户端握手成功,他们就可以随意地以较少的开销相互发送数据。
WebSocket 通信使用WS(端口80)或WSS(端口443)协议在单个 TCP 套接字上进行。根据 Can I Use,撰写本文时除了 Opera Mini 之外几乎所有的浏览器支持 WebSockets 。
现状
从历史上看,创建需要实时数据通讯(如游戏或聊天应用程序)的 Web 应用需要滥用 HTTP 协议来建立双向数据传输。尽管有许多种方法用于实现实时功能,但没有一种方法与 WebSockets 一样高效。 HTTP 轮询、HTTP流、Comet、SSE —— 它们都有自己的缺点。
HTTP 轮询
解决问题的第一个尝试是定期轮询服务器。 HTTP 长轮询生命周期如下:
- 客户端发出请求并一直等待响应。
- 服务器推迟响应,直到发生更改、更新或超时。请求保持“挂起”,直到服务器有东西返回客户端。
- 当服务器端有一些更改或更新时,它会将响应发送回客户端。
- 客户端发送新的长轮询请求以侦听下一组更改。
长轮询中存在很多漏洞 —— 标头开销、延迟、超时、缓存等等。
HTTP 流式传输
这种机制减少了网络延迟的痛苦,因为初始请求无限期地保持打开状态。即使在服务器推送数据之后,请求也永远不会终止。 HTTP 流中的前三步生命周期方法与 HTTP 轮询是相同的。
但是,当响应被发送回客户端时,请求永远不会终止,服务器保持连接打开状态,并在发生更改时发送新的更新。
服务器发送事件(SSE)
使用 SSE,服务器将数据推送到客户端。聊天或游戏应用不能完全依赖 SSE。 SSE 的完美用例是类似 Facebook 的新闻 Feed:每当有新帖发布时,服务器会将它们推送到时间线。 SSE 通过传统 HTTP 发送,并且对打开的连接数有限制。
这些方法不仅效率低下,维护它们的代码也使开发人员感到厌倦。
WebSocket
WebSockets 旨在取代现有的双向通信技术。当涉及全双工实时通信时,上述现有方法既不可靠也不高效。
WebSockets 类似于 SSE,但在将消息从客户端传回服务器方面也很优秀。由于数据是通过单个 TCP 套接字连接提供的,因此连接限制不再是问题。
实战教程
正如介绍中所提到的,WebSocket 协议只有两个议程。让我们看看 WebSockets 如何实现这些议程。为此我将分析一个 Node.js 服务器并将其连接到使用 React.js 构建的客户端上。
议程1:WebSocket在服务器和客户端之间建立握手
在服务器级别创建握手
我们可以用单个端口来分别提供 HTTP 服务和 WebSocket 服务。下面的代码显示了一个简单的 HTTP 服务器的创建过程。一旦创建,我们会将 WebSocket 服务器绑定到 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 });
创建 WebSocket 服务器后,我们需要在接收来自客户端的请求时接受握手。我将所有连接的客户端作为对象保存在代码中,并在收请从浏览器发来的求时使用唯一的用户ID。
// 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)) });
那么,当接受连接时会发生什么?
在发送常规 HTTP 请求以建立连接时,在请求头中,客户端发送 *Sec-WebSocket-Key*
。服务器对此值进行编码和散列,并添加预定义的 GUID。它回应了服务器发送的握手中 *Sec-WebSocket-Accept*
中生成的值。
一旦请求在服务器中被接受(在必要验证之后),就完成了握手,其状态代码为 101
。如果在浏览器中看到除状态码 101
之外的任何内容,则意味着 WebSocket 升级失败,并且将遵循正常的 HTTP 语义。
*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:实时信息传输
我将编写一个基本的实时文档编辑器,用户可以将它们连接在一起并编辑文档。我跟踪了两个事件:
- 用户活动:每次用户加入或离开时,我都会将消息广播给所有连接其他的客户端。
- 内容更改:每次修改编辑器中的内容时,都会向所有连接的其他客户端广播。
该协议允许我们用二进制数据或 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。
编码快乐!
更多编程相关知识,请访问:编程教学!!
以上是Node和React中如何进行实时通信?的详细内容。更多信息请关注PHP中文网其他相关文章!

node、nvm与npm的区别:1、nodejs是项目开发时所需要的代码库,nvm是nodejs版本管理工具,npm是nodejs包管理工具;2、nodejs能够使得javascript能够脱离浏览器运行,nvm能够管理nodejs和npm的版本,npm能够管理nodejs的第三方插件。

Vercel是什么?本篇文章带大家了解一下Vercel,并介绍一下在Vercel中部署 Node 服务的方法,希望对大家有所帮助!

node怎么爬取数据?下面本篇文章给大家分享一个node爬虫实例,聊聊利用node抓取小说章节的方法,希望对大家有所帮助!

node导出模块的两种方式:1、利用exports,该方法可以通过添加属性的方式导出,并且可以导出多个成员;2、利用“module.exports”,该方法可以直接通过为“module.exports”赋值的方式导出模块,只能导出单个成员。

安装node时会自动安装npm;npm是nodejs平台默认的包管理工具,新版本的nodejs已经集成了npm,所以npm会随同nodejs一起安装,安装完成后可以利用“npm -v”命令查看是否安装成功。

node中没有包含dom和bom;bom是指浏览器对象模型,bom是指文档对象模型,而node中采用ecmascript进行编码,并且没有浏览器也没有文档,是JavaScript运行在后端的环境平台,因此node中没有包含dom和bom。

Node.js 如何实现异步资源上下文共享?下面本篇文章给大家介绍一下Node实现异步资源上下文共享的方法,聊聊异步资源上下文共享对我们来说有什么用,希望对大家有所帮助!


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

WebStorm Mac版
好用的JavaScript开发工具

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

DVWA
Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

禅工作室 13.0.1
功能强大的PHP集成开发环境