WebSocket 前世今生 ?建立 WebSocket 的 6 种方式?

WebSocket 允许创建“实时”应用程序,这些应用程序比传统的 API 协议更快、开销也更小。 WebSocket有时被称为高端计算机通信协议,通过 WebSocket 来建立客户端-服务器通信通道。本文将深入探讨 WebSocket的起源?WebSocket 是什么?它是如何工作的?常见的 建立WebSocket 连接的6个通用的库。

1.WebSocket出现之前的世界?

1.1 WebSockets 之前:使用 JavaScript 编写 Web 脚本

1995 年,Netscape Communications 聘请了 Brendan Eich,目标是将脚本功能嵌入到其 Netscape Navigator 浏览器中,JavaScript 就这样诞生了。

微软也很快凭借 Internet Explorer 浏览器进入了赛场,这是最初的浏览器大战真正开始的地方。 两家公司都在争夺谁能获取最好浏览器的宝座,因此不可避免地要定期向 Netscape 和 Internet Explorer 添加新特性和功能。WebSocket 前世今生 ?建立 WebSocket 的 6 种方式?

浏览器大战

1.2 WebSockets 之前:XMLHttpRequest 和 AJAX 的诞生

当时引入的两个最重要的功能是将 Java applets嵌入页面的能力,以及 Microsoft 的ActiveX 控件。

两者本质上是预编译的组件,可以选择在网页中呈现自己的嵌入式用户界面。

虽然通过 Java applets提供了一些类似的网络功能,但最重要的后台通信功能首次出现在 1999 年,即 Microsoft XMLHTTP ActiveXObject 接口。 它在 Internet Explorer 5.0 中原生支持,无需安装插件,可以用一行 JavaScript 实例化,并且在处理 Java applets时不会带来任何问题。

XMLHTTP 对象可以静默地向服务器发出请求并接收响应,所有这些功能都无需重新加载页面或以其他方式中断用户操作。 然后 JavaScript 代码可以处理响应并对页面进行修改,从而将大量丰富的体验集成到网站中。

常见的早期用例包括允许下拉框根据用户先前的输入填充选项,以及在填写用户注册表时“即时”验证用户名可用性。

比如下面用于实例化 XMLHTTP 对象的示例 JavaScript 代码:

// 注意:ActiveXObject是老版本IE浏览器的私有属性,大多数浏览器并不支持
const xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
xmlhttp.open("GET", "/api/demostration", true);
xmlhttp.onreadystatechange = function () {
  if (xmlhttp.readyState == 4) {
    alert(xmlhttp.responseText);
  }
};
xmlhttp.send(null);

XMLHTTP 后来由于被其他浏览器采用而成为 XMLHttpRequest 事实上的标准。 这大约是创造术语“AJAX”的时间,AJAX代表“异步 JavaScript 和 XML”。下面是使用标准 XMLHttpRequest 对象的示例:

const req = new XMLHttpRequest();
req.addEventListener("load", () => console.log(this.responseText));
req.open("GET", "/api/demostration");
req.send();

与以前代码结构基本类似,只是代码更加简洁。但是,无论如何,XMLHttpRequest 仍然遵循用于检索原始 HTML 文档的相同 HTTP 请求-响应模型。 没有允许服务器主动连接用户或为更复杂的用例建立任何类型的通用双向连接的能力。

尽管如此,JavaScript一直在添加新的特性和功能,浏览器也在增强文档对象模型 (DOM)。 这导致在如何使用 JavaScript 来丰富用户与网页交互的体验方面具有越来越大的潜力。

1.3 孵化实时网络

当某件事在技术上可行,并且有较高的ROI时,通常会竭尽全力。因此,开发人员开始使用 XMLHttpRequest 来模拟浏览器和服务器之间的实时通信。

这些技术中最常见的是长轮询, 打开与服务器的 XMLHttpRequest 连接并保持打开状态,直到不再需要通信后关闭!

下面是HTTP长轮询的大致流程:WebSocket 前世今生 ?建立 WebSocket 的 6 种方式?

HTTP长轮询

在没有其他合适工具的情况下,对于 Web 应用程序开发人员来说,长轮询很难正确执行,并且充满了必须管理的意外复杂情况, 其他 Comet 技术也是如此。

2.WebSocket 横空出世?

WebSockets 于 2008 年首次出现,并在 2010 年左右开始获得广泛的浏览器支持。

如上文所示,在 WebSockets 出现之前,“实时”网络已经存在,但它很难实现,速度较慢,并且是通过hack并非为实时应用程序设计的现有网络技术来实现的。

网络是建立在 HTTP 协议的基础上,该协议最初被设计为一种请求-响应机制。 用户打开一个连接,描述想要的东西,得到一个响应,然后关闭连接。

WebSocket 前世今生 ?建立 WebSocket 的 6 种方式?

Websocket建立流程

WebSocket 是一种全双工协议,主要用于客户端-服务器通信。 它本质上是双向的,这意味着通信在客户端-服务器之间来回发生。

使用 WebSocket 开发的连接会持续到任何g参与方停止连接为止。 一旦一方断开连接,另一方将无法通信,因为连接会在前端自动断开。

WebSocket 需要 HTTP 的支持来发起连接,当涉及到无缝数据流和各种不同步流量时,它是现代 Web 应用程序开发的支柱。

3.WebSocket的场景覆盖?

WebSocket 是一种必不可少的客户端-服务器通信工具,需要充分了解其作用和适用场景才能更好的使用它。在以下情况下可以使用 WebSocket:

‍开发实时网络应用程序

WebSocket 最常见的用途是在实时应用程序开发中,它有助于在客户端持续显示数据。 由于后端服务器不断发回此数据,WebSocket 允许在已打开的连接中不间断地推送或传输此数据。 WebSockets 的使用使此类数据传输更快,并利用了应用程序的性能。

‍创建聊天应用程序

聊天应用程序开发人员在一次性交换和发布/广播消息等操作中调用 WebSocket 寻求帮助。 由于使用相同的 WebSocket 连接来发送/接收消息,因此通信变得简单快捷。

游戏应用程序

在游戏应用程序开发过程中,服务器必须不间断地接收数据,而不需要刷新 UI。 WebSocket 在不影响游戏应用程序 UI 的情况下实现了这一目标。

4.Websocket vs HTTP的异同?

由于 HTTP 和 WebSocket 都用于应用程序通信,因此人们常常感到困惑,很难从这两者中选择。 如前所述,WebSocket 是一种双向协议。 与此相反,HTTP 是一种单向协议,作用于 TCP 协议之上。

由于WebSocket协议能够支持不间断的数据传输,因此主要用于实时应用开发。 HTTP 是无状态的,用于开发 RESTful 和 SOAP 应用程序。

SOAP 仍然可以使用 HTTP 来实现,但是 REST 被广泛传播和使用。

WebSocket 前世今生 ?建立 WebSocket 的 6 种方式?

Websocket和HTTP连接建立流程

在 WebSocket 中,通信发生在双端,这使其成为速度更快的协议。 在 HTTP 中,连接只能在一端建立,这使得它比 WebSocket 更慢。

WebSocket 使用统一的 TCP 连接,需要一方终止连接。 在它发生之前,连接保持持续活动状态。 HTTP 需要为单独的请求建立不同的连接。 请求完成后,连接会自动断开。

5.WebSocket 连接是如何建立的?

该过程从涉及使用新方案 ws 或 wss 的 WebSocket 握手开始。 为了快速理解,您可以将它们分别视为 HTTP 和 HTTPS。WebSocket 前世今生 ?建立 WebSocket 的 6 种方式?

注意:HTTP和WebSocket相交部分表示,WebSocket的握手阶段需要HTTP参与

使用此方案,服务器和客户端应遵循标准的 WebSocket 连接协议。 WebSocket 连接建立从 HTTP 请求升级开始,它具有几个标头,例如: Connection: Upgrade、Upgrade: WebSocket、Sec-WebSocket-Key 等。下面是这个连接是如何建立的大致流程。

注意:WebSocket的握手阶段需要HTTP参与,连接建立后将会自动切换为WebSocket协议传输

4.1 请求阶段

