整理客户端代码

This commit is contained in:
John Smith 2021-12-15 21:12:09 +08:00
parent 34d1a8a44a
commit 2fdfc88fb2

View File

@ -234,6 +234,8 @@ class BLiveClient:
async def init_room(self): async def init_room(self):
""" """
初始化连接房间需要的字段
:return: True代表没有降级如果需要降级后还可用重载这个函数返回True :return: True代表没有降级如果需要降级后还可用重载这个函数返回True
""" """
res = True res = True
@ -304,18 +306,28 @@ class BLiveClient:
return True return True
@staticmethod @staticmethod
def _make_packet(data, operation): def _make_packet(data: dict, operation: int) -> bytes:
"""
创建一个要发送给服务器的包
:param data: 包体JSON数据
:param operation: 操作码见Operation
:return: 整个包的数据
"""
body = json.dumps(data).encode('utf-8') body = json.dumps(data).encode('utf-8')
header = HEADER_STRUCT.pack( header = HEADER_STRUCT.pack(
HEADER_STRUCT.size + len(body), HEADER_STRUCT.size + len(body), # pack_len
HEADER_STRUCT.size, HEADER_STRUCT.size, # raw_header_size
1, 1, # ver
operation, operation, # operation
1 1 # seq_id
) )
return header + body return header + body
async def _send_auth(self): async def _send_auth(self):
"""
发送认证包
"""
auth_params = { auth_params = {
'uid': self._uid, 'uid': self._uid,
'roomid': self._room_id, 'roomid': self._room_id,
@ -329,10 +341,16 @@ class BLiveClient:
await self._websocket.send_bytes(self._make_packet(auth_params, Operation.AUTH)) await self._websocket.send_bytes(self._make_packet(auth_params, Operation.AUTH))
async def _network_coroutine(self): async def _network_coroutine(self):
"""
网络协程负责连接服务器接收消息解包
"""
# 如果之前未初始化则初始化 # 如果之前未初始化则初始化
if self._host_server_token is None: if self._host_server_token is None:
if not await self.init_room(): try:
raise InitError('初始化失败') if not await self.init_room():
raise InitError('初始化失败')
except asyncio.CancelledError:
return
retry_count = 0 retry_count = 0
while True: while True:
@ -340,47 +358,34 @@ class BLiveClient:
# 连接 # 连接
host_server = self._host_server_list[retry_count % len(self._host_server_list)] host_server = self._host_server_list[retry_count % len(self._host_server_list)]
async with self._session.ws_connect( async with self._session.ws_connect(
f'wss://{host_server["host"]}:{host_server["wss_port"]}/sub', f"wss://{host_server['host']}:{host_server['wss_port']}/sub",
receive_timeout=self._heartbeat_interval + 5, receive_timeout=self._heartbeat_interval + 5,
ssl=self._ssl ssl=self._ssl
) as websocket: ) as websocket:
self._websocket = websocket self._websocket = websocket
await self._send_auth() await self._on_ws_connect()
self._heartbeat_timer_handle = self._loop.call_later(
self._heartbeat_interval, self._on_send_heartbeat
)
# 处理消息 # 处理消息
message: aiohttp.WSMessage message: aiohttp.WSMessage
async for message in websocket: async for message in websocket:
retry_count = 0 retry_count = 0
if message.type != aiohttp.WSMsgType.BINARY: await self._on_ws_message(message)
logger.warning('room %d 未知的websocket消息type=%s %s', self.room_id,
message.type, message.data)
continue
try:
await self._handle_ws_message(message.data)
except asyncio.CancelledError:
raise
except Exception: # noqa
logger.exception('room %d 处理websocket消息时发生错误', self.room_id)
except asyncio.CancelledError:
break
except (aiohttp.ClientConnectionError, asyncio.TimeoutError): except (aiohttp.ClientConnectionError, asyncio.TimeoutError):
# 重连 # 重连
pass pass
except asyncio.CancelledError:
# 正常停止
break
except ssl_.SSLError: except ssl_.SSLError:
logger.exception('SSL错误') logger.exception('SSL错误')
# 证书错误时无法重连 # 证书错误时无法重连
break break
finally: finally:
self._websocket = None self._websocket = None
if self._heartbeat_timer_handle is not None: await self._on_ws_close()
self._heartbeat_timer_handle.cancel()
self._heartbeat_timer_handle = None
# 准备重连
retry_count += 1 retry_count += 1
logger.warning('room %d 掉线重连中%d', self.room_id, retry_count) logger.warning('room %d 掉线重连中%d', self.room_id, retry_count)
try: try:
@ -388,12 +393,53 @@ class BLiveClient:
except asyncio.CancelledError: except asyncio.CancelledError:
break break
async def _on_ws_connect(self):
"""
websocket连接成功
"""
await self._send_auth()
self._heartbeat_timer_handle = self._loop.call_later(self._heartbeat_interval, self._on_send_heartbeat)
def _on_send_heartbeat(self): def _on_send_heartbeat(self):
"""
定时发送心跳包的回调
"""
coro = self._websocket.send_bytes(self._make_packet({}, Operation.HEARTBEAT)) coro = self._websocket.send_bytes(self._make_packet({}, Operation.HEARTBEAT))
asyncio.ensure_future(coro, loop=self._loop) asyncio.ensure_future(coro, loop=self._loop)
self._heartbeat_timer_handle = self._loop.call_later(self._heartbeat_interval, self._on_send_heartbeat) self._heartbeat_timer_handle = self._loop.call_later(self._heartbeat_interval, self._on_send_heartbeat)
async def _handle_ws_message(self, data): async def _on_ws_message(self, message: aiohttp.WSMessage):
"""
收到websocket消息
:param message: websocket消息
"""
if message.type != aiohttp.WSMsgType.BINARY:
logger.warning('room %d 未知的websocket消息type=%s %s', self.room_id,
message.type, message.data)
return
try:
await self._parse_ws_message(message.data)
except asyncio.CancelledError:
raise
except Exception: # noqa
logger.exception('room %d 处理websocket消息时发生错误', self.room_id)
async def _on_ws_close(self):
"""
websocket连接断开
"""
if self._heartbeat_timer_handle is not None:
self._heartbeat_timer_handle.cancel()
self._heartbeat_timer_handle = None
async def _parse_ws_message(self, data: bytes):
"""
解析websocket消息
:param data: websocket消息数据
"""
offset = 0 offset = 0
while offset < len(data): while offset < len(data):
try: try:
@ -402,6 +448,7 @@ class BLiveClient:
break break
if header.operation == Operation.HEARTBEAT_REPLY: if header.operation == Operation.HEARTBEAT_REPLY:
# 心跳包,自己造个消息当成业务消息处理
popularity = int.from_bytes( popularity = int.from_bytes(
data[offset + HEADER_STRUCT.size: offset + HEADER_STRUCT.size + 4], data[offset + HEADER_STRUCT.size: offset + HEADER_STRUCT.size + 4],
'big' 'big'
@ -412,35 +459,46 @@ class BLiveClient:
'popularity': popularity 'popularity': popularity
} }
} }
await self._handle_command(body) await self._parse_command(body)
elif header.operation == Operation.SEND_MSG_REPLY: elif header.operation == Operation.SEND_MSG_REPLY:
# 业务消息
body = data[offset + HEADER_STRUCT.size: offset + header.pack_len] body = data[offset + HEADER_STRUCT.size: offset + header.pack_len]
if header.ver == WS_BODY_PROTOCOL_VERSION_DEFLATE: if header.ver == WS_BODY_PROTOCOL_VERSION_DEFLATE:
# 压缩过的先解压,为了避免阻塞网络线程,放在其他线程执行
body = await self._loop.run_in_executor(None, zlib.decompress, body) body = await self._loop.run_in_executor(None, zlib.decompress, body)
await self._handle_ws_message(body) await self._parse_ws_message(body)
else: else:
# 没压缩过的
try: try:
body = json.loads(body.decode('utf-8')) body = json.loads(body.decode('utf-8'))
await self._handle_command(body) await self._parse_command(body)
except Exception: except Exception:
logger.error('body=%s', body) logger.error('body=%s', body)
raise raise
elif header.operation == Operation.AUTH_REPLY: elif header.operation == Operation.AUTH_REPLY:
# 认证响应
await self._websocket.send_bytes(self._make_packet({}, Operation.HEARTBEAT)) await self._websocket.send_bytes(self._make_packet({}, Operation.HEARTBEAT))
else: else:
# 未知消息
body = data[offset + HEADER_STRUCT.size: offset + header.pack_len] body = data[offset + HEADER_STRUCT.size: offset + header.pack_len]
logger.warning('room %d 未知包类型operation=%d %s%s', self.room_id, logger.warning('room %d 未知包类型operation=%d %s%s', self.room_id,
header.operation, header, body) header.operation, header, body)
offset += header.pack_len offset += header.pack_len
async def _handle_command(self, command): async def _parse_command(self, command: Union[list, dict]):
"""
解析业务消息
:param command: 业务消息
"""
# 这里可能会多个消息一起发
if isinstance(command, list): if isinstance(command, list):
for one_command in command: for one_command in command:
await self._handle_command(one_command) await self._parse_command(one_command)
return return
for handler in self._handlers: for handler in self._handlers: