【HTML】深入WebSocket的世界

1,241 阅读9分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

【HTML】深入WebSocket的世界

引言

github:【HTML】深入WebSocket的世界

内容速递:看了本文您能了解到的知识

WebSocket

1、什么是WebSocket

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

MDN文档

2、WebSocket与HTTP

说到WebSocket那就离不开它的好兄弟HTTP,它们都位于OSI模型的应用层,并且都依赖于传输层的TCP协议。

但是,说到这里并不足以说明它们之间的关系,不是吗?在RFC 6455中找到:

it is designed to work over HTTP ports 80 and 443 as well as to support HTTP proxies and intermediaries.

WebSocket通过HTTP端口80和443进行工作,并支持HTTP代理和中介。

看一张图,对比一下两者的差异

HTTP与WebSocket通信方式

WebSocket通过HTTP1.1 协议的101状态码进行握手。为了创建Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为握手(handshaking)

3、WebSocket的好处

说到WebSocket的好处,那就得看看为什么需要WebSocket?大家都知道HTTP协议,但是用的多的人,大都知道HTTP有一个明显的缺陷,那就是请求只能是由客户端发起。

3.1、轮询

在遇到实时性要求比较高的需求的时候,需要频繁的使用HTTP向服务端拉取数据。这种方式就叫做轮询:最典型的场景就是聊天室。

轮询的方式有几个比较明显的特点:

1、需要不断发送HTTP请求,效率低,非常浪费资源

2、不能保证及时性。在请求的间隙中,无法及时获取服务端最新的数据。

因此,工程师们一直在思考,有没有更好的方法,将偷懒(效率)执行下去。于是WebSocket应运而生!

3.2、轮询的进化

WebSocket虽然是一个新的技术,但是它基于TCP协议,对服务端天然友好,它的端口号也是80和443,并且握手阶段采用 HTTP 协议。这样看来使用WebSocket的代价是比较能接受的。但要说到是轮询的进化,那么它的优势必然不止于此!

WebSocket优势

1、建立在 TCP 协议之上,服务器端的实现比较容易。

2、与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

eg:ws://127.0.0.1 wss://127.0.0.1

3、数据格式比较轻量,性能开销小,通信高效。

4、可以发送文本,也可以发送二进制数据。

5、没有同源限制,客户端可以与任意服务器通信。

4、WebSocket的客户端API

4.1、WebSocket 构造函数

WebSocket()构造函器会返回一个 WebSocket对象。

语法:

const ws = new WebSocket(url [, protocols]);

参数:

  • url

    要连接的 URL;这应该是 WebSocket 服务器将响应的 URL。

  • protocols 可选

    一个协议字符串或者一个包含协议字符串的数组。这些字符串用于指定子协议,这样单个服务器可以实现多个 WebSocket 子协议(例如,您可能希望一台服务器能够根据指定的协议(protocol)处理不同类型的交互)。如果不指定协议字符串,则假定为空字符串。

示例:

// 客户端与服务器进行连接
const ws = new WebSocket('ws://localhost:8080');

4.2、WebSocket.readyState

readyState属性返回实例对象的当前状态

语法:

const readyState = WebSocket.readyState;

状态:

状态说明
CONNECTING值为0,表示正在连接。
OPEN值为1,表示连接成功,可以通信了。
CLOSING值为2,表示连接正在关闭。
CLOSED值为3,表示连接已经关闭,或者打开连接失败。

示例:

switch (ws.readyState) {
  case WebSocket.CONNECTING:
    // do something
    break;
  case WebSocket.OPEN:
    // do something
    break;
  case WebSocket.CLOSING:
    // do something
    break;
  case WebSocket.CLOSED:
    // do something
    break;
  default:
    // this never happens
    break;
}

4.3、WebSocket.onopen

**WebSocket.onopen**属性定义一个事件处理程序,当WebSocket的连接状态readyState变为1时调用;

这意味着当前连接已经准备好发送和接受数据。这个事件处理程序通过 事件(建立连接时)触发。

语法:

ws.onopen = function(event) {
  console.log('WebSocket is open now.');
};

如果要指定多个回调函数,可以使用addEventListener方法。

ws.addEventListener('open', function (event) {
  ws.send('WebSocket is open now.');
});

4.4、WebSocket.onclose

WebSocket.close() 方法关闭 WebSocket连接或连接尝试(如果有的话)。 如果连接已经关闭,则此方法不执行任何操作。

语法:

ws.onclose = function(event) {
  console.log('WebSocket is close now.');
}

如果要指定多个回调函数,可以使用addEventListener方法。

ws.addEventListener('close', function (event) {
  ws.send('WebSocket is close now.');
});

参数:

回调的参数:CloseEvent

4.5、WebSocket.send

WebSocket.send() 方法将需要通过 WebSocket 链接传输至服务器的数据排入队列,并根据所需要传输的 data bytes 的大小来增加 bufferedAmount的值 。若数据无法传输(例如数据需要缓存而缓冲区已满)时,套接字会自行关闭。

语法:

WebSocket.send("Hello server!");

参数:

data:用于传输至服务器的数据。它必须是以下类型之一:

  • USVString:文本字符串。字符串将以 UTF-8 格式添加到缓冲区,并且 bufferedAmount 将加上该字符串以 UTF-8 格式编码时的字节数的值。
  • ArrayBuffer:您可以使用一有类型的数组对象发送底层二进制数据;其二进制数据内存将被缓存于缓冲区,bufferedAmount 将加上所需字节数的值。
  • BlobBlob 类型将队列 blob 中的原始数据以二进制中传输。 bufferedAmount 将加上原始数据的字节数的值。
  • ArrayBufferView:您可以以二进制帧的形式发送任何 JavaScript 类数组对象;其二进制数据内容将被队列于缓冲区中。值 bufferedAmount 将加上必要字节数的值。