Connection: Upgrade :表示 WebSocket 握手,而 Sec-WebSocket-Key 表示 Base64 编码的随机值, 该值是在每次 WebSocket 握手期间随机生成的。 除了上述之外,Key标头也是此请求的一部分。

上面列出的标头组合起来就形成了一个 HTTP GET 请求。 它将包含类似的数据:

GET ws://websocketexample.com:8181/ HTTP/1.1
Host: localhost:8181
Connection: Upgrade
// 握手
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
// 升级为websocket协议
Sec-WebSocket-Version: 13
// 协议版本
Sec-WebSocket-Key: b6gjhT32u488lpuRwKaOWs==
// 随机生成

Sec-WebSocket-Version表示可供客户端使用的 WebSocket 协议版本。

4.2 响应阶段

响应标头 Sec-WebSocket-Accept 具有在 Sec-WebSocket-Key 请求标头中同等的作用。 这与特定的协议规范有关,并被广泛用于防止误导信息。 换句话说,它增强了 API 的安全性,并阻止配置不当的服务器在应用程序开发中造成失误。

在先前发送的请求成功后,将收到类似于下面提到的文本序列的响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
// 升级成功
Sec-WebSocket-Accept: rG8wsswmHTJ85lJgAE3M5RTmcCE=

在客户端、服务端建立连接的安全性方面,可以通过下面一段话进行总结:

WebSocket没有规定服务器可以在握手期间对客户端进行身份验证的任何特定方式。 WebSocket 服务器可以使用通用 HTTP 服务器可用的任何客户端身份验证机制,例如 cookie、HTTP 身份验证或 TLS 身份验证!

5.几个可用的WebSocket库?

5.1 ws

ws 是一个“简单易用、快速且经过全面测试的 Node.js WebSocket 客户端和服务器”。 它绝对是一个标准实现,旨在完成协议的所有工作。 但是,连接恢复、发布/订阅等其他功能是您必须自己管理的问题。

以下是使用 Node.js 和 WebSockets 构建实时应用程序时需要实现的代码。

客户端

const WebSocket = require('ws');
// 导入
const ws = new WebSocket('ws://www.host.com/path');
ws.on('open', function open() {
  ws.send('something');
});
// 监听
ws.on('message', function incoming(data) {
  console.log(data);
});

服务端

const WebSocket = require('ws');
// 导入获取Server模块
constcons wss = new WebSocket.Server({ port: 8080 });
// 启动WebSocket服务器
wss.on('connection', function connection(ws) {
  ws.on('message', function incoming(message) {
    console.log('received: %s', message);
  });
  ws.send('something');
});

5.2 μWebSockets

μWS 是 ws 的直接替代品,注重性能和稳定性。 据我所知,μWS 是最快的 WebSocket 服务器实现, 它由 SocketCluster 在后台使用。

const WebSocketServer = require('uws').Server;
// 获取Server模块
const wss = new WebSocketServer({ port: 3000 });
function onMessage(message) {
  console.log('received: ' + message);
}
// 监听connection
wss.on('connection', function(ws) {
  ws.on('message', onMessage);
  ws.send('something');
});

5.3 faye-websocket

faye-websocket 是客户端和服务器的标准 WebSocket 实现,作为 Faye 项目的一部分,起源于 Ruby-on-Rails 社区。

在下面的服务器示例代码中,可以看到处理连接升级和从入站套接字缓冲区转换消息帧的所有工作都由库提供的 WebSocket 类处理。 与其他最小化解决方案一样,这是一个简洁的实现——您需要自己处理特定于应用程序的问题。

客户端代码

const WebSocket = require('faye-websocket'),
// 导入获取Client模块
const ws = new WebSocket.Client('ws://www.example.com/');
ws.on('open', function(event) {
  console.log('open');
  ws.send('Hello, world!');
});
ws.on('message', function(event) {
  console.log('message', event.data);
});
ws.on('close', function(event) {
  console.log('close', event.code, event.reason);
  ws = null;
});

服务端代码

