进制之间转换
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个字节,加上发送的长度,
头部入参说明
- 第一个4个字节 包总长度 00ff = 255
- 第二个2个字节, 头部长度 换算10进制 0010 = 16
- 第三个2个字节, 协议版本 0001
- 第四个4个字节,0000 0007 = 7 ,7代表进入弹幕
- 第五个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