WebSocket是什么?
WebSocket是一种在Web应用程序中实现实时双向通信的协议。它允许在客户端和服务器之间建立持久性连接,使得双方可以通过该连接进行长时间的数据传输。
WebSocket协议位于应用层,它提供了一种基于TCP协议的全双工通信机制,WebSocket在建立连接时依赖HTTP/HTTPS协议。
主要用途
WebSocket的主要用途是实现实时的双向通信。它可以用于许多不同类型的应用,包括但不限于:
- 在线聊天应用:允许用户实时发送和接收消息,而无需页面刷新或轮询服务器。
- 实时协作应用:支持多用户实时编辑文档或共享白板等场景。
- 实时游戏:允许多个玩家之间进行实时的游戏交互。
- 实时数据展示:用于显示实时数据,如股票市场变化、天气预报更新等。
- 实时通知和提醒:用于向用户发送实时的通知消息,如新邮件提醒、社交媒体通知等。
- 在线会议和视频通话:支持实时的音视频通信。
通信模式
WebSocket 支持以下几种通信模式:
- 一对一通信:
- WebSocket 最常见的使用方式是一对一的通信模式,其中一个客户端与一个 WebSocket 服务器之间建立一条连接,实现双向的实时通信。这种模式适用于聊天应用、实时通知等场景。
- 一对多通信:
- WebSocket 也支持多个客户端同时连接到同一个 WebSocket 服务器的通信模式。在这种模式下,服务器可以向所有连接到它的客户端广播消息,或者向特定的客户端发送消息。这种模式适用于群聊、广播通知等场景。
- 多对多通信:
- WebSocket 支持多对多的通信模式,其中多个客户端之间建立 WebSocket 连接,并且能够相互之间进行通信。在这种模式下,每个客户端都可以与其他客户端进行直接的双向通信,而服务器则充当中间人进行消息的转发和管理。这种模式适用于实时协作、实时游戏等应用场景。
优缺点
优点 | 缺点 |
---|---|
实现了实时双向通信 | 不支持跨域通信 |
具有较低的网络开销和较高的实时性 | 部分浏览器和网络设备可能不支持 WebSocket |
简单易用,易于集成到现有的 Web 应用中 | 需要额外的服务器资源来维护长连接 |
支持服务器主动向客户端推送数据 | 可能会增加服务器端的复杂性 |
可以减少 HTTP 请求头和响应头的大小,降低网络延迟和流量 | 需要保证 WebSocket 连接的稳定性和可靠性 |
报文格式
WebSocket报文格式相对简单,由帧(Frame)组成。
基本的WebSocket帧结构包括:FIN
、RSV
、Opcode
、Mask
、Payload Length
、Masking Key
和Payload Data
等字段。
- FIN (1 bit):表示该帧是否是消息的最后一帧。如果该位被设置为1,表示这是消息的最后一帧;如果为0,表示后续还有帧组成同一个消息。
- RSV1, RSV2, RSV3 (1 bit each):保留位,用于扩展,目前应该始终为0。
- Opcode (4 bits):指示帧的类型,有以下几种可能的取值:
- 0x0:表示一个连续的数据帧。
- 0x1:表示一个文本帧。
- 0x2:表示一个二进制帧。
- 0x8:表示一个连接关闭帧。
- 0x9:表示一个Ping帧。
- 0xA:表示一个Pong帧。
- 其他值为保留值,不常用。
- Mask (1 bit):标识是否对Payload Data进行掩码处理。如果为1,表示数据被掩码处理;如果为0,表示数据没有被掩码处理。
- Payload Length (7 bits):指示Payload Data的长度。如果Payload Length的值在0~125之间,则表示Payload Data的实际长度。如果值为126,则后续2个字节将被用来表示Payload Data的扩展长度。如果值为127,则后续8个字节将被用来表示Payload Data的扩展长度。
- Extended Payload Length:当Payload Length的值为126或127时,用于表示Payload Data的扩展长度。长度为2个字节或8个字节,取决于Payload Length字段的值。
- Masking-key (4 bytes):如果Mask标志位为1,则存在4字节的掩码密钥,用于对Payload Data进行解码。如果Mask标志位为0,则不存在该字段。
- Payload Data:WebSocket 帧中携带的实际数据,它是根据 Payload Length 字段指示的长度而确定的。在 WebSocket 协议中,Payload Data 可以是文本数据、二进制数据或其他任何形式的数据,具体取决于发送方和接收方之间的协商。
握手过程
WebSocket 握手过程是客户端和服务器之间建立 WebSocket 连接的过程,它遵循 HTTP 协议的规范。
下面是 WebSocket 握手的基本步骤:
- 客户端发送握手请求:
- 客户端向服务器发送一个 HTTP 请求,请求的路径是 WebSocket 的目标地址(URL)。
- 请求头中包含了一些 WebSocket 相关的头信息,如
Upgrade: websocket
、Connection: Upgrade
、Sec-WebSocket-Key
等。其中,Sec-WebSocket-Key
是一个随机的 Base64 编码的字符串,用于确保服务器能够识别客户端的 WebSocket 请求。
- 服务器响应握手请求:
- 服务器收到客户端的握手请求后,返回一个 HTTP 响应,状态码为
101 Switching Protocols
,表示协议切换。 - 响应头中包含了一些必要的信息,如
Upgrade: websocket
、Connection: Upgrade
、Sec-WebSocket-Accept
等。其中,Sec-WebSocket-Accept
是通过将客户端请求头中的Sec-WebSocket-Key
加上一个特定的 GUID(全局唯一标识符),然后计算 SHA-1 摘要后进行 Base64 编码得到的。
- 服务器收到客户端的握手请求后,返回一个 HTTP 响应,状态码为
- 建立连接:
- 客户端收到服务器的响应后,表示 WebSocket 连接建立成功,此时客户端和服务器之间的通信将转换为 WebSocket 协议。
- 之后的通信将通过 WebSocket 协议进行,不再受限于传统的 HTTP 请求-响应模式。
如何使用?
绝大多数主流的编程语言都有支持 WebSocket 的库或框架,使得开发者可以方便地在其应用程序中实现 WebSocket 功能。以下是一些常见编程语言及其对应的 WebSocket 库:
- JavaScript/Node.js:
ws
、socket.io
- Python:
websockets
、socket.io-client
- Java:
javax.websocket
、Java-WebSocket
- C#:
WebSocketSharp
、SignalR
- Go:
gorilla/websocket
、nhooyr/websocket
- Ruby:
websocket-ruby
、faye-websocket
- PHP:
ratchet/pawl
、cboden/Ratchet
websocket如何保证通信的安全性
WebSocket 本身并没有提供通信的安全性,但可以通过其他机制来增强通信的安全性,例如使用 TLS/SSL 加密连接。
以下是保证 WebSocket 通信安全性的一些方法:
- 使用 TLS/SSL 加密连接:通过在 WebSocket 连接上使用 TLS/SSL 加密,可以确保通信数据在传输过程中是加密的,从而防止数据被窃取或篡改。您可以在 WebSocket URL 中使用
wss://
协议来指定安全的 WebSocket 连接。 - 身份验证:您可以在 WebSocket 连接建立时进行客户端和服务器之间的身份验证,以确保只有授权用户才能建立连接并进行通信。可以使用令牌、证书或其他身份验证机制来实现身份验证。
- 消息签名和验证:在发送和接收消息时,可以使用数字签名来确保消息的完整性和真实性。发送方使用私钥对消息进行签名,接收方使用公钥验证签名,以确保消息没有被篡改。
- 防止跨站请求伪造(CSRF)攻击:在使用 WebSocket 时,确保采取措施来防止 CSRF 攻击。可以通过实现基于令牌的身份验证和验证来源来防止 CSRF 攻击。
- 限制连接:可以限制允许连接到 WebSocket 服务器的客户端数量,并实施连接速率限制,以防止恶意行为和拒绝服务攻击。
- 定期审查和更新安全策略:定期审查和更新安全策略,包括 TLS/SSL 配置、身份验证机制和访问控制策略,以确保与最新的安全标准和最佳实践保持一致。
Python使用WebSocket
以下是一些常用的Python WebSocket库:
库名称 | 说明 |
---|---|
websockets | 1. 提供了简单而强大的API,易于使用 。 2. 支持 异步 操作,适用于高性能的WebSocket应用程序。3. 可以同时作为 客户端 和服务器 端使用。 4. 具有良好的文档和活跃的社区支持。 |
autobahn-python | 1. 功能丰富 ,提供了完整的WebSocket协议实现。 2. 支持 高级特性 ,如消息压缩、WebSocket扩展等。 3. 提供了WebSocket 客户端 和服务器 端的实现。 4. 与Twisted框架紧密集成,支持 异步 操作。 5. 适用于复杂的WebSocket应用场景,如实时游戏、聊天应用等。 |
socket.io-client-python | 1. 是Python版本的Socket.IO客户端库,兼容JavaScript版Socket.IO 。 2. 提供了与JavaScript版Socket.IO类似的API, 易于使用 。 3. 支持WebSocket传输和轮询传输两种方式。 4. 适用于与使用Socket.IO实现的WebSocket服务器进行通信。 |
Tornado | 1. 是一个强大的异步Web框架 ,支持WebSocket通信。 2. 提供了WebSocket 客户端 和服务器 端的实现。 3. 集成了WebSocket路由和处理器,方便WebSocket应用程序的开发。 4. 适用于需要高性能异步通信的应用场景,如实时数据传输、即时通讯等。 |
django-websocket-redis | 1. 基于Django框架,使用Redis作为消息队列,用于实现WebSocket通信。 2. 可以轻松地集成到Django应用程序中。 3. 提供了简单而有效的API来处理WebSocket连接。 4. 适用于需要在Django应用程序中实现WebSocket通信的场景。 |
aiowebsocket | 1. 是一个基于异步IO的WebSocket库,专门用于Python的asyncio框架。 2. 提供了异步的WebSocket客户端和服务器端实现。 3. 适用于需要在asyncio框架下进行高性能异步通信的应用场景。 |
Flask-SocketIO | 1. 支持房间管理功能,可以将客户端分组到不同的房间中,并在房间内进行广播或单播消息。 2. 具有跨浏览器兼容性,能够在各种主流浏览器中稳定运行。 3. 集成简单,易于安装和使用,且有良好的文档和社区支持。 |
websockets模块使用
详细用法请查阅:
使用websockets模块实现一个简单的聊天室案例:用户通过浏览器进入聊天室,多个用户可实时聊天。前端采用Javascript+html
。前端代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket 聊天室</title>
<style>
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
}
.container {
width: 80%;
margin: 0 auto;
text-align: center;
padding-top: 20px;
}
.chat-container {
display: flex;
flex-direction: column;
border: 1px solid #ccc;
border-radius: 5px;
overflow: hidden;
margin-top: 20px;
height: 400px; /* 设置容器的固定高度 */
}
.message-container {
flex-grow: 1;
overflow-y: auto;
padding: 10px;
}
.message {
margin-bottom: 10px;
max-width: 100%; /* 让消息div宽度占满容器 */
}
.message.received {
text-align: left;
background-color: #f0f0f0;
border-radius: 5px;
padding: 5px 10px;
}
.message.sent {
text-align: right;
background-color: #e2f3f5;
border-radius: 5px;
padding: 5px 10px;
}
#message {
width: 100%;
height: 40px;
margin-top: 20px;
border: 1px solid #ccc;
border-radius: 5px;
padding: 0 10px;
box-sizing: border-box;
}
</style>
</head>
<body>
<div class="container">
<h1>WebSocket 聊天室</h1>
<div class="chat-container">
<div class="message-container" id="messages"></div>
<input type="text" id="message" placeholder="输入消息并按 Enter 发送">
</div>
</div>
<script>
var socket = new WebSocket("ws://localhost:8765");
socket.onopen = function(event) {
console.log("WebSocket 连接已建立");
};
socket.onmessage = function(event) {
var messageDiv = document.createElement("div");
messageDiv.textContent = event.data;
messageDiv.className = "message received";
document.getElementById("messages").appendChild(messageDiv);
scrollToBottom();
};
document.getElementById("message").addEventListener("keypress", function(event) {
if (event.key === "Enter") {
var message = this.value;
var messageDiv = document.createElement("div");
messageDiv.textContent = message;
messageDiv.className = "message sent";
document.getElementById("messages").appendChild(messageDiv);
scrollToBottom();
socket.send(message);
this.value = "";
}
});
function scrollToBottom() {
var messageContainer = document.getElementById("messages");
messageContainer.scrollTop = messageContainer.scrollHeight;
}
</script>
</body>
</html>
后端代码如下:
import asyncio
import websockets
from datetime import datetime
# 客户端列表,用于存储所有连接到服务器的客户端
clients = set()
# 处理新连接的函数
async def handle_client(websocket, path):
# 添加新连接的客户端到客户端列表
clients.add(websocket)
try:
# 获取当前时间,格式化时分秒
now = datetime.now()
time_format = now.strftime("%H:%M:%S")
# 发送欢迎消息给新连接的客户端
client_addr = websocket.remote_address
print(f"{time_format} Client {client_addr} connected!")
welcome_message = "已连接服务器"
await websocket.send(welcome_message)
# 循环处理客户端发送的消息
async for message in websocket:
# 将收到的消息发送给所有连接到服务器的客户端
for client in clients:
# 获取当前时间,格式化时分秒
now = datetime.now()
time_format = now.strftime("%H:%M:%S")
print(f"{time_format} Received Client {client.remote_address} message: {message}") # 打印收到的消息
await client.send(message)
finally:
# 当连接关闭时,从客户端列表中移除对应的客户端
client_addr = websocket.remote_address
print(f"Client {client_addr} disconnected!")
clients.remove(websocket)
# 启动 WebSocket 服务器
start_server = websockets.serve(handle_client, "localhost", 8765)
# 启动事件循环,等待连接
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
启动WebSocket服务后,启用http服务,然后浏览器即可连接websocket进入聊天室进行群聊: