概述
如今,越来越多的应用程序基于实时通信的概念来传递信息。例如,聊天应用程序和视频会议。传统的 HTTP 速度很慢,并且依赖于客户端请求。实时通信也要求服务器在没有任何客户端请求的情况下与客户端进行通信。这带来了 Web 套接字的概念。Web 套接字有助于在客户端和服务器之间创建持久的实时通信。
什么是 Web 套接字?
Web 套接字可以称为传统 HTTP 的升级。Web 套接字是一种协议,可在客户端和服务器之间提供全双工或双向通信线路。
它是一种连续连接,允许以下操作:
- 每当发生任何事件时,数据都可以从服务器发送到客户端,而无需客户端发送任何请求。这称为服务器推送。插座可以被认为是邮递员。每当有任何带有您的地址的邮件时,邮递员都会将邮件发送给您,如果您有任何邮件,您无需经常致电邮局。这在必须通知客户端有关服务器上任何事件的应用程序中非常有用。如果不进行轮询,就无法在 HTTP 上有效地完成此操作。
- 在建立连接后,数据可以非常有效地向任何方向发送,因为 nodejs WebSocket 数据帧组织得很好,不需要每次都使用头。
设置 WebSocket 连接需要执行以下步骤:
- 客户端向服务器发送连接升级请求。这个 nodejs WebSocket 握手类似于普通的 HTTP GET 请求,但它有一个升级标头,要求服务器切换到二进制协议。换句话说,客户端要求服务器启用 nodejs WebSockets 的使用。 以下是此请求中发送的一些其他标头:
Connection: Upgrade Upgrade: websocket Sec-WebSocket-Version: 13 Sec-WebSocket-Key: client_generated_one_time_hash_key
- 如果服务器支持 Web 套接字,则接受请求,并将连接切换到 WebSockets。服务器发送 101 标头响应以告知它正在交换协议。 以下是服务器发送的其他标头:
Connection: Upgrade Upgrade: websocket Sec-WebSocket-Accept: concatenated_Sec-WebSocket-Key_&_RFC6455_static_value
- 一旦协议切换完成,就会创建一个连续且持久的连接。
- 现在,客户端可以向服务器发送一些消息,或者服务器可以在发生某些事件时向客户端发送一些消息。
- 通道由任一侧关闭以断开连接。
我们如何实现 WebSockets?
我们可以通过多种方式实现 Web 套接字。RFC 6455 是 W3c 标准化的 Websockets 协议的最新规范,支持各种浏览器,如 Opera、Safari、Google Chrome、Internet Explorer 和 Mozilla Firefox。
nodejs WebSocket API 提供客户端和服务器之间的异步通信,该通信使用 ws(不安全)或 wss(安全)通过 TCP 套接字进行,并且可供客户端和服务器使用。该 API 允许跨域消息,而不会出现任何问题。
socket.io 可以说是使用套接字的首选方式之一。该库允许在服务器和客户端之间进行双向、实时和基于事件的通信。它由 LearnBoost 的首席技术官兼 LearnBoost Labs 的首席科学家 Guillermo Rauch 创建。它由一个Node.js服务器和一个 javascript 客户端库组成。它是 nodejs WebSocket API 的包装器,使用起来非常简单。它使使用套接字变得非常容易。
该 socket.io 还提供了一些额外的功能,例如广播、长轮询回退(一种在客户端和服务器之间创建接近持久连接的方法)、自动重新连接以及轻松处理负载均衡器和代理。socket.io 库可用于迄今为止可用的 97% 的浏览器。没有像代理或防火墙这样的元素可以阻止服务器和客户端之间的 nodejs WebSocket 链接。
让我们看一下 Socket .io 的基本术语:
- 服务器创建 :const io = socketio(server) 这里 server 是创建的 HTTP 服务器。这用于创建套接字服务器。
- io.on() 方法 : io.on('connection', () => {}) 这会将传递的回调函数绑定到连接事件。建立连接时,将调用该函数。
- socket.emit() 方法 : socket.emit('事件名称', () => {}) 这可以在客户端和服务器端使用,以发出或创建客户端和服务器都监听的事件。
- socket.on() 方法 : socket.on('事件名称', () => {}) 用于监听和处理 socket.emit() 方法发出的事件。每当事件发生时,都会执行回调代码。
- socket.broadcast.emit() 方法:向所有客户端发出事件,但其本身除外,该客户端是发出事件的用户。
- io.emit() 方法 : io.emit() 它曾经毫无例外地向所有客户端发出。
- 断开连接方法 : socket.on('disconnect', () => {}) 内部套接字函数 当您想知道客户端何时断开连接时,请使用它。
安装
要在 socket.io 库的帮助下开始使用 Web 套接字,让我们首先设置我们的开发环境。若要创建服务器,必须在系统中安装Node.js。您可以通过Node.js官方网站上提供的安装程序轻松安装Node.js。
在项目目录中,通过在终端中运行以下命令来初始化Node.js项目:
npm init
将询问有关项目的一组问题,在您回答这些问题后,将创建一个package.json文件,其中包含有关该项目的所有信息。
现在,您需要在开始工作之前安装一些依赖项。执行以下命令以安装 express。需要 Express 来创建一个 HTTP 服务器,nodejs WebSocket 将在其上运行。
npm i express
现在要安装 socket.io,请在终端中运行以下命令。
npm i socket.io
现在,在package.json文件中,您必须在依赖项下提及 express 和 socket.io 及其相应的版本。
服务器端代码示例
现在,在安装完所有依赖项后,您可以开始用 Node.js 编写服务器端代码。服务器是连接中的中心点。来自一个客户端的数据通过服务器传输到另一个客户端。发送到服务器的数据由服务器处理,然后转发到其他客户端。
在这里,我们将在 express 的帮助下创建一个 HTTP 服务器。我们将制作一个变量,其值可以由所有客户端增加,更新后的值将由服务器发送到每个客户端。
创建一个文件server.js并在其中编写以下代码。
const express = require("express"); const app = express(); const port = 3000; app.get("/", function (req, res) { res.send(" Working with Web Sockets "); }); //Listen the HTTP Server const server = app.listen(port, () => { console.log("Server Is Running Port: " + port); });
在上面的代码中,首先,我们借助 require() 函数导入了 express 模块。然后,通过制作 express 实例创建了一个 express 应用程序。为 / route 创建一个 HTTP GET 端点,该端点返回响应 Working with Web Sockets。该应用程序用于侦听 PORT 3000。
使用 node server 运行上述代码并打开 localhost:3000 时,您将看到消息 - Working with Web Sockets。
现在,我们已经创建了一个 HTTP 服务器。现在要集成 websockets,您需要进行如下代码所示的更改。
const express = require("express"); const app = express(); const port = 3000; let count = 0; // variable whose value can be changed by clients. // GET endpoint for the '/' route. app.get("/", function (req, res) { res.send(" Working with Web Sockets "); }); // GET endpoint for the '/connect' route path that sends an HTML file to the client. app.get("/connect", function (req, res) { res.sendFile(__dirname + "/index.html"); }); //Listen the HTTP Server const server = app.listen(port, () => { console.log("Server Is Running Port: " + port); }); // Create nodejs websocket by importing socket.io and using the HTTP server with it. const io = require("socket.io")(server); //Listen for a client connection io.on("connection", (socket) => { // Socket is a Link to the Client console.log("New Client is Connected!"); socket.emit("welcome", { msg: "Welcome to the server!!", count }); // Here the client is connected and we can exchange messages. socket.on("increment", function () { count = count + 1; io.emit("count", count); }); //Whenever someone disconnects this piece of code executed socket.on("disconnect", function () { console.log("A user disconnected"); }); });
在上面的代码中,初始值为 0 的 count 变量。已导入 socket.io 库,并且已将 HTTP 服务器传递到其实例。已为连接事件创建了 .on 侦听器,以处理新的客户端连接。回调函数中的套接字参数可以看作是指向客户端的链接。
我们还添加了另一个 GET 端点,用户将在其中接收 HTML 文件。我们将在下一节中创建此文件。当客户端连接时,服务器会向客户端发出欢迎事件。在此中,传递一个对象,其中包含消息 msg 和 count 变量的值。
现在,已经为套接字连接创建了一些事件处理程序:
- 增加:当某个客户端触发增量事件时,计数的值将递增,并将新值发送到所有客户端。
- 断开:当某些客户端断开连接时,将执行断开连接的关联回调代码。
使用命令节点运行服务器时server.js您将在终端中获得以下输出:
$ node server.js Server Is Running Port: 3000
因此,我们在 socket.io 库的帮助下创建了一个支持 nodejs WebSocket 的服务器。现在,在下一节中,我们将了解如何将客户端连接到此服务器。
客户端的 WebSockets
现在,对于客户端的工作,我们将看看两种方法。首先是在 HTML 文件中使用 socket.io,其次是 socket.io-client 库。
Socket.io HTML 文件:
在项目目录下创建一个index.html文件,并在其中编写以下代码。
<!DOCTYPE html> <html> <head> <title>SocketIO Client</title> <script src="/socket.io/socket.io.js"></script> <style> .container { width: 80%; margin: 1rem auto; } .text-center { text-align: center; } .btn-center { display: block; margin: auto; } </style> </head> <body> <div class="container"> <p class="text-center" id="message"></p> <div>Count : <span id="count"></span></div> </div> <button class="btn-center" id="btn-increment">Increment</button> </body> <script> var socket = io(); var count = 0; var incrementButton = document.getElementById("btn-increment"); incrementButton.addEventListener("click", function (e) { e.preventDefault(); // prevent page reloading socket.emit("increment", count + 1); }); socket.on("welcome", function (data) { document.getElementById("message").innerHTML = data.msg; count = parseInt(data.count); document.getElementById("count").innerHTML = parseInt(data.count); }); socket.on("count", function (_count) { count = parseInt(_count); document.getElementById("count").innerHTML = parseInt(_count); }); </script> </html>
在上面的代码中,socket.io 已包含在 head 标签中。在接近末尾的脚本标记中,按钮单击绑定到触发增量事件的函数。收到此消息后,服务器将递增计数的值并触发事件计数。count 的 on() 处理程序更新客户端的计数值。welcome 的 on() 侦听器初始化消息和 count 的值。
上面代码的输出如下所示,当你打开 localhost:3000/connect 时:
在服务器终端上:
The server is running Port: 3000 New Client is Connected
现在在其他选项卡上打开 localhost:3000/connect,然后从任何选项卡中单击“增量”按钮。 现在,两个选项卡上的 count 值将变为 1。 发生这种情况的原因是服务器向所有客户端发送了一条消息以更新计数的值。
使用 Socket.io-client 库
现在让我们尝试使用我们已安装的 socket.io-client 库。在项目目录中,创建一个新文件,client.js并在其中写入以下代码:
const io = require("socket.io-client"); //First Connect to the Server on the Specific URL (HOST: PORT) let socket = io("http://localhost:3000"); //Now Listen for Events (welcome event). socket.on("welcome", (data) => { /*For the listener we specify the event name and we give the callback to which be called on the event is emitted*/ //Log the Welcome message console.log("Message: ", data); }); socket.on("count", (count) => console.log("Count:", count)); socket.emit("increment"); setTimeout(() => { socket.emit("increment"); }, 5000);
在上面的代码中,导入了库 socket.io-client,并通过提供要连接的服务器的 URL 来创建客户端的实例。在我们的例子中,它是 localhost:3000,因为我们的服务器在 localhost:3000 上运行。欢迎和计数事件的处理程序用于记录服务器发送的数据。我们以 5 秒的延迟发出了两次增量事件。
建立连接后,服务器将发出欢迎消息。然后 count 的值会因为两个 socket.emit() 而改变两次。运行client.js文件时,终端中的输出将是:
$ node app2 Message: { msg: 'Welcome to the server!!', count: 0 } Count: 1 Count: 2 // after a delay of 5 seconds
上面的代码是套接字如何工作的示例。
使用 socket.io 库的优势
Socket.io 在后台使用 Engine.io 在客户端和服务器之间提供基于轮询的长时间双向通信。Engine.io 创建基于低级别的连接,并处理传输、升级和断开连接检测。高级 Socket.io 和低级 Engine.io 可以称为 Socket.io 代码库的两层。Socket.io 的其他各种优点是:
- 可靠性:防火墙、代理、防病毒软件和负载平衡器不会影响使用 socket.io 建立套接字通信。
- 自动重新连接支持:如果断开连接且未明确告知,则客户端将尝试重新连接到另一端可用的服务器。
- 断线检测:当客户端或服务器关闭双向通信时,当检测到连接关闭时,会通知另一方。
- 多路复用支持:一个基础连接可以支持多个通信通道。
- 二进制流支持:可序列化的二进制数据(如数组、缓冲区和 Blob)也可以很容易地在 socket.io 中发出。
是什么让 WebSockets 难以扩展?
不需要非持久性通信的应用程序在负载均衡器的帮助下进行扩展,负载均衡器将流量路由到某个可用节点。由于多种原因,此方法可能不适用于 nodejs WebSocket。
WebSocket 要求每个用户都能看到其他用户,因此他们应该连接到同一个 nodejs WebSocket 服务器。活动用户的数量取决于能够处理多少硬件。Javascript 运行时环境可以很好地处理并发,但是如果用户数量开始变大,则需要垂直扩展来处理用户同步。缩放可以是垂直的,也可以是水平的。水平缩放要困难得多。
在 Web 套接字的情况下,当我们扩展应用程序时,我们需要在节点之间提供一些数据共享机制。这是必需的,以便所有节点都具有相同的应用程序状态视图。一个节点的客户端也应该能够与其他节点的客户端进行通信。
如果一个节点知道连接到它的客户端而不是其他节点,则向客户端广播信息也会变得困难。这需要在集群的 nodejs WebSocket 节点之间使用某种直接通信机制来添加背板。群集的处理可以通过某些外部发布-订阅机制来完成。但这一切都不容易设置,因此在 WebSockets 的情况下,扩展成为一个非常困难的过程。
结论
- Web 套接字可以称为传统 HTTP 的升级。
- Web 套接字是一种协议,可在客户端和服务器之间提供全双工或双向通信线路。
- RFC 6455 是 W3c 标准化的 nodejs Websockets 协议的最新规范,支持各种浏览器
- 客户端向服务器发送升级请求,服务器通过发送 101 响应接受该请求。在此之后,客户端和服务器都可以发送消息,而无需客户端请求它们。
- Socket.io 由 LearnBoost 的首席技术官 Guillermo Rauch 创建,是实现 Web 套接字的首选库。
- Socket.io 就像 WebSockets API 的包装器,使使用 WebSockets 变得容易。
- Socket.io 包含两部分:在浏览器上运行的 JavaScript 客户端库和 Node.js 服务器。
- Socket.io 在后台使用 Engine.io 来提供可靠性、多路复用支持、断开连接检测、二进制数据流、广播和自动重新连接等功能。
- 扩展 nodejs WebSocket 非常困难,因为一个客户端必须能够看到所有其他客户端。这很难在多个 WebSocket 节点中实现。