const WebSocket = require('faye-websocket'),
const http = require('http');
const server = http.createServer();
// 导入获取Server模块
server.on('upgrade', function(request, socket, body) {
  if (WebSocket.isWebSocket(request)) {
    var ws = new WebSocket(request, socket, body);
    ws.on('message', function(event) {
      ws.send(event.data);
    });
    ws.on('close', function(event) {
      console.log('close', event.code, event.reason);
      ws = null;
    });
  }
});
server.listen(8000);

5.4 Socket.io

Socket.IO 已经存在了一段时间,可以被认为是 WebSockets 的“jQuery”。 它使用长轮询和 WebSockets 进行传输,默认情况下从长轮询开始,然后升级到 WebSockets。

长轮询已经基本不需要,Socket.IO 如今的主要吸引力是它的主动断连能力、自动支持 JSON 和“命名空间”,它们本质上是在同一客户端连接上多路复用的隔离消息通道。

Socket.IO 实际上不能与通用的 WebSockets 解决方案互换,无论是在服务器端还是在客户端。它有自己的附加握手协议,每条消息中都包含一些附加元数据。

客户端代码

const io = require('socket.io-client');
const socket = io();
socket.emit('chat message', 'Hello there');

服务端代码

const app = require('express')();
const http = require('http').Server(app);
// 启动server并传递给socket.io模块
const io = require('socket.io')(http);
app.get('/', function(req, res){
  res.sendFile(__dirname + '/index.html');
});
io.on('connection', function(socket){
  console.log('a user connected');
});
http.listen(3000, function(){
  console.log('listening on *:3000');
});

5.5 SocketCluster

SocketCluster 是一个功能齐全的客户端-服务器消息传递框架,完全围绕 WebSockets 构建,并在底层使用 μWebSockets。

与 Socket.IO 等更简单的解决方案相比,SocketCluster 需要稍微多一些安装,但通常很容易启动和运行。

客户端代码

const socket = socketCluster.create();
socket.emit('sampleClientEvent', {message: 'This is an object with a message property'});

服务端代码

const SocketCluster = require('socketcluster');
// 关于实例化参数的具体含义可以阅读文档
const socketCluster = new SocketCluster({
  workers: 1, 
  // Number of worker processes
  brokers: 1, 
  // Number of broker processes
  port: 8000, 
  // The port number on which your server should listen
  appName: 'myapp', 
  // A unique name for your app
  // Switch wsEngine to 'sc-uws' for a MAJOR performance boost (beta)
  wsEngine: 'ws',
  /* A JS file which you can use to configure each of your
   * workers/servers - This is where most of your backend code should go
   */
  workerController: __dirname + '/worker.js',
  /* JS file which you can use to configure each of your
   * brokers - Useful for scaling horizontally across multiple machines (optional)
   */
  brokerController: __dirname + '/broker.js',
  // Whether or not to reboot the worker in case it crashes (defaults to true)
  rebootWorkerOnCrash: true
});

5.6 SocketCluster

SockJS 是一个浏览器 JavaScript 库,它提供了一个类似于 WebSocket 的对象。SockJS 为您提供了一个连贯的、跨浏览器的 Javascript API,它在浏览器和 Web 服务器之间创建了一个低延迟、全双工、跨域的通信通道”。

客户端代码

const sock = new SockJS('https://mydomain.com/my_prefix');
sock.onopen = function() {
  console.log('open');
  sock.send('test');
};
sock.onmessage = function(e) {
  console.log('message', e.data);
  sock.close();
};
sock.onclose = function() {
  console.log('close');
};

服务端代码

const http = require('http');
const sockjs = require('sockjs');
const echo = sockjs.createServer({ sockjs_url: 'http://cdn.jsdelivr.net/sockjs/1.0.1/sockjs.min.js' });
// 创建Server
echo.on('connection', function(conn) {
  conn.on('data', function(message) {
    conn.write(message);
  });
  conn.on('close', function() {});
});

var server = http.createServer();
echo.installHandlers(server, {prefix:'/echo'});
server.listen(9999, '0.0.0.0');

参考资料

https://www.wallarm.com/what/a-simple-explanation-of-what-a-websocket-is

https://ably.com/topic/websockets

https://github.com/uNetworking/uWebSockets

https://github.com/socketio/socket.io

作者:”前端进阶“,关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论