前端需要知道的计算机网络知识----WebSocket

377 阅读6分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第13天,点击查看活动详情

一、概述

我们了解的网络通讯通常是客户端发起请求,等待服务端的响应。若想实现服务端推送消息给客户端,需要通过轮询长轮询iframe流来实现。但这几种办法多少显得有些美中不足。

于是乎,随着技术的不断更新,在能者的努力下,我们迎来了完善的双工通信的更好实现方式WebSocket的时代。

1.1 全双工通信

全双工通信又称为双向同时通信,即通信的双方可以同时发送和接收信息的信息交互方式。

与之对比的是半双工通信:这种通信方式可以实现双向的通信,但不能在两个方向上同时进行,必须轮流交替地进行。也就是说,通信信道的每一段都可以是发送端,也可以是接收端。但同一时刻里,信息只能有一个传输方向。如日常生活中的例子有步话机通信,对讲机等。

我们使用的HTTP协议就是半双工通信。

1.2 定义

WebSocket 是独立的、创建在 TCP 上的协议。

Websocket 通过HTTP/1.1 协议的101状态码进行握手。

为了创建Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为握手(handshaking)。

WebSocket API也被W3C定为标准,主流的浏览器都已经支持WebSocket通信。

二、学习使用

有一个网站可以去模拟WebSocket的使用,建议自己尝试一下。

image.png 接下来我们通过node和前端代码来实现websocket通信。

2.1 node搭建简单的服务端环境

其实也可以选用socket.io 进行搭建,这里我们使用的是websockets/ws库,搭建过程不做主要赘述了。

npm i koa
npm i ws

新建index.js

const Koa = require("koa");
const WebSocket = require("ws");
​
const app = new Koa();
const ws = new WebSocket.Server({ port: 3005 }); // 可自己设置端口号
​
ws.on("connection", (ws) => {
    console.log("websocket server is connected");
​
    ws.on("message", (msg) => {
        console.log("websocket server receive msg:", msg);
    });
​
    ws.send("Information from the server");
});
​
app.use(async (ctx) => {
    ctx.body = "Hello Koa";
});
​
app.listen(3000);

这样一个简单的服务端websocket就搭建好了,我们直接执行 node index.js

我们尝试前端页面去访问http://localhost:3000/,可以看到我们的后端服务是没有问题的

2.2 尝试配置前端环境

我们尝试使用script来进行跨域的访问

<!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>
        <h1>websocket环境测试</h1>
    </body>
    <script>
        if ("WebSocket" in window) {
            console.log("您的浏览器支持 WebSocket!");
​
            // 打开一个 web socket
            var ws = new WebSocket("ws://127.0.0.1:3005/");
​
            ws.onopen = function () {
                // Web Socket 已连接上,使用 send() 方法发送数据
                ws.send("from client: hello everyone");
                console.log("数据发送中...");
            };
​
            ws.onmessage = function (evt) {
                var received_msg = evt.data;
                console.log("数据已接收..." + evt.data);
            };
​
            ws.onclose = function () {
                // 关闭 websocket
                console.log("连接已关闭...");
            };
        } else {
            // 浏览器不支持 WebSocket
            console.log("您的浏览器不支持 WebSocket!");
        }
    </script>
</html>

我们可以看到执行结果:

客户端:

您的浏览器支持 WebSocket!
index.html:22 数据发送中...
index.html:27 数据已接收...Information from the server

服务端:

websocket server is connected
websocket server receive msg: from client: hello everyone

以上只是我们简单的尝试了一下基本使用,主要去了解它的原理和使用方法。后续若想在vue项目中的使用,我会再单独输出一篇文章进行介绍,下面我们来看下基本的API。

三、API介绍

MDN WebSocket API

3.1 客户端API

客户端可以直接使用websocket

3.1.1 构造函数

WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例。

var ws = new WebSocket('ws://127.0.0.1:3005/');

作用:客户端与服务端连接

3.1.2 webSocket.onopen

当一个 WebSocket 连接成功时触发。

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

若是想指定多个回调函数,则可使用addEventListener

socket.addEventListener('open', function (event) {
     console.log("WebSocket is open now.");
});

3.1.3 webSocket.onclose

当一个 WebSocket 连接被关闭时触发.使用方法与open方法一致 event中的值:可自己打印查看

image.png

ws.onclose = function(event) {
  var code = event.code;
  var reason = event.reason;
  var wasClean = event.wasClean;

  console.log("WebSocket is closed now.");

};

ws.addEventListener("close", function(event) {
  var code = event.code;
  var reason = event.reason;
  var wasClean = event.wasClean;
  console.log("WebSocket is closed now.");
});

3.1.4 webSocket.message

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

ws.onmessage = function(event) {
  var data = event.data;
  console.log("数据已接收..." + evt.data);
  // 处理数据
};

ws.addEventListener("message", function(event) {
  var data = event.data;
  console.log("数据已接收..." + evt.data);
  // 处理数据
});

在这里需要特别注意下event.data的数据类型,可能是String,可能是二进制数据(blob对象或Arraybuffer对象)

3.1.5 webSocket.onerror

ws.onerror = function(event) {
  // handle error event
};

ws.addEventListener("error", function(event) {
  // handle error event
});

3.1.6 WebSocket.send()

实例对象的send()方法用于向服务器发送数据,在这里可发送的数据类型为文本字符串、Blob 对象、ArrayBuffer

WebSocket.send("Hello server!");

// Blob
var file = document.querySelector('input[type="file"]').files[0];
WebSocket.send(file);

// ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
  binary[i] = img.data[i];
}
ws.send(binary.buffer);

3.2 服务端实现

可以使用我们搭建简单服务端环境时的ws模块

