Websocket学习笔记

WebSocket是什么?

WebSocket是一种在Web应用程序中实现实时双向通信的协议。它允许在客户端和服务器之间建立持久性连接,使得双方可以通过该连接进行长时间的数据传输。

WebSocket协议位于应用层,它提供了一种基于TCP协议的全双工通信机制,WebSocket在建立连接时依赖HTTP/HTTPS协议。

主要用途

WebSocket的主要用途是实现实时的双向通信。它可以用于许多不同类型的应用,包括但不限于:

  1. 在线聊天应用:允许用户实时发送和接收消息,而无需页面刷新或轮询服务器。
  2. 实时协作应用:支持多用户实时编辑文档或共享白板等场景。
  3. 实时游戏:允许多个玩家之间进行实时的游戏交互。
  4. 实时数据展示:用于显示实时数据,如股票市场变化、天气预报更新等。
  5. 实时通知和提醒:用于向用户发送实时的通知消息,如新邮件提醒、社交媒体通知等。
  6. 在线会议和视频通话:支持实时的音视频通信。

通信模式

WebSocket 支持以下几种通信模式:

  1. 一对一通信
    • WebSocket 最常见的使用方式是一对一的通信模式,其中一个客户端与一个 WebSocket 服务器之间建立一条连接,实现双向的实时通信。这种模式适用于聊天应用、实时通知等场景。
  2. 一对多通信
    • WebSocket 也支持多个客户端同时连接到同一个 WebSocket 服务器的通信模式。在这种模式下,服务器可以向所有连接到它的客户端广播消息,或者向特定的客户端发送消息。这种模式适用于群聊、广播通知等场景。
  3. 多对多通信
    • WebSocket 支持多对多的通信模式,其中多个客户端之间建立 WebSocket 连接,并且能够相互之间进行通信。在这种模式下,每个客户端都可以与其他客户端进行直接的双向通信,而服务器则充当中间人进行消息的转发和管理。这种模式适用于实时协作、实时游戏等应用场景。

优缺点

优点缺点
实现了实时双向通信不支持跨域通信
具有较的网络开销和较实时部分浏览器和网络设备可能不支持 WebSocket
简单易用,易于集成到现有的 Web 应用中需要额外的服务器资源来维护长连接
支持服务器主动向客户端推送数据可能会增加服务器端的复杂性
可以减少 HTTP 请求头和响应头的大小,降低网络延迟流量需要保证 WebSocket 连接的稳定性可靠性

报文格式

WebSocket报文格式相对简单,由Frame)组成。

基本的WebSocket帧结构包括:FINRSVOpcodeMaskPayload LengthMasking KeyPayload Data等字段。

  1. FIN (1 bit):表示该帧是否是消息的最后一帧。如果该位被设置为1,表示这是消息的最后一帧;如果为0,表示后续还有帧组成同一个消息。
  2. RSV1, RSV2, RSV3 (1 bit each):保留位,用于扩展,目前应该始终为0。
  3. Opcode (4 bits):指示帧的类型,有以下几种可能的取值:
    • 0x0:表示一个连续的数据帧。
    • 0x1:表示一个文本帧。
    • 0x2:表示一个二进制帧。
    • 0x8:表示一个连接关闭帧。
    • 0x9:表示一个Ping帧。
    • 0xA:表示一个Pong帧。
    • 其他值为保留值,不常用。
  4. Mask (1 bit):标识是否对Payload Data进行掩码处理。如果为1,表示数据被掩码处理;如果为0,表示数据没有被掩码处理。
  5. Payload Length (7 bits):指示Payload Data的长度。如果Payload Length的值在0~125之间,则表示Payload Data的实际长度。如果值为126,则后续2个字节将被用来表示Payload Data的扩展长度。如果值为127,则后续8个字节将被用来表示Payload Data的扩展长度。
  6. Extended Payload Length:当Payload Length的值为126或127时,用于表示Payload Data的扩展长度。长度为2个字节或8个字节,取决于Payload Length字段的值。
  7. Masking-key (4 bytes):如果Mask标志位为1,则存在4字节的掩码密钥,用于对Payload Data进行解码。如果Mask标志位为0,则不存在该字段。
  8. Payload Data:WebSocket 帧中携带的实际数据,它是根据 Payload Length 字段指示的长度而确定的。在 WebSocket 协议中,Payload Data 可以是文本数据、二进制数据或其他任何形式的数据,具体取决于发送方和接收方之间的协商。

握手过程

WebSocket 握手过程是客户端和服务器之间建立 WebSocket 连接的过程,它遵循 HTTP 协议的规范。

