bilibili 弹幕协议分析,golang代码还原

我的文章

进制之间转换

1. 二进制转八进制  %b -> %o
2. 二进制转十进制  %b ->  %d
3. 二进制转十六进制 %b -> %x
4. 八进制转二进制 %o -> %b
5. 八进制转十进制 %o -> %d
6. 八进制转十六进制 %o -> %x
7. 十进制转二进制 %d -> %b
8. 十进制转八进制 %d -> %o
9. 十进制转十六进制 %d -> %x
10. 十六进制转二进制 %x -> %b
11. 十六进制转八进制 %x -> %o
12. 十六进制转十进制 %x -> %d
// 例
fmt.Printf("十进制%d转成八进制%o",num1,num2)
%b    表示为二进制
%c    该值对应的unicode码值
%d    表示为十进制
%o    表示为八进制
%q    该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示
%x    表示为十六进制,使用a-f
%X    表示为十六进制,使用A-F
%U    表示为Unicode格式:U+1234,等价于"U+%04X"
%E    用科学计数法表示
%f    用浮点数表示
hex := fmt.Sprintf("%08x", i) // hex 16进制

打开bilibili 弹幕协议分析

在这里插入图片描述

#协议内容如下
相关协议链接地址 blog.csdn.net/xfgryujk/article/det...

协议的头部是 4 2 2 4 4 总共16个字节,加上发送的长度,
头部入参说明

  1. 第一个4个字节 包总长度 00ff = 255
  2. 第二个2个字节, 头部长度 换算10进制 0010 = 16
  3. 第三个2个字节, 协议版本 0001
  4. 第四个4个字节,0000 0007 = 7 ,7代表进入弹幕
  5. 第五个4个字节 固定常数 0000 0001 = 1

返回参数:
第7到8个字节:返回协议参数,int16类型
0:解析json值
1: 人气值,int32
2: zip压缩
3: brotli压缩

