当前位置: 首页 > news >正文

手搓聊天室1.0.0----基于ws和protobuf协议

                                    前言

本系列文章本旨为讲解如何利用websocket和protobuf手搓聊天室,并如何破解这种加密,分为三阶段,第一阶段讲解websocketprotobuf相关知识,并手搓简单Chatbox(也即本文),第二阶段为如何讲解实现websocket爬虫(讲解抖音直播弹幕,b站直播弹幕等),第三阶段为不断完善Chatbox,形成可投入产品。代码讲放在https://github.com/mingheyan/Chatbox,分为不同阶段不同版本

先看本文效果

image

websockets

WebSocket 协议最初是为了改善 Web 应用中实时通信的性能而设计的。传统的 HTTP 请求-响应模型在处理实时数据时效率较低,因为每次通信都需要建立和关闭连接。而 WebSocket 提供了一种持久连接的方式,使得客户端和服务器可以随时发送和接收消息。

WebSocket 是一种基于 TCP 的双向通信协议,允许客户端和服务器之间进行实时、全双工的数据传输,广泛应用于在线聊天、实时游戏、物联网等领域。以下是关于 WebSocket 协议的详细讲解,包括其工作原理、客户端(JavaScript)和服务端(Python)的实现方式。

下载pip install websockets

在 Python 中,websockets 是一个常用的库,用于实现 WebSocket 协议的服务器和客户端。以下是 websockets 库中一些常用的方法和功能:

1. 服务器端方法

  • websockets.serve()
    用于创建 WebSocket 服务器。它接受一个处理函数(用于处理 WebSocket 连接)、主机地址和端口号等参数。

    示例:

    import asyncio
    import websockets
    
    async def handler(websocket, path):
        async for message in websocket:
            await websocket.send(f"Echo: {message}")
    
    start_server = websockets.serve(handler, "localhost", 8765)
    asyncio.get_event_loop().run_until_complete(start_server)
    asyncio.get_event_loop().run_forever()
    
  • websocket.recv()
    用于接收客户端发送的消息。它是一个异步方法,返回接收到的消息。

  • websocket.send()
    用于向客户端发送消息。它也是异步方法。

  • websocket.close()
    用于显式关闭 WebSocket 连接。

2. 客户端方法

  • websockets.connect()
    用于建立与 WebSocket 服务器的连接。

    示例:

    import asyncio
    import websockets
    
    async def client():
        async with websockets.connect("ws://localhost:8765") as websocket:
            await websocket.send("Hello, server!")
            response = await websocket.recv()
            print(f"Received: {response}")
    
    asyncio.run(client())
    
  • websocket.recv()websocket.send()
    与服务器端类似,客户端也可以使用这些方法接收和发送消息。

3. 其他功能

  • 广播消息
    服务器可以维护一个客户端连接集合,并向所有连接的客户端广播消息。

    示例:

    connected_clients = set()
    
    async def handler(websocket, path):
        connected_clients.add(websocket)
        try:
            async for message in websocket:
                await broadcast(message)
        finally:
            connected_clients.remove(websocket)
    
    async def broadcast(message):
        for client in connected_clients:
            await client.send(message)
    
  • 心跳机制
    WebSocket 协议支持心跳机制(ping/pong),websockets 库会自动处理这些机制,无需手动干预。

下面我们基于此来进行实现chatbox

JavaScript 客户端实现

在浏览器中,可以通过原生的 WebSocket API 建立 WebSocket 连接。以下是一个简单的实现示例:

import asyncio
import websockets

# 存储所有连接的客户端
Connections = set()

# 广播函数
async def broadcast(message):
    if Connections:
        await asyncio.wait([conn.send(message) for conn in Connections])

# 处理客户端连接
async def handler(websocket, path):
    # 添加新连接到集合
    Connections.add(websocket)
    try:
        async for message in websocket:
            # 接收到消息后进行广播
            await broadcast(message)
    finally:
        # 客户端断开连接时移除
        Connections.remove(websocket)

# 启动 WebSocket 服务
async def main():
    async with websockets.serve(handler, "localhost", 8080):
        await asyncio.Future()  # 保持服务运行

asyncio.run(main())

Python 服务端实现

在 Python 中,可以使用 websockets 库来创建 WebSocket 服务端。以下是一个简单的服务端实现示例:

const socket = new WebSocket('ws://localhost:8080');

socket.onopen = () => {
    console.log('连接到服务器');
    // 发送一条消息到服务器
    socket.send('Hello, Server!');
};

socket.onmessage = (event) => {
    console.log('从服务器收到消息:', event.data);
    // 可以将消息显示在页面上
    document.getElementById('messages').innerText += event.data + '\n';
};

socket.onerror = (error) => {
    console.error('WebSocket 错误:', error);
};

socket.onclose = () => {
    console.log('与服务器断开连接');
};

