多个消息处理器并发处理、继续整理客户端代码

This commit is contained in:
John Smith 2021-12-15 23:44:44 +08:00
parent 2fdfc88fb2
commit 89b540dddd

View File

@ -156,6 +156,8 @@ class BLiveClient:
def add_handler(self, handler: 'handlers.HandlerInterface'): def add_handler(self, handler: 'handlers.HandlerInterface'):
""" """
添加消息处理器 添加消息处理器
注意多个处理器是并发处理的不要依赖处理的顺序
消息处理器和接收消息运行在同一协程如果处理消息耗时太长会阻塞接收消息这种情况建议将消息推到队列让另一个协程处理
:param handler: 消息处理器 :param handler: 消息处理器
""" """
@ -177,27 +179,17 @@ class BLiveClient:
""" """
启动本客户端 启动本客户端
""" """
if self._network_future is not None: if self.is_running:
logger.warning('room %s 已经在运行中不能再次start', self.room_id) logger.warning('room %s 已经在运行中不能再次start', self.room_id)
return return
self._network_future = asyncio.ensure_future(self._network_coroutine(), loop=self._loop) self._network_future = asyncio.ensure_future(self._network_coroutine_wrapper(), loop=self._loop)
self._network_future.add_done_callback(self.__on_network_coroutine_done)
def __on_network_coroutine_done(self, future):
self._network_future = None
logger.debug('room %s 网络协程结束', self.room_id)
exception = future.exception()
if exception is not None:
exc_info = (type(exception), exception, exception.__traceback__)
logger.exception('room %s 网络协程异常结束:', self.room_id, exc_info=exc_info)
def stop(self): def stop(self):
""" """
停止本客户端 停止本客户端
""" """
if self._network_future is None: if not self.is_running:
logger.warning('room %s 已经停止不能再次stop', self.room_id) logger.warning('room %s 已经停止不能再次stop', self.room_id)
return return
@ -215,7 +207,7 @@ class BLiveClient:
""" """
等待本客户端停止 等待本客户端停止
""" """
if self._network_future is None: if not self.is_running:
logger.warning('room %s 已经停止不能join', self.room_id) logger.warning('room %s 已经停止不能join', self.room_id)
return return
@ -225,7 +217,7 @@ class BLiveClient:
""" """
释放本客户端的资源调用后本客户端将不可用 释放本客户端的资源调用后本客户端将不可用
""" """
if self._network_future is not None: if self.is_running:
logger.warning('room %s 在运行状态中调用了close', self.room_id) logger.warning('room %s 在运行状态中调用了close', self.room_id)
# 如果session是自己创建的则关闭session # 如果session是自己创建的则关闭session
@ -324,21 +316,20 @@ class BLiveClient:
) )
return header + body return header + body
async def _send_auth(self): async def _network_coroutine_wrapper(self):
""" """
发送认证包 负责处理网络协程的异常网络协程具体逻辑在_network_coroutine里
""" """
auth_params = { try:
'uid': self._uid, await self._network_coroutine()
'roomid': self._room_id, except asyncio.CancelledError:
'protover': 2, # 正常停止
'platform': 'web', pass
'clientver': '1.14.3', except Exception as e: # noqa
'type': 2 logger.exception('room %s 网络协程异常结束:', self.room_id)
} finally:
if self._host_server_token is not None: logger.debug('room %s 网络协程结束', self.room_id)
auth_params['key'] = self._host_server_token self._network_future = None
await self._websocket.send_bytes(self._make_packet(auth_params, Operation.AUTH))
async def _network_coroutine(self): async def _network_coroutine(self):
""" """
@ -346,11 +337,8 @@ class BLiveClient:
""" """
# 如果之前未初始化则初始化 # 如果之前未初始化则初始化
if self._host_server_token is None: if self._host_server_token is None:
try: if not await self.init_room():
if not await self.init_room(): raise InitError('初始化失败')
raise InitError('初始化失败')
except asyncio.CancelledError:
return
retry_count = 0 retry_count = 0
while True: while True:
@ -372,15 +360,11 @@ class BLiveClient:
await self._on_ws_message(message) await self._on_ws_message(message)
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.error('room %d 发生SSL错误无法重连', self.room_id)
# 证书错误时无法重连 raise
break
finally: finally:
self._websocket = None self._websocket = None
await self._on_ws_close() await self._on_ws_close()
@ -388,10 +372,7 @@ class BLiveClient:
# 准备重连 # 准备重连
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: await asyncio.sleep(1, loop=self._loop)
await asyncio.sleep(1)
except asyncio.CancelledError:
break
async def _on_ws_connect(self): async def _on_ws_connect(self):
""" """
@ -400,6 +381,30 @@ class BLiveClient:
await self._send_auth() await self._send_auth()
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 _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 _send_auth(self):
"""
发送认证包
"""
auth_params = {
'uid': self._uid,
'roomid': self._room_id,
'protover': 2,
'platform': 'web',
'clientver': '1.14.3',
'type': 2
}
if self._host_server_token is not None:
auth_params['key'] = self._host_server_token
await self._websocket.send_bytes(self._make_packet(auth_params, Operation.AUTH))
def _on_send_heartbeat(self): def _on_send_heartbeat(self):
""" """
定时发送心跳包的回调 定时发送心跳包的回调
@ -422,18 +427,11 @@ class BLiveClient:
try: try:
await self._parse_ws_message(message.data) await self._parse_ws_message(message.data)
except asyncio.CancelledError: except asyncio.CancelledError:
# 正常停止,让外层处理
raise raise
except Exception: # noqa except Exception: # noqa
logger.exception('room %d 处理websocket消息时发生错误', self.room_id) 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): async def _parse_ws_message(self, data: bytes):
""" """
解析websocket消息 解析websocket消息
@ -459,7 +457,7 @@ class BLiveClient:
'popularity': popularity 'popularity': popularity
} }
} }
await self._parse_command(body) await self._handle_command(body)
elif header.operation == Operation.SEND_MSG_REPLY: elif header.operation == Operation.SEND_MSG_REPLY:
# 业务消息 # 业务消息
@ -472,9 +470,9 @@ class BLiveClient:
# 没压缩过的 # 没压缩过的
try: try:
body = json.loads(body.decode('utf-8')) body = json.loads(body.decode('utf-8'))
await self._parse_command(body) await self._handle_command(body)
except Exception: except Exception:
logger.error('body=%s', body) logger.error('room %d body=%s', self.room_id, body)
raise raise
elif header.operation == Operation.AUTH_REPLY: elif header.operation == Operation.AUTH_REPLY:
@ -489,20 +487,23 @@ class BLiveClient:
offset += header.pack_len offset += header.pack_len
async def _parse_command(self, command: Union[list, dict]): async def _handle_command(self, command: Union[list, dict]):
""" """
解析业务消息 解析并处理业务消息
:param command: 业务消息 :param command: 业务消息
""" """
# 这里可能会多个消息一起发 # 这里可能会多个消息一起发
if isinstance(command, list): if isinstance(command, list):
for one_command in command: for one_command in command:
await self._parse_command(one_command) await self._handle_command(one_command)
return return
for handler in self._handlers: results = await asyncio.gather(
try: *(handler.handle(self, command) for handler in self._handlers),
await handler.handle(self, command) loop=self._loop,
except Exception: # noqa return_exceptions=True
logger.exception('room %d 处理消息时发生错误command=%s', self.room_id, command) )
for res in results:
if isinstance(res, Exception):
logger.exception('room %d 处理消息时发生错误command=%s', self.room_id, command, exc_info=res)