使用 asyncio 的 Python 网络

Posted

技术标签:

【中文标题】使用 asyncio 的 Python 网络【英文标题】:Python Networking with asyncio 【发布时间】:2018-07-09 16:51:11 【问题描述】:

所以我正在学习如何制作一个简单的聊天应用程序,但它是作业的一部分,我必须为此使用 asyncio。问题是当客户端按下 Ctrl-C 时出现运行时错误,我不知道如何修复它 第二个问题是当服务器终止时,客户端挂起,等待用户输入;相反,我希望客户 打印后也终止服务器关闭连接。 这是服务器的代码:

import sys, asyncio

all_clients = set([])

@asyncio.coroutine
def handle_connection(reader, writer):
    all_clients.add(writer)
    client_addr = writer.get_extra_info('peername')
    print('New client '.format(client_addr))
    while True:
        data = yield from reader.read(100)
        if data == None or len(data) == 0:
            break
        message = data.decode()
        print("Received  from ".format(message, client_addr))
        for other_writer in all_clients:
            if other_writer != writer:
                new_message = ' says: '.format(client_addr,data)
                other_writer.write(new_message.encode())
                yield from other_writer.drain()
    all_clients.remove(writer)
    print("Close the client socket")
    yield from writer.drain()
    writer.close()

def run():
    loop = asyncio.get_event_loop()
    coro = asyncio.start_server(handle_connection,'127.0.0.1',
                                                8888,loop=loop)
    server = loop.run_until_complete(coro)

    # Serve requests until Ctrl+C is pressed
    print('Serving on '.format(server.sockets[0].getsockname()))
    try:
        loop.run_forever()
    except KeyboardInterrupt:
        print('\nGot keyboard interrupt, shutting down',file=sys.stderr)
        sys.exit()
    for task in asyncio.Task.all_tasks():
        task.cancel()
    server.close()
    loop.run_until_complete(server.wait_closed())
    loop.close()    

if __name__ == '__main__':
    run()

这是客户端的代码:

import sys, asyncio
import aioconsole

class NoneException(Exception):
    pass

class ClosingException(Exception):
    pass

@asyncio.coroutine
def open_connection(loop):
    reader, writer = yield from asyncio.open_connection('127.0.0.1',
                                                     8888,loop=loop)
    return reader, writer

@asyncio.coroutine
def use_connection(reader, writer):
    yield from asyncio.gather(read_from_network(reader,writer),
                                        send_to_server(writer))

@asyncio.coroutine
def read_from_network(reader,writer):
    while True:
        net_message = yield from reader.read(100)
        if writer.transport.is_closing():
            print('Terminating read from network.')
            break
        elif net_message == None:
            continue
        elif len(net_message) == 0:
            print('The server closed the connection.')
            writer.close()
            break
        print('\nReceived: %r' % net_message.decode())
        print('>> ',end='',flush=True)

@asyncio.coroutine
def send_to_server(writer):
    try:
        while True:
            original_message = yield from aioconsole.ainput('>> ')
            if original_message != None:
                console_message = original_message.strip()
                if console_message == '':
                    continue
                if console_message == 'close()' or \
                            writer.transport.is_closing():
                    raise ClosingException()
                writer.write(console_message.encode())
    except ClosingException:
        print('Got close() from user.')
    finally:
        if not writer.transport.is_closing():
            writer.close()

def run():
    try:
        loop = asyncio.get_event_loop()
        reader,writer=loop.run_until_complete(open_connection(loop))
        loop.run_until_complete(use_connection(reader,writer))
    except KeyboardInterrupt:
        print('Got Ctrl-C from user.')
    except Exception as e:
        print(e,file=sys.stderr)
    finally:
        loop.close()


if __name__ == '__main__':
    run()

这里是详细信息或客户端按下 Ctrl-C 时引发的运行时错误

^C从用户那里得到 Ctrl-C。 异常被忽略: 回溯(最近一次通话最后): 文件“client.py”,第 56 行,在 send_to_server 文件“/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/streams.py”,第 306 行,关闭 文件“/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/selector_events.py”,第 622 行,关闭 文件“/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py”,第 573 行,在 call_soon _check_closed 中的文件“/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py”,第 357 行 RuntimeError: 事件循环已关闭

【问题讨论】:

【参考方案1】:

用户writer.close() 在关闭事件循环之前。无需在您的 send_to_server 方法中关闭它。

def run():
   try:
      loop = asyncio.get_event_loop()
      reader,writer=loop.run_until_complete(open_connection(loop))
      loop.run_until_complete(use_connection(reader,writer))
   except KeyboardInterrupt:
       print('Got Ctrl-C from user.')

       # closing writer before the event loop
       writer.close()
   except Exception as e:
       print(e,file=sys.stderr)
   finally:
       loop.close()

【讨论】:

以上是关于使用 asyncio 的 Python 网络的主要内容,如果未能解决你的问题,请参考以下文章

深究Python中的asyncio库-asyncio简介与关键字

使用 asyncio.gather() 的协程/期货的经过时间

asyncio模块

简单的 Python 多线程网络服务器,带有 Asyncio 和在主函数中调用的事件

asyncio 网络抓取 101:使用 aiohttp 获取多个 url

iOS开发网络篇—简单介绍ASI框架的使用