blive/PROTOCOL.md
2022-01-08 12:50:03 +08:00

161 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# B 站直播弹幕 websocket 协议分析
## 包格式
```txt
____________________________________________________________________________
| | | | | |
| package_size | header_size | protocol_version | operation | sequence_id |
|______________|_____________|__________________|____________|_____________|
| |
| data |
| |
|__________________________________________________________________________|
```
包为 byte 数据,由头部和数据组成,字节序均为大端模式
头部长度为 16
| 偏移量 | 长度 | 含义 |
| ------ | ---- | ------------------------- |
| 0 | 4 | 包总大小 package_size |
| 4 | 2 | 头部大小 header_size |
| 6 | 2 | 协议版本 protocol_version |
| 8 | 4 | 操作码 operation |
| 12 | 4 | 包序列 sequence_id |
操作码含义
| 操作码 | 含义 |
| ------ | --------------------------------------------------- |
| 2 | 心跳 HEARTBEAT (标记该包为心跳包) |
| 3 | 心跳回应 HEARTBEAT_REPLY (有时候带着人气值数据返回) |
| 5 | 消息 NOTIFY (B 站的弹幕或业务消息都属于这个操作码) |
| 7 | 认证 AUTH (标记该包为认证包) |
| 8 | 认证回复 AUTH_REPLY |
协议版本
| 协议版本 | 含义 |
| -------- | ----------------------- |
| 0 | NORMAL 未压缩的正常消息 |
| 1 | HEARTBEAT 心跳 |
| 2 | DEFLATE zlib 压缩包 |
| 3 | BROTLI brotil 压缩包 |
## 连接建立流程
1. 根据直播间 id 得到 websocket 连接地址
B 站直播间 id 分短 id 和真正的 id,如 510,605,1314,7777 等都属于短 id,需要请求
`GET https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByRoom?room_id=房间号`
得到真正的房间号,再次请求
`GET https://api.live.bilibili.com/room/v1/Danmu/getConf?room_id=真正的房间号&platform=pc&player=web`
系统会分配 3 个 websocket 连接地址 (负载均衡考虑) 和 连接 token,按照 `wss://{host}:{wss_port}/sub` (TLS WS 连接) 或 `ws://{host}:{ws_port}/sub` (WS 连接格式) 的格式拼接,即可得到 websocket 连接地址
2. 建立连接后,发送认证包
建立连接后需要立即发送认证包,否则服务器会断开连接
认证包数据载荷为 json 格式的字符串,内容如下,和头部拼接时候需要 使用 utf-8 编码转化为 bytes
```json
{
"uid": 0, // 用户id,0 代表游客
"roomid": 123, //你要收取弹幕的房间号
"protover": 3, //后面通信要使用的协议版本,主要影响处理粘包时的解码选择,选3代表后面粘包使用 brotil 压缩,
"platform": "web", // 平台选择web端
"type": 2, //不明确填2
"clientver": "1.4.3", //客户端版本,填1.4.3
"key": "your token" //填写上一步系统给的token值
}
```
头部操作码选择 7 (AUTH 认证) ,版本为 0 (NORMAL 未压缩的消息), sequence_id 填 1,包大小和头部大小请自行计算填写
3. 定时发送心跳包
B 站服务器设置的心跳间隔约为 70s 左右,所以连接建立后每隔 30s 发送一次心跳维持连接基本稳妥。心跳包内容随意填写无影响,头部操作码选择 2 (HEARTBEAT 心跳), 版本选择 0 (NORMAL 未压缩的消息)
## 收到包的消息处理
服务器推送的包只会有 3 种操作码,分别为 3(心跳回应)5(消息)8(认证回复),所以我们只需要对这三种包进行处理
- ### 心跳回应
心跳回应数据都是未压缩的,所以可以跳过协议判断,直接 `data[16+4:]`截取有效载荷再使用 utf-8 编码转化为字符串即可,16 是头部长度,+4 是因为数据段前面还有 4 位无效数据?(含义不明,不知道为什么有),直接去掉
- ### 认证回复
认证回复为发送认证包后收到的回复,数据包无压缩,直接 `data[16:]`截取有效载荷再使用 utf-8 编码转化为字符串即可
- ### 消息
首先说明,B 站对 消息 这一数据包出于服务器效率考虑会存在粘包的情况(目前的情况是一定会有粘包的存在)所以在编写处理代码时无可避免要处理这一情况并且在认证AUTH包发送阶段选择的 协议版本 的值会在这一阶段产生影响,如果在认证时选择 0 (NORMAL 未压缩的消息) 或者 1 (HEARTBEAT),则默认服务器在粘包使用 zlib 压缩多个包,若选择 3(Brotil 压缩),则会使用 Brotil 压缩多个包。首先说明服务端的粘包逻辑,这有利于编写解包代码
- 服务器正常打包每一个数据包
```txt
----------
| header |
|--------|
| |
| data |
| |
----------
----------
| header |
|--------|
| |
| data |
| |
----------
```
- 服务器发现包太多,或者单个消息包太大,都会使用认证期选择的压缩协议压缩数据包,并再套上一个包头,协议版本字段写明使用的压缩协议
```txt
----------------
| header |
----------------
| ---------- |
| | header | |
| |--------| |
| | | |
| | data | |
| | | |
| ---------- |
| ---------- |
| | header | |
| |--------| |
| | | |
| | data | |
| | | |
| ---------- |
----------------
```
所以判断是否粘贴包的逻辑为
```txt
--------->说明数据载荷为多个包粘贴或单个包太大---->丢弃头部,使用对应压缩协议解压数据段,得到无缝拼接的包数据
/ 然后对数据进行拆包提取数据
/
zlib/brotil压缩
/
收到数据----->判断头部协议版本
\
正常消息
\
-------->说明数据载荷为单个数据---->直接提取数据
```
拆包逻辑的主要思想是:首先读取头部信息,计算数据长度是否大于头部声明的数据包长度。如果数据长度大于头部声明的数据长度,则按声明的数据长度截取数据,取出第一个包,然后相同逻辑判断后面的数据,代码实现请参考源代码 blive/core.py `BWS_MsgPackage`类的`unpack`方法实现