也可以使用socket.io

或者WebSocket-Node

3.3 常量和属性

WebSocket.readyState的值

  • 0 (WebSocket.CONNECTING) 正在链接中
  • 1 (WebSocket.OPEN)已经链接并且可以通讯
  • 2 (WebSocket.CLOSING)连接正在关闭
  • 3  (WebSocket.CLOSED)连接已关闭或者没有链接成功

属性:

属性名值类型描述
binaryTypeString表示连接传输的二进制数据类型的字符串。默认为"blob"。
bufferedAmountNumber只读。如果使用send()方法发送的数据过大,虽然send()方法会马上执行,但数据并不是马上传输。浏览器会缓存应用流出的数据,你可以使用bufferedAmount属性检查已经进入队列但还未被传输的数据大小。在一定程度上可以避免网络饱和。
extensionsString只读属性,服务器选择的扩展。目前,链接可以协定的扩展值只有空字符串或者一个扩展列表。
protocolString/Array只读,用于返回服务器端选中的子协议的名字
readyStateString只读。连接当前状态,这些状态是与常量相对应的。

四、数据帧格式

WebSocket客户端、服务端通信的最小单位是帧(frame),由1个或多个帧组成一条完整的消息(message)。

  0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-------+-+-------------+-------------------------------+
 |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
 |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
 |N|V|V|V|       |S|             |   (if payload len==126/127)   |
 | |1|2|3|       |K|             |                               |
 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
 |     Extended payload length continued, if payload len == 127  |
 + - - - - - - - - - - - - - - - +-------------------------------+
 |                               |Masking-key, if MASK set to 1  |
 +-------------------------------+-------------------------------+
 | Masking-key (continued)       |          Payload Data         |
 +-------------------------------- - - - - - - - - - - - - - - - +
 :                     Payload Data continued ...                :
 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
 |                     Payload Data continued ...                |
 +---------------------------------------------------------------+

FIN(1个bit) :标识这一帧数据是否是该分块的最后一帧。

  • 1 为最后一帧
  • 0 不是最后一帧。需要分为多个帧传输

RSV1, RSV2, RSV3(各1个bit) :默认为0.接收协商采用WebSocket扩展定义为非0设定。如果出现非零的值,且并没有采用WebSocket扩展,连接出错。

Opcode(4个bit) :操作码,Opcode的值决定了应该如何解析后续的数据载荷(data payload)

  • %x0 表示一个继续帧
  • %x1 表示一个文本帧
  • %x2 表示一个二进制帧
  • %x3-7 为以后的非控制帧保留
  • %x8 表示一个连接关闭
  • %x9 表示一个ping
  • %x10 表示一个pong
  • %x11-15 为以后的控制帧保留

masked(1个bit) :表示是否要对数据载荷进行掩码操作。定义了masking-key是否存在。并且使用masking-key掩码解析Payload data。

  • 1 客户端发送数据到服务端
  • 0 服务端发送数据到客户端

payload length:数据载荷的长度,单位是字节。为7位,或7+16位,或1+64位。

  • 0-125,则是payload的真实长度 ,数据的长度为x字节。
  • 126,后续2个字节代表一个16位的无符号整数,该无符号整数的值为数据的长度,125<数据长度<65535
  • 127,后续8个字节代表一个64位的无符号整数(最高位为0),该无符号整数的值为数据的长度,数据长度>65535

Masking-key (0或4bytes) :当masked为1的时候才存在,为4个字节,否则为0,用于对我们需要的数据进行解密

Payload data:(x+y) bytes:“有效负载数据”是指“扩展数据”和“应用数据”。

  • Extension data: x bytes:除非协商过扩展,否则“扩展数据”长度为0 bytes。在握手协议中,任何扩展都必须指定“扩展数据”的长度,这个长度如何进行计算,以及这个扩展如何使用。如果存在扩展,那么这个“扩展数据”包含在总的有效负载长度中。
  • Application data: y bytes:任意的“应用数据”,占用“扩展数据”后面的剩余所有字段。“应用数据”的长度等于有效负载长度减去“扩展应用”长度。

五、websocket与HTTP

5.1 区别

定义不同:

  • websocket:WebSocket是一种建立在TCP连接上进行全双工通信的协议。
  • HTTP:超文本传输协议,是一个简单的请求-响应协议,运行在TCP之上,是单向通信协议

连接方式

  • websocket:WebSocket是需要浏览器和服务器握手进行建立连接的。
  • HTTP:http是浏览器发起向服务器的连接

连接时效不同:

  • websocket:WebSocket是持久连接。
  • HTTP:短连接,可以通过一直发送请求和长轮询保持连接,但本质还是短连接

协议不同:

  • websocket:websocket的协议是以 ws/wss 开头。
  • HTTP:http 对应的是 http/https

5.2 联系

  1. 都是基于TCP协议
  2. websocket是基于http的,兼容性较好
  3. 连接过程中错误处理方式相同
  4. 据可以在网络中传输数据
  5. 使用 Request/Response模型进行连接的建立

六、websocket特点

服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

  1. 建立在tcp上的全双工通信的协议,服务器端的实现比较容易,可使用插件。

  2. 与Http协议有着良好的兼容性,默认端口也是80和443,并且握手阶段采用http协议,因此握手时不容易屏蔽,能通过各种http代理服务器.

  3. 数据格式比较轻量,性能开销较少,通信效率高。

    1. 在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。
    2. HTTP请求每次都要携带完整的头部.
  4. 可以发送文本,也可以发送二进制数据。Websocket定义了二进制帧,能够轻松的处理二进制。

  5. 没有同源限制,可以换可以与任意服务器通信

  6. 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。