基于此,我们写了个基本的聊天室,来帮助我们深入了解websocket的过程,源代码在https://github.com/mingheyan/Chatbox

建立一个输入用户名的弹窗,其用户名自动存入
Connections = set()中,之后利用广播机制发送给客户端。
客户端
image
服务端
image

image

建立一个发送消息的组件,发送的消息使用广播机制,我们打开F12,到ws模块里面,如下图,当我们发送消息的时候,客户端会同步呈现,ws模块里面是我们发送的信息

image

image

image

基于此我们便直接答完了一个简单自定义聊天室1.0,在1.0版本,我们实现了

  • 简单的前端界面(有点丑),
  • 实现了用户组,和实时界面功能
  • 使用json定义数据结构

是不是非常简单捏
那我们加大点难度,了解protobuf模块,并使用protobuf来定义数据结构

Protocol Buffers(简称 Protobuf)是一种语言无关、平台无关的序列化数据格式,由 Google 开发并开源。它主要用于高效地存储和传输结构化数据,广泛应用于网络通信、数据存储、微服务通信等场景。以下是关于 Protobuf 的详细讲解,包括其设计原理、使用方法、以及在不同语言中的实现。


1. Protobuf 的设计原理

1.1 为什么需要 Protobuf?

在开发中,我们经常需要将数据序列化(将数据结构或对象状态转换为可存储或传输的格式)和反序列化(将序列化的数据恢复为原始数据结构)。常见的序列化方式包括 JSON、XML 等,但它们存在以下问题:

  • 性能问题:JSON 和 XML 的序列化和反序列化速度较慢,且数据体积较大。
  • 语言依赖:不同语言对序列化格式的支持程度不同,难以实现跨语言通信。
  • 版本兼容性:当数据结构发生变化时,老版本的代码可能无法解析新版本的数据。

Protobuf 通过以下方式解决了这些问题:

  • 高效性:Protobuf 的序列化速度比 XML 快 20-100 倍,数据体积比 JSON/XML 小 3-10 倍。
  • 语言无关性:Protobuf 提供了多种语言的实现,包括 C++、Java、Python、Go、JavaScript 等。
  • 版本兼容性:Protobuf 允许开发者在不破坏现有代码的情况下,对数据结构进行扩展。
1.2 基本概念
  • .proto 文件:定义了数据的结构。开发者通过 .proto 文件声明消息类型(类似于类的定义)。
  • 消息(Message):Protobuf 中的数据结构,类似于其他语言中的类或结构体。
  • 字段(Field):消息中的属性,每个字段都有一个名称、类型和编号。
  • 字段编号:字段编号是消息定义中的唯一标识符,用于在序列化和反序列化时区分字段。
1.3 数据类型

Protobuf 支持多种数据类型,包括:

  • 标量类型:如 int32floatdoublestring 等。
  • 复合类型:如 message(自定义消息类型)。
  • 特殊类型:如 enum(枚举类型)、map(键值对类型)。

2. Protobuf 的使用方法

2.1 定义 .proto 文件

假设我们正在开发一个用户管理系统,需要定义用户信息和用户列表。以下是 .proto 文件的示例:

syntax = "proto3";  // 指定 Protobuf 语法版本

package user;  // 定义包名,用于避免命名冲突

// 定义用户信息
message User {
    int32 id = 1;  // 用户ID
    string name = 2;  // 用户姓名
    string email = 3;  // 用户邮箱
    repeated string phone = 4;  // 用户电话号码(可重复字段)
}

// 定义用户列表
message UserList {
    repeated User users = 1;  // 用户数组
}
2.2 编译 .proto 文件

Protobuf 提供了一个编译器 protoc,用于将 .proto 文件编译为特定语言的代码。例如:

  • Python:生成 .py 文件。
  • Java:生成 .java 文件。
  • C++:生成 .h.cpp 文件。

以 Python 为例,编译命令如下:

protoc --python_out=. user.proto

这将生成一个 user_pb2.py 文件,其中包含了用户定义的消息类型和相关方法。

2.3 使用生成的代码

以下是 Python 中使用生成的代码的示例:

from user_pb2 import User, UserList

# 创建一个用户对象
user = User(id=1, name="Alice", email="alice@example.com")
user.phone.append("123-456-7890")
user.phone.append("098-765-4321")

# 创建用户列表
user_list = UserList()
user_list.users.append(user)

# 序列化为二进制数据
serialized_data = user_list.SerializeToString()
print("Serialized Data:", serialized_data)

# 反序列化为对象
new_user_list = UserList()
new_user_list.ParseFromString(serialized_data)
for user in new_user_list.users:
    print(f"User ID: {user.id}, Name: {user.name}, Email: {user.email}, Phones: {user.phone}")

image

3. Protobuf 的高级特性

3.1 枚举类型

Protobuf 支持定义枚举类型,用于表示一组固定的常量值。例如:

