作者:禅与计算机程序设计艺术
1.简介
WebSocket(Web Socket)是一种在单个TCP连接上进行全双工通讯的网络通信协议。它提供了一种双向通信的方式,允许服务端主动推送信息给客户端。随着HTML5出现,WebSocket已经成为现代浏览器和服务器之间通信的重要方式之一。WebSocket可以将多种多样的应用场景引入到互联网应用中。本文主要讨论WebSocket在实际应用中的一些典型应用场景、基本概念、算法原理及具体的代码实例。最后对未来的发展方向和存在的问题进行展望。希望通过阅读本文,读者能够掌握WebSocket的相关知识并在实际工作中运用得当。
2. WebSocket概念
WebSocket定义于RFC 6455标准文档,其目标是在单个TCP连接上提供全双工通信通道,允许服务端主动向客户端推送数据,而且WebSocket协议使得客户端和服务器之间的数据交换变得更加简单、高效。WebSocket协议属于可选层协议,它依赖HTTP协议,如果用户请求使用WebSocket协议访问页面,那么浏览器会自动使用WebSocket协议与服务器建立连接,实现数据交换。
2.1 基本概念
- WebSocket是基于HTTP协议的一种通信协议,是一个独立的协议,它可以运行在TCP/IP四层以上传输层之上,它是由两端点——客户端和服务端组成。WebSocket协议是建立在TCP/IP之上的,需要建立两个TCP连接,一个用于客户端到服务端的连接,另一个用于服务端到客户端的连接。
- WebSocket有两种工作模式:WebSocket客户端和WebSocket服务器模式。
- WebSocket客户端工作流程如下:
- 用户首先向服务器发送HTTP请求,要求建立WebSocket连接,若服务器接受该请求则返回101状态码表示同意建立WebSocket连接;
- 如果建立成功,客户端和服务端之间就可以进行数据传输,采用的是TCP长连接机制。
- WebSocket服务器工作流程如下:
- 服务端接收到HTTP请求后,创建套接字,监听客户端连接请求;
- 当客户端发起连接请求时,服务端接收到请求并分配一个唯一的ID作为WebSocket连接标识符;
- 服务端发送101响应,表明已同意建立WebSocket连接,然后等待客户端发送数据;
- 一旦客户端发送数据,服务端立即处理,并把数据发送给其他所有在线客户端。
- WebSocket协议是一个双向通信协议,客户端和服务端都可以向对方发送消息,所以需要客户端和服务端都实现相应的协议。比如WebSocket客户端需要实现WebSocket API接口,而服务端需要部署相应的WebSocket服务器程序。
- WebSocket协议中有很多不同的应用场景,包括:聊天系统、实时数据推送、游戏开发等。其中最常用的就是实时数据推送,即服务端向客户端推送最新的数据。
2.2 数据帧
WebSocket数据帧分为文本数据帧、二进制数据帧两种类型。文本数据帧一般用于发送文本数据,而二进制数据帧用于发送如图片、视频、音频等二进制文件。WebSocket协议定义了以下规则来区分数据帧: - 每个数据帧都是以固定长度的消息头开头,消息头中指定了数据帧的类型、长度、扩展字段等。
- 在数据帧头部还有一个FIN位(Final Frame),用来标志数据帧是否是最后一个数据帧,即在一次完整的消息中,若有多个数据帧,则第一个数据帧的FIN为0,中间的数据帧的FIN均为1,最后一个数据帧的FIN为0。
- 在消息头之后,紧跟着的是数据体。对于文本类型的数据帧来说,数据体就是文本字符串;对于二进制类型的数据帧来说,数据体就是字节数组或文件流。
2.3 消息类型
WebSocket协议定义了4种不同类型的消息: - 文本类型(TEXT):用于发送UTF-8编码的文本数据。
- 二进制类型(BINARY):用于发送任意二进制数据,如图像、视频、音频等。
- Ping消息:用于检查客户端与服务器之间的连接是否正常。
- Pong消息:服务器在收到Ping消息后,应当返回Pong消息。
2.4 握手协商过程
WebSocket协议规定了客户端和服务端的握手协商过程。具体来说,握手协商过程分为三个步骤:
- 请求连接:首先,客户端发送一个WebSocket连接请求,包含请求资源的URI,以及一些请求首部字段(如Cookie)。
- 服务器回应连接:接着,服务端接收到客户端的连接请求,确认并回复101状态码,表明已同意建立WebSocket连接。同时,服务端可能会发送一些与WebSocket连接相关的首部字段。
- WebSocket连接建立:第三步,客户端和服务端完成握手协商,升级为WebSocket协议。至此,WebSocket连接就建立好了。
2.5 URI scheme
WebSocket协议的URI scheme为ws://或wss://,分别表示WebSocket的非加密连接和加密连接,采用http协议之上。
示例:
ws://example.com/websocket
wss://secure.example.com:8080/websocket
3. WebSocket基本算法原理
WebSocket协议在协议栈中处于第7层,因此可以参与到互联网协议栈中各种通信进程中,例如HTTP请求、HTTPS请求、FTP请求等。WebSocket采用客户端/服务器模型,包含客户端和服务器两个部分,并且每个部分都可以主动发起连接请求,也可以被动接受连接请求。为了建立WebSocket连接,客户端和服务器都会在HTTP协议之上进行一次握手,连接建立完成后,双方可以直接进行消息的传递。WebSocket协议中定义了一系列功能,比如WebSocket允许服务端主动推送信息给客户端,并支持服务器向客户端推送数据,WebSocket服务器可以支持推送消息、支持多路复用、支持压缩等。下面,我们结合WebSocket协议基本算法原理,分析一下具体的应用场景、基本概念、算法原理及具体的代码实例。
4. WebSocket实战案例——发布订阅系统
发布订阅(Publish/Subscribe)模式是面向消息的分布式计算模型,它是一种在消息通道上进行异步通信的模式,允许一组终端(称为发布者)发送消息,而不必知道谁对这些消息感兴趣。消息通常是有限的,因此发布者和订阅者的数量往往是多对多的关系,每个终端都可以作为发布者,也可以作为订阅者,因此订阅者必须保持健康状态,以便在需要时接收发布者的消息。
通过WebSocket协议,我们可以很方便地构建发布订阅系统,下面用发布订阅系统来实现简单的聊天室功能。首先,我们编写WebSocket服务端代码:
const express = require('express');
const app = express();
// 保存订阅的客户端对象列表
let subscribers = [];
app.use(express.json());
// 启动WebSocket服务
const server = require('http').createServer(app);
const wss = new (require('ws')).Server({server});
// 监听新连接事件
wss.on('connection', ws => {
// 将新的客户端添加到订阅者列表中
console.log(`[Server] New client connected`);
subscribers.push(ws);
// 向所有订阅者发送消息
const message = JSON.stringify({
type:'message',
data: `A new subscriber has joined`
});
broadcast(message);
// 监听客户端关闭事件
ws.on('close', () => {
// 从订阅者列表中移除该客户端
subscribers.splice(subscribers.indexOf(ws), 1);
// 向所有订阅者发送消息
const leftMessage = JSON.stringify({
type: 'left',
data: `${ws._socket.remoteAddress} disconnected`
});
broadcast(leftMessage);
console.log(`[Server] ${ws._socket.remoteAddress} disconnected`);
});
});
function broadcast(data) {
for (let i = 0; i < subscribers.length; i++) {
try {
// 向订阅者发送消息
subscribers[i].send(data);
} catch (err) {}
}
}
// 启动服务端口
server.listen(3000, function() {
console.log('[Server] Listening on port %d', server.address().port);
});
上面代码主要负责启动WebSocket服务,保存客户端订阅者列表,并管理订阅者间的消息广播。其中,broadcast函数用于将消息广播给所有订阅者,包括新增订阅者加入后的欢迎消息和断开连接后的通知消息。
接下来,我们编写WebSocket客户端代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket Chat</title>
</head>
<body>
<!-- 欢迎信息 -->
<h1 id="welcome"></h1>
<!-- 消息列表 -->
<ul id="messages"></ul>
<!-- 输入框 -->
<input type="text" id="messageBox" placeholder="Say something..." />
<!-- 脚本 -->
<script src="/socket.io/socket.io.js"></script>
<script>
// 创建Socket连接
let socket = io();
// 获取欢迎信息
socket.emit('join', {username: prompt("Please enter your name:")});
// 绑定收到的消息
socket.on('message', msg => {
const li = document.createElement('li');
li.textContent = `${msg.username}: ${msg.content}`;
document.getElementById('messages').appendChild(li);
});
// 绑定用户加入信息
socket.on('joined', username => {
document.getElementById('welcome').innerHTML = `<p>Welcome to the chat room, ${username}!</p>`;
});
// 绑定用户离开信息
socket.on('left', username => {
const li = document.createTextNode(`${username} left.`);
document.getElementById('messages').appendChild(li);
});
// 绑定提交按钮点击事件
document.getElementById('messageBox').addEventListener('keypress', e => {
if (e.keyCode === 13 &&!e.shiftKey) {
e.preventDefault();
// 获取输入的内容
const content = document.getElementById('messageBox').value;
// 清空输入框
document.getElementById('messageBox').value = '';
// 发送消息
socket.emit('message', {
username: prompt("Enter your name:"),
content: content
});
}
});
</script>
</body>
</html>
上面代码主要负责初始化WebSocket连接,监听消息、欢迎消息和离开消息的接收,以及用户输入消息发送。由于WebSocket协议没有严格的客户端-服务器模式的实现,因此这里的WebSocket客户端也具有“模拟”客户端的效果,所以只需准备好一个普通的HTML页面即可,不需要特别复杂的库或者插件。