00000000: 0000 00ff 0010 0001 0000 0007 0000 0001  ................
00000001: 7b22 7569 6422 3a33 3335 3230 3037 3434  {"uid":335200744
00000002: 2c22 726f 6f6d 6964 223a 3233 3230 3230  ,"roomid":232020
00000003: 3831 2c22 7072 6f74 6f76 6572 223a 332c  81,"protover":3,
00000004: 2270 6c61 7466 6f72 6d22 3a22 7765 6222  "platform":"web"
00000005: 2c22 7479 7065 223a 322c 226b 6579 223a  ,"type":2,"key":
00000006: 2232 5279 5173 6f35 3432 4c77 4756 7774  "2RyQso542LwGVwt
00000007: 536f 7379 7533 2d64 436c 4e73 5843 3051  Sosyu3-dClNsXC0Q
00000008: 3938 3046 4779 6874 4847 7062 4d39 7654  980FGyhtHGpbM9vT
00000009: 4734 6566 396f 7150 5935 4c39 6772 7470  G4ef9oqPY5L9grtp
0000000a: 4b44 7153 6568 3647 3634 7667 3145 5644  KDqSeh6G64vg1EVD
0000000b: 4378 5972 566f 2d47 4171 7267 6d6e 6370  CxYrVo-GAqrgmncp
0000000c: 4147 5054 3341 6c4b 6d57 7536 326d 666e  AGPT3AlKmWu62mfn
0000000d: 4772 3567 6564 4732 6677 4168 365f 7834  Gr5gedG2fwAh6_x4
0000000e: 5f59 6b49 3867 4a69 6f31 3648 5439 6542  _YkI8gJio16HT9eB
0000000f: 4873 6351 594f 4953 4a7a 773d 3d22 7d    HscQYOISJzw=="}


{"uid":335200743,"roomid":23202081,"protover":3,"platform":"web","type":2,"key":"2RyQso542LwGVwtSosyu3-dClNsXC0Q980FGyhtHGpbM9vTG4ef9oqPY5L9grtpKDqSeh6G64vg1EVDCxYrVo-GAqrgmncpAGPT3AlKmWu62mfnGr5gedG2fwAh6_x4_YkI8gJio16HT9eBHscQYOISJzr=="}

验证头部协议

    handshake := "000000ff001000010000000700000001"
    等价 16字节
    byteArr := []byte{
        0x00, 0x00,0x00,0xff, 0x00,0x10, 0x00,0x01, 0x00,0x00, 0x00,0x07, 0x00,0x00, 0x00,0x01,
    }
    t.Log(len(byteArr))
    buf, err := hex.DecodeString(handshake)
    t.Log("长度",len(buf))
    t.Log(buf)
    t.Log("二进制" + fmt.Sprintf("%08b",buf))
    t.Log("八进制" + fmt.Sprintf("%08o",buf))
    t.Log("十六进制" + fmt.Sprintf("%08x",buf))
    t.Log("十进制" + fmt.Sprintf("%d",buf))
    输出结果:
    qimiao_test.go:30: 长度 16字节
    qimiao_test.go:31: [0 0 0 255 0 16 0 1 0 0 0 7 0 0 0 1]
    qimiao_test.go:32: 二进制[00000000 00000000 00000000 11111111 00000000 00010000 00000000 00000001 00000000 00000000 00000000 00000111 00000000 00000000 00000000 00000001]
    qimiao_test.go:33: 八进制[00000000 00000000 00000000 00000377 00000000 00000020 00000000 00000001 00000000 00000000 00000000 00000007 00000000 00000000 00000000 00000001]
    qimiao_test.go:34: 十六进制000000ff001000010000000700000001
    qimiao_test.go:35: 十进制[0 0 0 255 0 16 0 1 0 0 0 7 0 0 0 1]
    //     "github.com/imroc/biu"
    var b int32
    //入参二进制字符串
    err = biu.ReadBinaryString("[00000000 00000000 00000000 11111111]", &b)
    fmt.Println(b,err) //255 总长度

完整代码:

package main

import (
    "bytes"
    "compress/zlib"
    "encoding/binary"
    "encoding/json"
    "fmt"
    "github.com/gorilla/websocket"
    "github.com/andybalholm/brotli"
    "io"
    "log"
    "net/http"
)

// bilibili 弹幕demo
func main()  {

    ws, _, err := websocket.DefaultDialer.Dial("wss://tx-gz-live-comet-02.chat.bilibili.com/sub", nil)
    if err != nil {
        log.Println(err)
        return
    }
    // 根据抓包获取
    str := `{"uid":335200741,"roomid":23202081,"protover":3,"platform":"web","type":2,"key":"2RyQso542LwGVwtSosyu3-dClNsXC0Q980FGyhtHGpbM9vTG4ef9oqPY5L9grtpKDqSeh6G64vg1EVDCxYrVo-GAqrgmncpAGPT3AlKmWu62mfnGr5gedG2fwAh6_x4_YkI8gJio16HT9eBHscQYOISJzw=="}`
    totalLen := 16 + len([]byte(str))
    strByte := []byte(str)


    dataBuff := bytes.NewBuffer([]byte{})

    // 总长度 4字节
    if err := binary.Write(dataBuff,binary.BigEndian,int32(totalLen));err != nil {
        log.Println(err)
        return
    }
    // 头部长度16 2字节
    if err := binary.Write(dataBuff,binary.BigEndian,int16(16));err != nil {
        log.Println(err)
        return
    }
    // 协议版本号 固定1 2字节
    if err := binary.Write(dataBuff,binary.BigEndian,int16(1));err != nil {
        log.Println(err)
        return
    }
    // 加入弹幕 固定协议7, 4字节
    if err := binary.Write(dataBuff,binary.BigEndian,int32(7));err != nil {
        log.Println(err)
        return
    }

    // 常量 1固定 , 4字节
    if err := binary.Write(dataBuff,binary.BigEndian,int32(1));err != nil {
        log.Println(err)
        return
    }


    err = ws.WriteMessage(websocket.BinaryMessage,append(dataBuff.Bytes(),strByte...))
    if err != nil {
        log.Println(err)
        return
    }
    for {
        // 等待信息返回
        _, message, err := ws.ReadMessage()
        if err != nil {
            log.Println(err,111)
            return
        }

        log.Println(message[0:16],fmt.Sprintf("%08x",message[0:16]))
        //0 1 2 3 4 5 6 7 8
        headByte := bytes.NewBuffer(message[0:4])
        var headint32 int32
        err = binary.Read(headByte,binary.BigEndian,&headint32)
        if err != nil {
            log.Println(err,444)
            return
        }
        log.Println("长度",headint32,headint32 - 16)
        // 对第八位 解码规则判断 0 为正常可见的字符串 2 为需要zlib解码的关键信息
        buf := message[6:8]
        bytesBuffer := bytes.NewBuffer(buf)
        var ageen int16
        err = binary.Read(bytesBuffer,binary.BigEndian,&ageen)
        if err != nil {
            log.Println(err,222)
            return
        }
        switch ageen {
        case 2:
            b := bytes.NewReader(message[16:])
            r, _ := zlib.NewReader(b)
            bs, _ := io.ReadAll(r)
            log.Printf("zip压缩: %s", string(bs))
        case 0:
            b := message[16:]
            log.Println("json弹幕", string(b))
        case 1:
            b := message[16:]
            var renqiInt int32
            bytesBuffers := bytes.NewBuffer(b)
            err = binary.Read(bytesBuffers,binary.BigEndian,&renqiInt)
            if err != nil {
                log.Println(err,222)
                return
            }
            log.Println("人气:",renqiInt)
        case 3:
            b := bytes.NewReader(message[16:])
            r := brotli.NewReader(b)
            bytess,_ := io.ReadAll(r)
            log.Println("brotli压缩", string(bytess),"解压前长度:" ,len(message[16:]) ,"解压长度:",len(bytess))
        default:
            log.Println("其他:",ageen)
        }

    }


}

type BiRes struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Ttl     int    `json:"ttl"`
    Data    struct {
        Group            string  `json:"group"`
        BusinessId       int     `json:"business_id"`
        RefreshRowFactor float64 `json:"refresh_row_factor"`
        RefreshRate      int     `json:"refresh_rate"`
        MaxDelay         int     `json:"max_delay"`
        Token            string  `json:"token"`
        HostList         []struct {
            Host    string `json:"host"`
            Port    int    `json:"port"`
            WssPort int    `json:"wss_port"`
            WsPort  int    `json:"ws_port"`
        } `json:"host_list"`
    } `json:"data"`
}

