如何使用WebSocket

WebSocket是一种全双工通信协议,旨在优化Web应用的双向通信。它基于HTTP完成一次握手,允许服务端主动推送信息。本文介绍了WebSocket的基本概念、特点、握手过程,并展示了如何使用Node.js搭建服务端及应用W3C标准的WebSocket API客户端。同时,讨论了子协议和心跳检测的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基本概念

概述

WebSocket是一种在单个TCP连接上进行全双工通信的协议。

旨在解决Web应用程序 客户端 与 服务端 之间若要进行双向通信不得不使用轮询这种开销极大的方式的问题。

特点

  • 全双工 服务端可以主动向客户端推送信息,客户端也可以主动向服务端发送信息
  • 与HTTP共享端口,基于HTTP完成握手
  • 数据传输基于帧,支持发送文本类型数据和二进制数据
  • 没有同源限制,客户端可以与任意服务端通信
  • 支持协议标识(ws或wss)与寻址,通过URL指定服务

握手

WebSocket握手基于HTTP,只需一次握手即可完成连接,具体握手流程如下:

客户端发起握手

通过HTTP请求告知服务端需要更换通信协议

GET ws://localhost:3000/hello HTTP/1.1
Host: 192.168.0.110:8080
Connection: Upgrade
Upgrade: websocket
Origin: http://localhost:8080
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: ddWE76tEFVgUfKjHIWDWvw==
  • 请求必须是有效的HTTP请求
  • 请求的方法必须是GET,HTTP版本至少是1.1。
  • 请求的URL必须满足以下格式要求 ws:// {host} [ : {port} ] {path} [ ? {query} ]或wss:// {host} [ : {port} ] {path} [ ? {query} ]
  • 请求首部必须包含Origin字段
  • 请求首部必须包含Upgrade字段,且值为websocket,由于Upgrade字段只在客户端和邻接服务端产生作用,所以也必须包含Connection字段,且值为Upgrade,代理会在转发前删除Upgrade字段和Connection字段,然后再进行转发。
  • 请求首部必须包含Sec-WebSocket-Version字段,值为13,用于指定协议版本
  • 请求首部必须包含Sec-WebSocket-Key字段,值必须为一个16位的base64编码的字符串

服务端接收握手

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 2EoQihfR9RZchLRSwVgDycMsrOE=
Origin: http://localhost:8080

对请求头中的信息进行验证,若验证通过则返回状态码为101的响应,在此只对Sec-WebSocket-Key字段的验证过程进行简单描述,如下:

  1. 服务端将请求头中Sec-WebSocket-Key字段的值拿出来与GUID进行拼接
  2. 将拼接后的值使用SHA-1进行计算出摘要
  3. 使用base64对摘要进行编码
  4. 将编码值设置为响应头字段Sec-WebSocket-Accept的值,返回至客户端
  5. 客户端使用同样的流程,并将最终的结果与服务端返回的值进行比对,若相等,则验证通过

算法的简单Node.js实现如下:

安装依赖

 yarn add js-sha1
const requestSecWebSocketKey = "ddWE76tEFVgUfKjHIWDWvw==";
const guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
const sha1 = require("js-sha1");
var hash = sha1.create();
hash.update(requestSecWebSocketKey);
hash.update(guid);
const result = Buffer.from(hash.digest(), "utf-8").toString("base64");
console.log(result);// 2EoQihfR9RZchLRSwVgDycMsrOE=

具体的验证规则请查阅RFC6455

应用

使用Node.js搭建简单的WebSocket服务端

安装依赖

yarn add websocket
var WebSocketServer = require("websocket").server;
var http = require("http");

// 创建http服务
var server = http.createServer(function (request, response) {
  response.writeHead(404);
  response.end();
});
server.listen(3000, function () {
  console.log(new Date() + " Server is listening on port 3000");
});

// 基于http服务创建websocket服务
wsServer = new WebSocketServer({
  httpServer: server,
  autoAcceptConnections: false,
});

// websocket Handlers
const handlerMap = new Map();

// 初始化 websocket Handlers
handlerMap.set("/hello", function (msg) {
  if (msg.type === "utf8") {
    const obj = JSON.parse(msg.utf8Data);
    this.send(
      JSON.stringify({
        name: "SERVER",
        message: `你好 ${obj.name}`,
      })
    );
  }
});

// 对请求做处理
wsServer.on("request", function (request) {
  if (handlerMap.has(request.resource)) {
    const connection = request.accept(null, request.origin);
    connection.on("message", (...arg) => {
      handlerMap.get(request.resource).apply(connection, arg);
    });
  } else {
    request.reject();
    return;
  }
});

基于W3C标准的WebSocket API客户端

以下代码基于HTML5 WebSocket,相关API 参照 MDN WebSocket

// 建立连接
const websocket = new WebSocket("ws://localhost:3000/hello");
// 建立连接成功时调用 即readyState为 WebSocket.OPEN 时
websocket.onopen = () => {
	websocket.send(
		JSON.stringify({
			name: "坠落清风",
			message: "你好 服务端!",
		})
	);
};
// 收到服务端消息时调用
websocket.onmessage = (event) => {
	const obj = JSON.parse(event.data);
	console.log(obj.message);
};
// 连接关闭后调用 即readyState为 WebSocket.CLOSED 时
websocket.onclose = function (event) {
	console.log("WebSocket is closed now. reason:" + event.reason);
	if (event.code) {
		// 根据关闭后的状态码,做出对应的处理
	}
};
// 发生错误时执行调用
websocket.onerror = function (event) {
	console.error("WebSocket error observed:", event);
};

除了HTML5 WebSocket之外,我们还可以使用一些开源的基于W3C标准的客户端实现,如:
SockJS-client
socket.io-client

子协议

子协议可以看作是客户端和服务端之间如何交换数据的约定。客户端可以通过以下方式请求服务器使用特定的子协议

const websocket = new WebSocket("ws://localhost:3000/hello",['jsonrpc','wamp']);

这些值将会包含在握手时HTTP请求的头部字段Sec-WebSocket-Protocol中。如果该头部被指定,服务端需要从中选择一个所支持的协议。

心跳检测

一种检测客户端与服务端之间是否还处于正常连接的机制(即验证连接有效性),顾名思义,类似于心跳,客户端每隔一段时间就会向服务端发送数据帧,如果连接正常,服务端会返回一个pong帧。

用于解决连接异常关闭,且无法被直接检测到的问题。

以下代码仅表达基本概念,若需要完备的心跳检测机制,可以去使用一些成熟的WebSocket库。

let count = 0;
const intervalId = setInterval(() => {
	// 如果大于或等于5次都未收到服务端的pong帧,则判定连接出现异常
	if (count >= 5) {
		clearInterval(intervalId);
		// 进行重连
		reconnect();
	}
	else {
		websocket.send("ping");
		count++;
	}
}, 30000);
websocket.onmessage = (event) => {
	if (event.data === "pong") {
		count = 0;
	}
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值