这是我将要编写的WebSocket系列的第一篇文章,其目标是以最简单的方式解释事物。让我们直接进入它。
WebSockets允许用户使用持久的双向连接向服务器发送和接收消息。基本上,这是客户端和服务器之间的通信方式。让我们先了解一下这种通信,稍后我们将回到WebSockets。
客户端和服务器
Web浏览器(客户端)和服务器通过TCP/IP进行通信。超文本传输协议(HTTP)是TCP/IP 之上的标准应用程序协议,支持请求(来自Web浏览器)及其响应(来自服务器)。
这是如何运作的?
我们来看看这些简单的步骤: -
- 客户端向服务器发送请求。
- 建立连接。
- 服务器发回响应。
- 客户端收到响应。
- 连接已关闭。
这是对客户端和服务器之间标准通信如何工作的简单概述。现在仔细看看第一步。5。
连接已关闭。
HTTP请求已达到其目的,不再需要它,因此连接已关闭。
如果服务器想要向客户端发送消息,该怎么办?
在我们的标准请求/响应方案中,必须根据开始通信的请求建立连接。如果服务器想要发送消息,则客户端必须发送另一个请求以建立连接并接收消息。
客户端如何知道服务器想要发送消息?
考虑这个例子:
客户饿了,并在网上订购了一些食物。他每秒发出一个请求以检查订单是否准备就绪。
0秒:食物准备好了吗?(客户)
0秒:不,等等。(服务器)
1秒:食物准备好了吗?(客户)
1秒:不,等等。(服务器)
2秒:食物准备好了吗?(客户)
2秒:不,等等。(服务器)
3秒:食物准备好了吗?(客户)
3秒:是的,先生,这是您的订单。(服务器)
这就是你所谓的HTTP轮询。客户端向服务器发出重复请求,并检查是否有任何消息要接收。
如你所见,这不是很有效。我们正在使用不必要的资源,失败请求的数量也很麻烦。
有没有办法克服这个问题
是的,有一种用于克服这种缺陷的轮询技术,它被称为长轮询(Long Polling)。
Long Polling基本上涉及向服务器发出HTTP请求,然后保持连接打开以允许服务器稍后响应(由服务器确定)。
考虑上面例子的Long-Polling版本: -
0秒:食物准备好了吗?(客户)
3秒:是的,先生,这是您的订单。(服务器)
这样,问题解决了。
不完全是。虽然长轮询工作,但它在CPU,内存和带宽方面非常昂贵(因为我们阻止资源保持连接打开)。
我们现在干什么?看起来事情已经失控。让我们回到我们的救世主:WebSocket。
为什么选择WebSockets
如您所见,Polling和Long-Polling都是非常昂贵的选项,以模拟客户端和服务器之间的实时通信。此性能瓶颈是您希望使用WebSocket的原因。
WebSockets不需要您发送请求以进行响应。它们允许双向数据流,因此您只需要监听任何数据。
您可以只听服务器,它会在可用时向您发送消息。
让我们看一下WebSocket的性能方面。
资源消耗
下图显示了一个相对常见的用例中WebSockets与Long Polling之间的带宽消耗差异:
差异很大,并且随着请求的数量呈指数增长。
速度
以下是在一秒内为每个连接提供的1,10和50个请求的结果:
如您所见,使用Socket.io对每个连接发出单个请求的速度要慢50%,因为必须首先建立连接。这种开销较小,但对于十个请求仍然很明显。在来自同一连接的50个请求中,Socket.io已经快50%。为了更好地了解峰值吞吐量,我们将查看基准测试,每个连接的请求数量更多(500,1000和2000):
在这里,您可以看到HTTP基准峰值约为每秒约950个请求,而Socket.io每秒约为3900个请求。有效,对吗?
注意:Socket.io是用于实时Web应用程序的JavaScript库。它在内部实现WebSocket。将此视为WebSocket的包装器,它提供了更多功能(本系列的下一篇博客文章详细介绍了Socket.io)。
WebSockets如何工作
这些是建立WebSocket连接所涉及的步骤。
- 客户端(浏览器)向服务器发送HTTP请求。
- 通过HTTP协议建立连接。
- 如果服务器支持WebSocket协议,则同意升级连接。这称为握手。
- 现在握手已完成,初始HTTP连接将被使用相同底层TCP / IP协议的WebSocket连接替换。
- 此时,数据可以在客户端和服务器之间自由地来回流动。
我们将创建两个文件:一个服务器和一个客户端。
首先,创建一个<html>
名为client.html
包含<script>
标记的简单文档。让我们看看它的外观: -
Client.html
<html>
<script>
// Our code goes here
</script>
<body>
<h1>This is a client page</h1>
</body>
</html>
Server.js
现在创建另一个文件server.js
。导入HTTP模块并创建服务器。让它听port 8000
。
这将作为一个简单的http
服务器监听port 8000
。让我们看一下:
//importing http module
const http = require('http');
//creating a http server
const server = http.createServer((req, res) => {
res.end("I am connected");
});
//making it listen to port 8000
server.listen(8000);
运行命令
node server.js
开始监听port 8000
。您可以根据需要选择任何端口,我只是选择了8000无特殊原因。
我们现在完成了客户端和服务器的基本设置。那很简单,对吧?让我们现在就开始吧。
客户端设置
要构造WebSocket,请使用WebSocket()
返回websocket
对象的构造函数。此对象提供用于创建和管理与服务器的WebSocket连接的API 。
简单来说,这个websocket
对象将帮助我们建立与服务器的连接并创建双向数据流,即从两端发送和接收数据。
让我们看看如何:
<html>
<script>
//calling the constructor which gives us the websocket object: ws
let ws = new WebSocket('url');
</script>
<body>
<h1>This is a client page</h1>
</body>
</html>
该WebSocket
构造函数需要一个URL来听。在我们的例子中,这是'ws://localhost:8000'
因为我们的服务器正在运行。
现在,这可能与您习惯的有点不同。我们没有使用HTTP
协议,我们正在使用WebSocket
协议。这会告诉客户“嘿,我们使用WebSocket协议”,因此'ws://'
代替'http://'
。够简单吗?现在让我们实际创建一个WebSocket服务器server.js
。
服务器设置
我们将ws
在节点服务器中使用第三方模块来使用和设置WebSocket
服务器。
首先,我们将导入ws
模块。然后我们将创建一个websocket服务器并将其HTTP
交给服务器监听port 8000
。
HTTP服务器正在侦听端口8000,WebSocket服务器正在侦听此HTTP服务器。基本上,这就是监听者。
现在我们的websocket正在关注流量port 8000
。这意味着它将在客户端可用时立即尝试建立连接。我们的server.js
文件看起来像这样:
const http = require('http');
//importing ws module
const websocket = require('ws');
const server = http.createServer((req, res) => {
res.end("I am connected");
});
//creating websocket server
const wss = new websocket.Server({ server });
server.listen(8000);
正如我们之前讨论的那样 - WebSocket()
构造函数返回一个websocket对象,提供用于创建和管理与服务器的WebSocket连接的API 。
在这里,wss
对象将帮助我们Event
在某些事情发生时听取发出的声音。就像建立连接或握手完成或连接关闭等。
让我们看看如何收听消息:
const http = require('http');
const websocket = require('ws');
const server = http.createServer((req, res) => {
res.end("I am connected");
});
const wss = new websocket.Server({ server });
//calling a method 'on' which is available on websocket object
wss.on('headers', (headers, req) => {
//logging the header
console.log(headers);
});
server.listen(8000);
该方法'on'
需要两个参数:事件名称和回调。事件名称是我们想要监听/发出的,回调指定了如何处理它。在这里,我们只是记录headers
事件。让我们看看我们得到了什么:
这是我们的HTTP标题,我希望你对它好奇,因为这正是幕后发生的事情。让我们分解它以更好地理解它。
- 您会注意到的第一件事是我们获得了状态代码
101
。您可能已经看到200
,201
,404
状态代码,但这个看起来不一样。101
实际上是切换协议状态代码。它说“嘿,我想升级”。 - 第二行显示升级信息。它指定它要升级到
websocket
协议。 - 这实际上是握手期间发生的事情。浏览器使用
HTTP
使用建立连接的连接HTTP/1.1
协议,然后将其Upgrade
它websocket
协议。
现在这将更有意义。
的Headers
响应标头被写入到插座作为握手的一部分之前被发射事件。这允许您在发送标头之前检查/修改标头。这意味着您可以根据需要修改标题以接受,拒绝或其他任何内容。默认情况下,它接受请求。
同样,我们可以添加一个connection
在握手完成时发出的事件。成功建立连接后,我们将向客户端发送消息。我们来看看如何:
const http = require('http');
const websocket = require('ws');
const server = http.createServer((req, res) => {
res.end("I am connected");
});
const wss = new websocket.Server({ server });
wss.on('headers', (headers, req) => {
//console.log(headers); Not logging the header anymore
});
//Event: 'connection'
wss.on('connection', (ws, req) => {
ws.send('This is a message from server, connection is established');
//receive the message from client on Event: 'message'
ws.on('message', (msg) => {
console.log(msg);
});
});
server.listen(8000);
我们也在听取message
客户的意见。让我们创造: -
<html>
<script>
let ws = new WebSocket('url');
//logging the websocket property properties
console.log(ws);
//sending a message when connection opens
ws.onopen = (event) => ws.send("This is a message from client");
//receiving the message from server
ws.onmessage = (message) => console.log(message);
</script>
<body>
<h1>This is a client page</h1>
</body>
</html>
这就是它在浏览器中的外观: -
第一个日志WebSocket
列出了websocket对象上的所有属性,第二个日志列出MessageEvent
了data
属性。如果仔细观察,您会看到我们从服务器收到了消息。
服务器日志将如下所示:
我们正确地收到了客户的消息。这标志着我们的连接成功建立。干杯吧!
结论
总结一下,让我们来看看我们学到的东西:
- 我们已经介绍了HTTP服务器的工作原理,什么是Polling,Long Polling。
- 什么是WebSockets以及我们为什么需要它们。
- 我们介绍了他们如何在幕后工作,并更好地理解所使用的标题。
- 我们创建了自己的客户端和服务器,并成功建立了它们之间的连接。
这是WebSockets的基础知识及其工作原理。本系列的下一篇文章socket.io
将更详细地介绍和使用它。我们还将看到为什么socket.io
当事情与唯一的本地人一起工作时我们确切需要WebSocket()
。当我们成功发送和接收消息时,为什么要使用臃肿的库?
如果您觉得它有用,请分享这篇文章并继续关注下一篇文章。
沙德。
参考
- WebSocket - Web API | MDN:https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
-
ws
节点服务器的模块| 文档:https://github.com/websockets/ws/blob/HEAD/doc/ws.md#event-headers
转:https://levelup.gitconnected.com/websocket-simplified-b532f266cc9f