type BiliReq struct {
    Uid      int    `json:"uid"`
    Roomid   int    `json:"roomid"`
    Protover int    `json:"protover"`
    Platform string `json:"platform"`
    Type     int    `json:"type"`
    Key      string `json:"key"`
}


func GetInfoHttpUrl() *BiRes {
    var bili = new(BiRes)
    res, err := http.Get("https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo?id=23202081&type=0")
    if err != nil {
        log.Println(err)
        return nil
    }

    defer res.Body.Close()
    b, err := io.ReadAll(res.Body)
    err = json.Unmarshal(b, &bili)
    if err != nil {
        return nil
    }
    return bili
}

可以借鉴bilibili的数据压缩,通过brotli,解压前和解压后的数据大小还是差别蛮大的

其他

int 和 []byte 互转

//isSymbol表示有无符号
func BytesToInt(b []byte, isSymbol bool)  (int, error){
    if isSymbol {
        return bytesToIntS(b)
    }
    return bytesToIntU(b)
}


//字节数(大端)组转成int(无符号的)
func bytesToIntU(b []byte) (int, error) {
    if len(b) == 3 {
        b = append([]byte{0},b...)
    }
    bytesBuffer := bytes.NewBuffer(b)
    switch len(b) {
    case 1:
        var tmp uint8
        err := binary.Read(bytesBuffer, binary.BigEndian, &tmp)
        return int(tmp), err
    case 2:
        var tmp uint16
        err := binary.Read(bytesBuffer, binary.BigEndian, &tmp)
        return int(tmp), err
    case 4:
        var tmp uint32
        err := binary.Read(bytesBuffer, binary.BigEndian, &tmp)
        return int(tmp), err
    default:
        return 0,fmt.Errorf("%s", "BytesToInt bytes lenth is invaild!")
    }
}