下面是 WebSocket 握手的基本步骤:

  1. 客户端发送握手请求
    • 客户端向服务器发送一个 HTTP 请求,请求的路径是 WebSocket 的目标地址(URL)。
    • 请求头中包含了一些 WebSocket 相关的头信息,如 Upgrade: websocketConnection: UpgradeSec-WebSocket-Key 等。其中,Sec-WebSocket-Key 是一个随机的 Base64 编码的字符串,用于确保服务器能够识别客户端的 WebSocket 请求。
  2. 服务器响应握手请求
    • 服务器收到客户端的握手请求后,返回一个 HTTP 响应,状态码为 101 Switching Protocols,表示协议切换。
    • 响应头中包含了一些必要的信息,如 Upgrade: websocketConnection: UpgradeSec-WebSocket-Accept 等。其中,Sec-WebSocket-Accept 是通过将客户端请求头中的 Sec-WebSocket-Key 加上一个特定的 GUID(全局唯一标识符),然后计算 SHA-1 摘要后进行 Base64 编码得到的。
  3. 建立连接
    • 客户端收到服务器的响应后,表示 WebSocket 连接建立成功,此时客户端和服务器之间的通信将转换为 WebSocket 协议。
    • 之后的通信将通过 WebSocket 协议进行,不再受限于传统的 HTTP 请求-响应模式。

如何使用?

绝大多数主流的编程语言都有支持 WebSocket 的库或框架,使得开发者可以方便地在其应用程序中实现 WebSocket 功能。以下是一些常见编程语言及其对应的 WebSocket 库:

  1. JavaScript/Node.jswssocket.io
  2. Pythonwebsocketssocket.io-client
  3. Javajavax.websocketJava-WebSocket
  4. C#WebSocketSharpSignalR
  5. Gogorilla/websocketnhooyr/websocket
  6. Rubywebsocket-rubyfaye-websocket
  7. PHPratchet/pawlcboden/Ratchet

websocket如何保证通信的安全性

WebSocket 本身并没有提供通信的安全性,但可以通过其他机制来增强通信的安全性,例如使用 TLS/SSL 加密连接。

以下是保证 WebSocket 通信安全性的一些方法:

  1. 使用 TLS/SSL 加密连接:通过在 WebSocket 连接上使用 TLS/SSL 加密,可以确保通信数据在传输过程中是加密的,从而防止数据被窃取或篡改。您可以在 WebSocket URL 中使用 wss:// 协议来指定安全的 WebSocket 连接。
  2. 身份验证:您可以在 WebSocket 连接建立时进行客户端和服务器之间的身份验证,以确保只有授权用户才能建立连接并进行通信。可以使用令牌、证书或其他身份验证机制来实现身份验证。
  3. 消息签名和验证:在发送和接收消息时,可以使用数字签名来确保消息的完整性和真实性。发送方使用私钥对消息进行签名,接收方使用公钥验证签名,以确保消息没有被篡改。
  4. 防止跨站请求伪造(CSRF)攻击:在使用 WebSocket 时,确保采取措施来防止 CSRF 攻击。可以通过实现基于令牌的身份验证和验证来源来防止 CSRF 攻击。
  5. 限制连接:可以限制允许连接到 WebSocket 服务器的客户端数量,并实施连接速率限制,以防止恶意行为和拒绝服务攻击。
  6. 定期审查和更新安全策略:定期审查和更新安全策略,包括 TLS/SSL 配置、身份验证机制和访问控制策略,以确保与最新的安全标准和最佳实践保持一致。

Python使用WebSocket

以下是一些常用的Python WebSocket库:

库名称说明
websockets1. 提供了简单而强大的API,易于使用
2. 支持异步操作,适用于高性能的WebSocket应用程序。
3. 可以同时作为客户端服务器端使用。
4. 具有良好的文档和活跃的社区支持。
autobahn-python1. 功能丰富,提供了完整的WebSocket协议实现。
2. 支持高级特性,如消息压缩、WebSocket扩展等。
3. 提供了WebSocket客户端服务器端的实现。
4. 与Twisted框架紧密集成,支持异步操作。
5. 适用于复杂的WebSocket应用场景,如实时游戏、聊天应用等。
socket.io-client-python1. 是Python版本的Socket.IO客户端库,兼容JavaScript版Socket.IO
2. 提供了与JavaScript版Socket.IO类似的API,易于使用
3. 支持WebSocket传输和轮询传输两种方式。
4. 适用于与使用Socket.IO实现的WebSocket服务器进行通信。
Tornado1. 是一个强大的异步Web框架,支持WebSocket通信。
2. 提供了WebSocket客户端服务器端的实现。
3. 集成了WebSocket路由和处理器,方便WebSocket应用程序的开发。
4. 适用于需要高性能异步通信的应用场景,如实时数据传输、即时通讯等。
django-websocket-redis1. 基于Django框架,使用Redis作为消息队列,用于实现WebSocket通信。
2. 可以轻松地集成到Django应用程序中。
3. 提供了简单而有效的API来处理WebSocket连接。
4. 适用于需要在Django应用程序中实现WebSocket通信的场景。
aiowebsocket1. 是一个基于异步IO的WebSocket库,专门用于Python的asyncio框架。
2. 提供了异步的WebSocket客户端和服务器端实现。
3. 适用于需要在asyncio框架下进行高性能异步通信的应用场景。
Flask-SocketIO1. 支持房间管理功能,可以将客户端分组到不同的房间中,并在房间内进行广播或单播消息。
2. 具有跨浏览器兼容性,能够在各种主流浏览器中稳定运行。
3. 集成简单,易于安装和使用,且有良好的文档和社区支持。

websockets模块使用

详细用法请查阅:

Python3 学习笔记 - 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进入聊天室进行群聊:


Hack All Sec 的博客

  • 20
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值