websocket消息优化

This commit is contained in:
John Smith 2020-02-05 17:28:10 +08:00
parent 49cdc56cff
commit 1c5df01fbc
2 changed files with 118 additions and 60 deletions

View File

@ -4,6 +4,7 @@ import asyncio
import enum import enum
import json import json
import logging import logging
import random
import time import time
from typing import * from typing import *
@ -58,7 +59,7 @@ class Room(blivedm.BLiveClient):
def __parse_gift(self, command): def __parse_gift(self, command):
data = command['data'] data = command['data']
return self._on_receive_gift(blivedm.GiftMessage( return self._on_receive_gift(blivedm.GiftMessage(
data['giftName'], data['num'], data['uname'], data['face'], None, None, None, data['uname'], data['face'], None,
data['uid'], data['timestamp'], None, None, data['uid'], data['timestamp'], None, None,
None, None, None, data['coin_type'], data['total_coin'] None, None, None, data['coin_type'], data['total_coin']
)) ))
@ -121,19 +122,31 @@ class Room(blivedm.BLiveClient):
author_type = 1 # 舰队 author_type = 1 # 舰队
else: else:
author_type = 0 author_type = 0
self.send_message(Command.ADD_TEXT, { # 为了节省带宽用list而不是dict
'avatarUrl': await models.avatar.get_avatar_url(danmaku.uid), self.send_message(Command.ADD_TEXT, [
'timestamp': danmaku.timestamp, # 0: avatarUrl
'authorName': danmaku.uname, await models.avatar.get_avatar_url(danmaku.uid),
'authorType': author_type, # 1: timestamp
'content': danmaku.msg, danmaku.timestamp,
'privilegeType': danmaku.privilege_type, # 2: authorName
'isGiftDanmaku': bool(danmaku.msg_type), danmaku.uname,
'authorLevel': danmaku.user_level, # 3: authorType
'isNewbie': danmaku.urank < 10000, author_type,
'isMobileVerified': bool(danmaku.mobile_verify), # 4: content
'medalLevel': 0 if danmaku.room_id != self.room_id else danmaku.medal_level danmaku.msg,
}) # 5: privilegeType
danmaku.privilege_type,
# 6: isGiftDanmaku
1 if danmaku.msg_type else 0,
# 7: authorLevel
danmaku.user_level,
# 8: isNewbie
1 if danmaku.urank < 10000 else 0,
# 9: isMobileVerified
1 if danmaku.mobile_verify else 0,
# 10: medalLevel
0 if danmaku.room_id != self.room_id else danmaku.medal_level
])
async def _on_receive_gift(self, gift: blivedm.GiftMessage): async def _on_receive_gift(self, gift: blivedm.GiftMessage):
avatar_url = models.avatar.process_avatar_url(gift.face) avatar_url = models.avatar.process_avatar_url(gift.face)
@ -144,8 +157,6 @@ class Room(blivedm.BLiveClient):
'avatarUrl': avatar_url, 'avatarUrl': avatar_url,
'timestamp': gift.timestamp, 'timestamp': gift.timestamp,
'authorName': gift.uname, 'authorName': gift.uname,
'giftName': gift.gift_name,
'giftNum': gift.num,
'totalCoin': gift.total_coin 'totalCoin': gift.total_coin
}) })
@ -154,7 +165,7 @@ class Room(blivedm.BLiveClient):
async def __on_buy_guard(self, message: blivedm.GuardBuyMessage): async def __on_buy_guard(self, message: blivedm.GuardBuyMessage):
self.send_message(Command.ADD_MEMBER, { self.send_message(Command.ADD_MEMBER, {
'avatarUrl': await models.avatar.get_avatar_url(message.uid), 'avatarUrl': await models.avatar.get_avatar_url(message.uid),
'timestamp': message.start_time, 'timestamp': message.start_time,
'authorName': message.username 'authorName': message.username
}) })
@ -173,7 +184,7 @@ class Room(blivedm.BLiveClient):
async def _on_super_chat_delete(self, message: blivedm.SuperChatDeleteMessage): async def _on_super_chat_delete(self, message: blivedm.SuperChatDeleteMessage):
self.send_message(Command.ADD_SUPER_CHAT, { self.send_message(Command.ADD_SUPER_CHAT, {
'ids': message.ids 'ids': message.ids
}) })
@ -191,7 +202,7 @@ class RoomManager:
logger.info('%d clients in room %s', len(room.clients), room_id) logger.info('%d clients in room %s', len(room.clients), room_id)
if client.application.settings['debug']: if client.application.settings['debug']:
client.send_test_message() await client.send_test_message()
def del_client(self, room_id, client: 'ChatHandler'): def del_client(self, room_id, client: 'ChatHandler'):
if room_id not in self._rooms: if room_id not in self._rooms:
@ -230,29 +241,49 @@ class RoomManager:
class ChatHandler(tornado.websocket.WebSocketHandler): class ChatHandler(tornado.websocket.WebSocketHandler):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._close_on_timeout_future = None
self.room_id = None self.room_id = None
def open(self): def open(self):
logger.info('Websocket connected %s', self.request.remote_ip) logger.info('Websocket connected %s', self.request.remote_ip)
self._close_on_timeout_future = asyncio.ensure_future(self._close_on_timeout())
async def _close_on_timeout(self):
try:
# 超过一定时间还没加入房间则断开
await asyncio.sleep(10)
logger.warning('Client %s joining room timed out', self.request.remote_ip)
self.close()
except (asyncio.CancelledError, tornado.websocket.WebSocketClosedError):
pass
def on_message(self, message): def on_message(self, message):
body = json.loads(message) try:
cmd = body['cmd'] body = json.loads(message)
if cmd == Command.HEARTBEAT: cmd = body['cmd']
pass if cmd == Command.HEARTBEAT:
elif cmd == Command.JOIN_ROOM:
if self.room_id is not None:
return return
self.room_id = int(body['data']['roomId']) elif cmd == Command.JOIN_ROOM:
logger.info('Client %s is joining room %d', self.request.remote_ip, self.room_id) if self.has_joined_room:
asyncio.ensure_future(room_manager.add_client(self.room_id, self)) return
else: self.room_id = int(body['data']['roomId'])
logger.warning('Unknown cmd: %s body: %s', cmd, body) logger.info('Client %s is joining room %d', self.request.remote_ip, self.room_id)
asyncio.ensure_future(room_manager.add_client(self.room_id, self))
self._close_on_timeout_future.cancel()
self._close_on_timeout_future = None
else:
logger.warning('Unknown cmd, client: %s, cmd: %d, body: %s', self.request.remote_ip, cmd, body)
except:
logger.exception('on_message error, client: %s, message: %s', self.request.remote_ip, message)
def on_close(self): def on_close(self):
logger.info('Websocket disconnected %s room: %s', self.request.remote_ip, str(self.room_id)) logger.info('Websocket disconnected %s room: %s', self.request.remote_ip, str(self.room_id))
if self.room_id is not None: if self.has_joined_room:
room_manager.del_client(self.room_id, self) room_manager.del_client(self.room_id, self)
if self._close_on_timeout_future is not None:
self._close_on_timeout_future.cancel()
self._close_on_timeout_future = None
# 跨域测试用 # 跨域测试用
def check_origin(self, origin): def check_origin(self, origin):
@ -261,52 +292,67 @@ class ChatHandler(tornado.websocket.WebSocketHandler):
return super().check_origin(origin) return super().check_origin(origin)
# 测试用 # 测试用
def send_test_message(self): async def send_test_message(self):
base_data = { base_data = {
'avatarUrl': '//i0.hdslb.com/bfs/face/29b6be8aa611e70a3d3ac219cdaf5e72b604f2de.jpg@48w_48h', 'avatarUrl': await models.avatar.get_avatar_url(300474),
'timestamp': time.time(), 'timestamp': time.time(),
'authorName': 'xfgryujk', 'authorName': 'xfgryujk',
} }
text_data = { text_data = [
**base_data, # 0: avatarUrl
'authorType': 0, base_data['avatarUrl'],
'content': '我能吞下玻璃而不伤身体', # 1: timestamp
'privilegeType': 0, base_data['timestamp'],
'isGiftDanmaku': False, # 2: authorName
'authorLevel': 20, base_data['authorName'],
'isNewbie': False, # 3: authorType
'isMobileVerified': True 0,
} # 4: content
member_data = base_data '我能吞下玻璃而不伤身体',
# 5: privilegeType
0,
# 6: isGiftDanmaku
0,
# 7: authorLevel
20,
# 8: isNewbie
0,
# 9: isMobileVerified
1,
# 10: medalLevel
0
]
member_data = base_data.copy()
gift_data = { gift_data = {
**base_data, **base_data,
'giftName': '摩天大楼',
'giftNum': 1,
'totalCoin': 450000 'totalCoin': 450000
} }
sc_data = { sc_data = {
**base_data, **base_data,
'price': 30, 'price': 30,
'content': 'The quick brown fox jumps over the lazy dog', 'content': 'The quick brown fox jumps over the lazy dog',
'id': 1 'id': random.randint(1, 65535)
} }
self.send_message(Command.ADD_TEXT, text_data) self.send_message(Command.ADD_TEXT, text_data)
text_data['authorName'] = '主播' text_data[2] = '主播'
text_data['authorType'] = 3 text_data[3] = 3
text_data['content'] = "I can eat glass, it doesn't hurt me." text_data[4] = "I can eat glass, it doesn't hurt me."
self.send_message(Command.ADD_TEXT, text_data) self.send_message(Command.ADD_TEXT, text_data)
self.send_message(Command.ADD_MEMBER, member_data) self.send_message(Command.ADD_MEMBER, member_data)
self.send_message(Command.ADD_SUPER_CHAT, sc_data) self.send_message(Command.ADD_SUPER_CHAT, sc_data)
sc_data['price'] = 100 sc_data['price'] = 100
sc_data['content'] = '敏捷的棕色狐狸跳过了懒狗' sc_data['content'] = '敏捷的棕色狐狸跳过了懒狗'
sc_data['id'] = 2 sc_data['id'] = random.randint(1, 65535)
self.send_message(Command.ADD_SUPER_CHAT, sc_data) self.send_message(Command.ADD_SUPER_CHAT, sc_data)
# self.send_message(Command.DEL_SUPER_CHAT, {'ids': [1, 2]}) # self.send_message(Command.DEL_SUPER_CHAT, {'ids': [sc_data['id']]})
self.send_message(Command.ADD_GIFT, gift_data) self.send_message(Command.ADD_GIFT, gift_data)
gift_data['giftName'] = '小电视飞船'
gift_data['totalCoin'] = 1245000 gift_data['totalCoin'] = 1245000
self.send_message(Command.ADD_GIFT, gift_data) self.send_message(Command.ADD_GIFT, gift_data)
@property
def has_joined_room(self):
return self.room_id is not None
def send_message(self, cmd, data): def send_message(self, cmd, data):
body = json.dumps({'cmd': cmd, 'data': data}) body = json.dumps({'cmd': cmd, 'data': data})
try: try:

View File

@ -112,9 +112,21 @@ export default {
onWsMessage(event) { onWsMessage(event) {
let {cmd, data} = JSON.parse(event.data) let {cmd, data} = JSON.parse(event.data)
let message = null let message = null
let time = data.timestamp ? new Date(data.timestamp * 1000) : new Date()
switch (cmd) { switch (cmd) {
case COMMAND_ADD_TEXT: case COMMAND_ADD_TEXT:
data = {
avatarUrl: data[0],
timestamp: data[1],
authorName: data[2],
authorType: data[3],
content: data[4],
privilegeType: data[5],
isGiftDanmaku: !!data[6],
authorLevel: data[7],
isNewbie: !!data[8],
isMobileVerified: !!data[9],
medalLevel: data[10]
}
if (!this.config.showDanmaku || !this.filterTextMessage(data) || this.mergeSimilarText(data.content)) { if (!this.config.showDanmaku || !this.filterTextMessage(data) || this.mergeSimilarText(data.content)) {
break break
} }
@ -122,7 +134,7 @@ export default {
id: `text_${this.nextId++}`, id: `text_${this.nextId++}`,
type: constants.MESSAGE_TYPE_TEXT, type: constants.MESSAGE_TYPE_TEXT,
avatarUrl: data.avatarUrl, avatarUrl: data.avatarUrl,
time: time, time: new Date(data.timestamp * 1000),
authorName: data.authorName, authorName: data.authorName,
authorType: data.authorType, authorType: data.authorType,
content: data.content, content: data.content,
@ -147,7 +159,7 @@ export default {
avatarUrl: data.avatarUrl, avatarUrl: data.avatarUrl,
authorName: data.authorName, authorName: data.authorName,
price: price, price: price,
time: time, time: new Date(data.timestamp * 1000),
content: '' // SC content: '' // SC
} }
break break
@ -160,7 +172,7 @@ export default {
id: `member_${this.nextId++}`, id: `member_${this.nextId++}`,
type: constants.MESSAGE_TYPE_MEMBER, type: constants.MESSAGE_TYPE_MEMBER,
avatarUrl: data.avatarUrl, avatarUrl: data.avatarUrl,
time: time, time: new Date(data.timestamp * 1000),
authorName: data.authorName, authorName: data.authorName,
title: 'NEW MEMBER!', title: 'NEW MEMBER!',
content: `Welcome ${data.authorName}!` content: `Welcome ${data.authorName}!`
@ -179,7 +191,7 @@ export default {
avatarUrl: data.avatarUrl, avatarUrl: data.avatarUrl,
authorName: data.authorName, authorName: data.authorName,
price: data.price, price: data.price,
time: time, time: new Date(data.timestamp * 1000),
content: data.content.trim() content: data.content.trim()
} }
break break