//字节数(大端)组转成int(有符号)
func bytesToIntS(b []byte) (int, error) {
    if len(b) == 3 {
        b = append([]byte{0},b...)
    }
    bytesBuffer := bytes.NewBuffer(b)
    switch len(b) {
    case 1:
        var tmp int8
        err := binary.Read(bytesBuffer, binary.BigEndian, &tmp)
        return int(tmp), err
    case 2:
        var tmp int16
        err := binary.Read(bytesBuffer, binary.BigEndian, &tmp)
        return int(tmp), err
    case 4:
        var tmp int32
        err := binary.Read(bytesBuffer, binary.BigEndian, &tmp)
        return int(tmp), err
    default:
        return 0,fmt.Errorf("%s", "BytesToInt bytes lenth is invaild!")
    }
}


//整形转换成字节
func IntToBytes(n int,b byte) ([]byte,error) {
    switch b {
    case 1:
        tmp := int8(n)
        bytesBuffer := bytes.NewBuffer([]byte{})
        binary.Write(bytesBuffer, binary.BigEndian, &tmp)
        return bytesBuffer.Bytes(),nil
    case 2:
        tmp := int16(n)
        bytesBuffer := bytes.NewBuffer([]byte{})
        binary.Write(bytesBuffer, binary.BigEndian, &tmp)
        return bytesBuffer.Bytes(),nil
    case 3,4:
        tmp := int32(n)
        bytesBuffer := bytes.NewBuffer([]byte{})
        binary.Write(bytesBuffer, binary.BigEndian, &tmp)
        return bytesBuffer.Bytes(),nil
    }
    return nil,fmt.Errorf("IntToBytes b param is invaild")
}

hex string 和 []byte 互转

[]byte
import "hex"
// 省略部分代码....

hexStr := "fee9ecaadafeee72d2eb66a0bd344cdd"
data, err := hex.DecodeString(hexStr)
if err != nil {
// handle error
}

转 hex string
import (
"fmt"
"crypto/md5"
)
// 省略部分代码
data := "test string"
// md5.Sum() return a byte array
h := md5.Sum([]byte(data))

// with "%x" format byte array into hex string
hexStr := fmt.Sprintf("%x", h)

字节大小端介绍

大端模式:高位字节排放在内存的低地址端,低位字节排放在内存的高地址端;

小端模式:低位字节排放在内存的低地址端,高位字节排放在内存的高地址端;
如下 弹幕协议前4个字节如图

    十进制: 255
    大端16进制: 000000ff
    小端16进制: ff000000

    var value uint32 = 255
    by := make([]byte,4)
    binary.LittleEndian.PutUint32(by,value)
    sixStr := hex.EncodeToString(by)
    resSmall := 255 // 小端
    log.Println(resSmall,sixStr,*(*byte)(unsafe.Pointer(&resSmall)) == 0xff)
    // 输出结果
    2022/05/09 10:29:00 255 ff000000 true

相关链接:
github.com/lovelyyoshino/Bilibili-...

blog.csdn.net/xfgryujk/article/det...

大小端https://segmentfault.com/a/1190000039738719?utm_source=sf-similar-article#comment-area

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 1年前 自动加精
gitxuzan
讨论数量: 1

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!