-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWebSocket.html
More file actions
214 lines (204 loc) · 8.88 KB
/
WebSocket.html
File metadata and controls
214 lines (204 loc) · 8.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Web sockets test</title>
<script src="https://github.com/nodeca/pako/blob/master/dist/pako.js"></script>
</head>
<body>
<p>输入WebSocket地址</p>
<input type="text" class="address" value="wss://broadcastlv.chat.bilibili.com/sub" />
<textarea class="content"></textarea>
<button class="but" onclick="OnConnect();">连接</button>
</body>
<script>
var q = document.querySelector.bind(document);
var c = console.log.bind(console)
//组合认证数据包
function getCertification(json) {
var bytes = str2bytes(json); //字符串转bytes
var n1 = new ArrayBuffer(bytes.length + 16)
var i = new DataView(n1);
i.setUint32(0, bytes.length + 16), //封包总大小
i.setUint16(4, 16), //头部长度
i.setUint16(6, 1), //协议版本
i.setUint32(8, 7), //操作码 7表示认证并加入房间
i.setUint32(12, 1); //就1
for (var r = 0; r < bytes.length; r++) {
i.setUint8(16 + r, bytes[r]); //把要认证的数据添加进去
}
return i; //返回
}
//字符串转bytes //这个方法是从网上找的QAQ
function str2bytes(str) {
const bytes = []
let c
const len = str.length
for (let i = 0; i < len; i++) {
c = str.charCodeAt(i)
if (c >= 0x010000 && c <= 0x10FFFF) {
bytes.push(((c >> 18) & 0x07) | 0xF0)
bytes.push(((c >> 12) & 0x3F) | 0x80)
bytes.push(((c >> 6) & 0x3F) | 0x80)
bytes.push((c & 0x3F) | 0x80)
} else if (c >= 0x000800 && c <= 0x00FFFF) {
bytes.push(((c >> 12) & 0x0F) | 0xE0)
bytes.push(((c >> 6) & 0x3F) | 0x80)
bytes.push((c & 0x3F) | 0x80)
} else if (c >= 0x000080 && c <= 0x0007FF) {
bytes.push(((c >> 6) & 0x1F) | 0xC0)
bytes.push((c & 0x3F) | 0x80)
} else {
bytes.push(c & 0xFF)
}
}
return bytes
}
function decode(blob) {
return new Promise(function (resolve, reject) {
let reader = new FileReader();
reader.onload = function (e) {
let buffer = new Uint8Array(e.target.result)
let result = {}
result.packetLen = readInt(buffer, 0, 4)
result.headerLen = readInt(buffer, 4, 2)
result.ver = readInt(buffer, 6, 2)
result.op = readInt(buffer, 8, 4)
result.seq = readInt(buffer, 12, 4)
if (result.op === 5) {
result.body = []
let offset = 0;
while (offset < buffer.length) {
let packetLen = readInt(buffer, offset + 0, 4)
let headerLen = 16// readInt(buffer,offset + 4,4)
let data = buffer.slice(offset + headerLen, offset + packetLen);
/**
* 仅有两处更改
* 1. 引入pako做message解压处理,具体代码链接如下
* https://github.com/nodeca/pako/blob/master/dist/pako.js
* 2. message文本中截断掉不需要的部分,避免JSON.parse时出现问题
*/
/** let body = textDecoder.decode(pako.inflate(data));
if (body) {
// 同一条 message 中可能存在多条信息,用正则筛出来
const group = body.split(/[\x00-\x1f]+/);
group.forEach(item => {
try {
result.body.push(JSON.parse(item));
}
catch(e) {
// 忽略非 JSON 字符串,通常情况下为分隔符
}
});
}**/
let body = '';
try {
// pako可能无法解压
body = textDecoder.decode(pako.inflate(data));
}
catch (e) {
body = textDecoder.decode(data)
}
if (body) {
// 同一条 message 中可能存在多条信息,用正则筛出来
const group = body.split(/[\x00-\x1f]+/);
group.forEach(item => {
try {
const parsedItem = JSON.parse(item);
if (typeof parsedItem === 'object') {
result.body.push(parsedItem);
} else {
// 这里item可能会解析出number
// 此时可以尝试重新用pako解压data(携带转换参数)
// const newBody = textDecoder.decode(pako.inflate(data, {to: 'String'}))
// 重复上面的逻辑,筛选可能存在的多条信息
// 初步验证,这里可以解析到INTERACT_WORD、DANMU_MSG、ONLINE_RANK_COUNT
// SEND_GIFT、SUPER_CHAT_MESSAGE
}
}
catch (e) {
// 忽略非 JSON 字符串,通常情况下为分隔符
}
});
}
offset += packetLen;
}
} else if (result.op === 3) {
result.body = {
count: readInt(buffer, 16, 4)
};
}
resolve(result)
}
reader.readAsArrayBuffer(blob);
});
}
function readInt(buffer, start, len) {
let result = 0
for (let i = len - 1; i >= 0; i--) {
result += Math.pow(256, len - i - 1) * buffer[start + i]
}
return result
}
function OnConnect() {
var address = q(".address");
var url = address.value;
var ws = new WebSocket(url);
ws.onopen = function () {
var room_id = 21144080;//上面获取到的room_id
var json = {
"uid": 0,
"roomid": room_id, //上面获取到的room_id
"protover": 1,
"platform": "web",
"clientver": "1.6.3"
}
console.log("WebSocket 已连接上");
ws.send(getCertification(JSON.stringify(json)).buffer);
//心跳包的定时器
timer = setInterval(function () { //定时器 注意声明timer变量
var n1 = new ArrayBuffer(16)
var i = new DataView(n1);
i.setUint32(0, 0), //封包总大小
i.setUint16(4, 16), //头部长度
i.setUint16(6, 1), //协议版本
i.setUint32(8, 2), // 操作码 2 心跳包
i.setUint32(12, 1); //就1
ws.send(i.buffer); //发送
}, 30000) //30秒
};
//WebSocket接收数据回调
ws.onmessage = function (evt) {
const packet = decode(evt.data);
switch (packet.op) {
case 8:
console.log('加入房间');
break;
case 3:
const count = packet.body.count
console.log(`人气:${count}`);
break;
case 5:
packet.body.forEach((body) => {
switch (body.cmd) {
case 'DANMU_MSG':
console.log(`${body.info[2][1]}: ${body.info[1]}`);
break;
case 'SEND_GIFT':
console.log(`${body.data.uname} ${body.data.action} ${body.data.num} 个 ${body.data.giftName}`);
break;
case 'WELCOME':
console.log(`欢迎 ${body.data.uname}`);
break;
// 此处省略很多其他通知类型
default:
console.log(body);
}
})
break;
default:
console.log(packet);
}
};
}
</script>
</html>