作为一个简单的示例,请考虑以下等效于/dev/zero的网络。 (或更实际的说,只是一个Web服务器发送一个大文件。)

如果客户端过早断开连接,则会收到大量日志消息:

WARNING:asyncio:socket.send() raised exception.

但是我没有找到任何捕获上述异常的方法。假设的服务器继续从磁盘读取千兆字节并将其发送到无效的套接字,而客户端却毫不费力,您就遭受了DoS攻击。

我从文档中发现的唯一一件事是从读取中获得 yield ,其中有一个空字符串指示关闭。但这在这里是不好的,因为普通的客户端不会发送任何东西,从而阻塞了写循环。

用流API或其他方法检测写入失败或被告知TCP连接已关闭的正确方法是什么?

代码:
from asyncio import *
import logging

@coroutine
def client_handler(reader, writer):
    while True:
        writer.write(bytes(1))
        yield from writer.drain()

logging.basicConfig(level=logging.INFO)
loop = get_event_loop()
coro = start_server(client_handler, '', 12345)
server = loop.run_until_complete(coro)
loop.run_forever()

最佳答案

这有点奇怪,但是您实际上可以通过强制将异常交给事件循环进行一次迭代控制来使异常到达client_handler协程:

import asyncio
import logging

@asyncio.coroutine
def client_handler(reader, writer):
    while True:
        writer.write(bytes(1))
        yield  # Yield to the event loop
        yield from writer.drain()

logging.basicConfig(level=logging.INFO)
loop = asyncio.get_event_loop()
coro = asyncio.start_server(client_handler, '', 12345)
server = loop.run_until_complete(coro)
loop.run_forever()

如果这样做,则在终止客户端连接时会得到以下输出:
ERROR:asyncio:Task exception was never retrieved
future: <Task finished coro=<client_handler() done, defined at aio.py:4> exception=ConnectionResetError(104, 'Connection reset by peer')>
Traceback (most recent call last):
  File "/usr/lib/python3.4/asyncio/tasks.py", line 238, in _step
    result = next(coro)
  File "aio.py", line 9, in client_handler
    yield from writer.drain()
  File "/usr/lib/python3.4/asyncio/streams.py", line 301, in drain
    raise exc
  File "/usr/lib/python3.4/asyncio/selector_events.py", line 700, in write
    n = self._sock.send(data)
ConnectionResetError: [Errno 104] Connection reset by peer

我真的不太确定为什么您需要显式地让事件循环获得对异常通过的控制-暂时没有时间进行深入研究。我认为需要翻转一下以指示连接已断开,并且在循环中调用yield from writer.drain()(这可能会使通过事件循环的短路)阻止这种情况的发生,但是我不确定。如果有机会进行调查,我将使用该信息更新答案。

09-13 12:50