Asyncio致命错误:protocol.data_received()调用失败

时间:2018-09-08 16:07:09

标签: python-asyncio

当客户端发送大消息〜5300字节时,asyncio获得2917-2923字节,然后引发此错误.Python 3.7。 未收到的邮件最终结束

  

{“ item”:4,“ active”:false,“ num”:2,“ turn_touch”:0,“ damaged_in_turn”:0},{“ item”:7,“ active”:fal

当然,json无法解析此消息

错误日志

ERROR:asyncio:Fatal error: protocol.data_received() call failed.
protocol: <__main__.Server object at 0xb5a2e0ac>
transport: <_SelectorSocketTransport fd=9 read=polling write=<idle, bufsize=0>>
Traceback (most recent call last):
  File "/usr/lib/python3.7/asyncio/selector_events.py", line 824, in _read_ready__data_received
    self._protocol.data_received(data)
  File "/home/den/piratebay/server.py", line 41, in data_received
    self.message_handle.check_message_id(self, data)
  File "/home/den/piratebay/message_handle.py", line 25, in check_message_id
    self.parsed_data = parse_data(data)
  File "/home/den/piratebay/action.py", line 50, in parse_data
    return json.loads(data)
  File "/usr/lib/python3.7/json/__init__.py", line 348, in loads
    return _default_decoder.decode(s)
  File "/usr/lib/python3.7/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/lib/python3.7/json/decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 2918 (char 2917)

server.py

class Server(asyncio.Protocol):
    def connection_made(self, transport):
        logging.debug('connection_made')
        """ Called on instantiation, when new client connects """
        self.transport = transport
        self.addr = transport.get_extra_info('peername')
        s = transport.get_extra_info("socket")
        print('Connection from {}'.format(self.addr))


    def data_received(self, data):
        """ Handle data as it's received.  """
        logging.debug('received {} bytes'.format(len(data)))
        data = data.decode()
        print('received data->',data)
        self.message_handle.check_message_id(self, data)


    def connection_lost(self, ex):
        """ Called on client disconnect. Clean up client state """       
        self.transport.close()


if __name__ == '__main__':
    loop = asyncio.get_event_loop()

    # Create server and initialize on the event loop
    coroutine = loop.create_server(Server,
                                   host=HOST,
                                   port=PORT)

此错误的原因是什么?谢谢!

1 个答案:

答案 0 :(得分:1)

不能保证单个data_received会收到整个应用程序级消息。 TCP是基于流的协议,甚至不提供 message 的概念,而只是提供字节流。客户端上的一次写入可以分为多个数据包,并由服务器进行多次读取。相反,可以将多个写入合并为一个数据包并作为一个单元接收。

正确编写的asyncio程序不能假设所有数据都将在单个data_received调用中到达。相反,data_received必须收集传入的数据,识别数据何时完成,然后才处理数据。如何识别消息的结尾将取决于所使用的协议-例如,http提供了预先声明内容长度的选项。如果希望客户端在发送消息后断开连接,则遇到文件结束条件将识别出消息的结束。

在后一种情况下,data_received将收集字节,这些字节将由eof_received处理:

import asyncio, io

class Server(asyncio.Protocol):
    def connection_made(self, transport):
        self.transport = transport
        addr = transport.get_extra_info('peername')
        print('Connection from {}'.format(addr))
        self._data = io.BytesIO()

    def data_received(self, data):
        print('received data->',data)
        self._data.write(data)

    def eof_received(self):
        print('received EOF')
        self._data.write(data)
        data = self._data.getvalue()
        data = data.decode()
        self.message_handle.check_message_id(self, data)

    def connection_lost(self, ex):
        self.transport.close()

请注意,上述代码使用基于流的API编写起来要简单得多,这是编写现代异步代码的recommended方法:

import asyncio

async def handle_client(r, w):
    data = await r.read()  # read until EOF
    data = data.decode()
    message_handle.check_message_id(data)
    # ...

loop = asyncio.get_event_loop()
message_handle = ...
server = loop.run_until_complete(
    asyncio.start_server(
        lambda r, w: handle_client(r, w, message_handle),
        '127.0.0.1', 8888))
loop.run_forever()