enum Role {
    ADMIN = 0;
    USER = 1;
    GUEST = 2;
}

message User {
    int32 id = 1;
    string name = 2;
    Role role = 3;  // 使用枚举类型
}
3.2 嵌套消息

可以在消息中嵌套其他消息类型。例如:

message Address {
    string street = 1;
    string city = 2;
    string state = 3;
}

message User {
    int32 id = 1;
    string name = 2;
    Address address = 3;  // 嵌套消息
}
3.3 重复字段

使用 repeated 关键字可以定义可重复字段,类似于数组或列表。例如:

message User {
    int32 id = 1;
    string name = 2;
    repeated string phone = 3;  // 可重复字段
}
3.4 默认值

Protobuf 为标量字段提供了默认值。例如:

  • 数字类型的默认值为 0
  • 字符串类型的默认值为 ""
  • 枚举类型的默认值为第一个枚举值。
3.5 版本兼容性

Protobuf 设计了强大的版本兼容性机制:

  • 向后兼容:添加新字段时,旧版本的代码仍然可以正常解析数据。
  • 向前兼容:删除字段或修改字段编号时,新版本的代码可以通过默认值处理旧数据。

4. Protobuf 的性能优势

4.1 数据体积小

Protobuf 的序列化数据体积比 JSON 和 XML 小得多。例如:

  • JSON:{"id":1,"name":"Alice","phones":["123-456-7890","098-765-4321"]}
  • Protobuf:二进制数据,体积更小。
4.2 序列化和反序列化速度快

Protobuf 的序列化和反序列化速度比 JSON 和 XML 快 20-100 倍,适合高性能场景。

4.3 语言无关性

Protobuf 提供了多种语言的实现,支持跨语言通信。例如:

  • Pythonuser_pb2.py
  • JavaUser.java
  • C++User.hUser.cpp

基于此我们对我们的聊天室使用protobuf进行存储数据
首先创建我们的存储文件格式:

chat.proto

image

客户端

在客户端引入protobuf文件

image
配置相关

image

发送逻辑改为

image
其中WebSocketMessage.encode(message).finish() 是 Protobuf 中用于序列化消息的方法调用
这样发送的数据变为二进制,打开F12查看,发现这里不再是明文数据
image

服务端

服务端可相对应的改

image

其中

data = WebSocketMessage()创建一个新的 WebSocketMessage Protobuf 对象

data.ParseFromString(message)ParseFromString 将二进制消息数据解析为 Protobuf 对象
message 是从客户端接收到的原始二进制数据

data.type == MessageType.CHAT_MESSAGE:

检查消息类型是否为聊天消息

SerializeToString() 将 Protobuf 对象转换回二进制格式,传给客户端广播

这样既将存储手段变为protobuf,并且websocket里面传输的数据变成二进制

image

在2.0版本,我们使用

  • 主要使用protobuf来定义数据
  • 间接实现了ws加密

下文我们将进行抖音直播间弹幕逆向和b站直播弹幕逆向

项目地址:https://github.com/mingheyan/Chatbox
将持续更新

相关文章:

  • git 查看某个函数的所有提交日志
  • 友思特应用 | 行业首创:基于深度学习视觉平台的AI驱动轮胎检测自动化
  • Linux安装Elasticsearch集群-----docker安装es集群
  • FastJson:JSON JSONObject JSONArray详解以及SimplePropertyPreFilter 的介绍
  • 虚拟电商-延迟任务系统的微服务改造(二)注册中心和Feign调用
  • RAG工具框架针对的常见问题
  • 前端字段名和后端不一致?解锁 JSON 映射的“隐藏规则” !!!
  • LeetCode 第19~21题
  • 【Qt】信号signal是单向的
  • YZi Labs 谈对 Plume 的投资:利用区块链创造现实价值的典范项目
  • 【C++】STL库面试常问点
  • Java基礎2小時速成(下篇) - 掌握核心技术「卷」
  • 【嵌入式学习】嘉立创画pcb门电路
  • Android 动态代理详解
  • 麒麟操作系统作为服务器,并且需要在浏览器上调试 MATLAB
  • LangChain组件Tools/Toolkits详解(1)——Tools接口与创建工具概述
  • Certd自动化申请和部署SSL证书并配置https
  • Go 语言常量
  • Nvidia 官方CUDA课程学习笔记
  • 【ESP32】虚拟机Ubuntu20.04下ESP32环境搭建
  • 梅花画与咏梅诗
  • 全球首台环形CT直线加速器在沪正式开机,系我国自主研发
  • “十四五”以来少数民族发展资金累计下达边疆省区252亿元
  • 人民日报任仲平:为什么中国意味着确定性、未来性、机遇性
  • 解放军仪仗司礼大队参加越南纪念南方解放50周年庆典活动
  • “80后”李岩已任安徽安庆市领导