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 弹幕协议分析

在这里插入图片描述
#协议内容如下
相关协议链接地址 https://blog.csdn.net/xfgryujk/article/details/80306776

协议的头部是 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

相关链接:
https://github.com/lovelyyoshino/Bilibili-Live-API/blob/master/API.WebSocket.md

https://blog.csdn.net/xfgryujk/article/details/80306776

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gitxuzan_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值