4.6、WebSocket.binaryType

WebSocket.binaryType 返回 websocket 连接所传输二进制数据的类型。

语法:

const binaryType = ws.binaryType;

返回值:

  • blob:如果传输的是 Blob 类型的数据。
  • arraybuffer:如果传输的是 ArrayBuffer 类型的数据。

4.7、WebSocket.onmessage

message 事件会在 WebSocket 接收到新消息时被触发。

语法:

ws.onmessage = function(event) {
  const data = event.data;
  // 处理数据
}

如果要指定多个回调函数,可以使用addEventListener方法。

ws.addEventListener('message', function (event) {
  const data = event.data;
  // 处理数据
});

注意,服务器数据可能是文本,也可能是二进制数据(blob对象或Arraybuffer对象),可以通过WebSocket.binaryType判断得到。

ws.onmessage = function(event){
  if(typeof event.data === String) {
    console.log("Received data string");
  }

  if(event.data instanceof ArrayBuffer){
    var buffer = event.data;
    console.log("Received arraybuffer");
  }
}

5、WebSocket环境搭建

讲到很多WebSocket的用法和好处,那么现在就搭建一个比较原生的WebSocket环境吧!

5.1、客户端与服务器

客户端:就是指的是浏览器了,可以直接使用原生WebSocket的构造函数。

服务器:这里使用Node搭建,当然也可以使用其他的语言。websocketd可以去玩一玩,不限脚本语言。

5.2、服务器

1、使用npm新建一个项目

npm init

2、安装ws库,不了解ws库的,可以看看官方使用文档:ws

npm install ws

3、新建文件wsServer.js

const { WebSocketServer } = require('ws')

// 构造函数
const ws = new WebSocketServer({
    port: 8080
})

// 监听连接
ws.on('connection', (ws, req) => {
    const clientAddress = req.socket.remoteAddress
    console.log('客户端已连接:', clientAddress)

    ws.send('客户端,你好!我是服务端!')

    ws.on('message', data => {
        console.log('收到客户端的信息:', data)
    })
})

4、运行

在项目目录中执行以下命令,ws://localhost:8080服务就起来了

node wsServer

5.3、客户端

在浏览器中直接使用WebSocket构造函数

创建一个ws.html文件

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket</title>
</head>

<body>
    <script>
        const ws = new WebSocket('ws://localhost:8080')
        ws.onopen = () => {
            console.log('已连接!')
            if (ws.readyState === 1) {
                ws.send('你好,服务器!我是客户端!')
            }
        }
        ws.onmessage = (messageEvent) => {
            console.log('客户端收到的消息', messageEvent.data)
        }
        ws.onclose = () => {
            console.log('连接关闭!')
        }
    </script>
</body>

</html>

放在浏览器中打开

image-20220801224734768

服务端的响应

image-20220801224842957

6、从控制台来分析 WebSocket

6.1、WS请求

WS请求:

打开浏览器的控制台,选择Network下面,其中有一个可以过滤请求类型的,这里选中WS,看到下方有一个请求

image-20220801225449656

WS的header:

其中主要Request HeadersResponse Headers

image-20220801230122352

WS的Message:

客户端与服务器的消息对话在Message中可以清晰的看到

image-20220801231341022

WS的Timing:

可以看到WS的Timing:request is not finished yet !

image-20220801231630420

6.2、客户端协议升级

报文格式:

WebSocket报文格式,且只支持GET方法

GET ws://localhost:8080/ HTTP/1.1
Host: localhost:8080
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
Upgrade: websocket
Origin: null
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh-TW;q=0.9,zh;q=0.8,en-US;q=0.7,en;q=0.6
Sec-WebSocket-Key: v1/kSADNgDv1r2afDX0fbg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

主要参数解析:

  • Connection: Upgrade:表示要升级协议
  • Upgrade: websocket:表示要升级到websocket协议。
  • Sec-WebSocket-Version: 13:表示websocket的版本。如果服务端不支持该版本,需要返回一个Sec-WebSocket-Versionheader,里面包含服务端支持的版本号。
  • Sec-WebSocket-Key:与后面服务端响应首部的Sec-WebSocket-Accept是配套的,提供基本的防护,比如恶意的连接,或者无意的连接。

6.3、服务端协议升级

报文格式:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 2iLZ72mDoCjQY1UEIRvsKyE4mwU=

主要参数解析:

  • 101 Switching Protocols 状态代码101表示协议切换
  • Sec-WebSocket-Accept: 与客户端请求首部的Sec-WebSocket-Key是配套的

7、常用WebSocket的Node库

常用的 Node 实现有以下三种。

有兴趣对Socket.io做一次详解,听说是目前最好用的一款!

总结

其实写到这里,WebSocket的大门才刚刚打开,WebSocket的安全性考虑,特别是实战化的东西往往才能真正体现出WebSocket的魅力。如果只是一味用来替换轮询,那就太遗憾了!

博客说明与致谢

文章所涉及的部分资料来自互联网整理,其中包含自己个人的总结和看法,分享的目的在于共建社区和巩固自己。

引用的资料如有侵权,请联系本人删除!

感谢万能的网络,W3C,菜鸟教程等!

感谢勤劳的自己个人博客GitHub学习GitHub

公众号【归子莫】,小程序【子莫说】

如果你感觉对你有帮助的话,不妨给我点赞鼓励一下,好文记得收藏哟!关注我一起成长!

幸好我在,感谢你来!