使用asyncio的Python简单套接字客户端/服务器

srj*_*jio 11 python sockets python-asyncio

我想使用asyncio协同程序而不是多线程来重新实现我的代码.

server.py

def handle_client(client):
    request = None
    while request != 'quit':
        request = client.recv(255).decode('utf8')
        response = cmd.run(request)
        client.send(response.encode('utf8'))
    client.close()

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 15555))
server.listen(8)

try:
    while True:
        client, _ = server.accept()
        threading.Thread(target=handle_client, args=(client,)).start()
except KeyboardInterrupt:
    server.close()
Run Code Online (Sandbox Code Playgroud)

client.py

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.connect(('localhost', 15555))
request = None

try:
    while request != 'quit':
        request = input('>> ')
        if request:
            server.send(request.encode('utf8'))
            response = server.recv(255).decode('utf8')
            print(response)
except KeyboardInterrupt:
    server.close()
Run Code Online (Sandbox Code Playgroud)

我知道有一些适当的异步网络库可以做到这一点.但我只是想在这种情况下只使用asyncio核心库,以便更好地理解它.

在处理客户端定义之前只添加async关键字真是太好了......这里有一段似乎有效的代码,但我仍然对实现感到困惑.

asyncio_server.py

def handle_client(client):
    request = None
    while request != 'quit':
        request = client.recv(255).decode('utf8')
        response = cmd.run(request)
        client.send(response.encode('utf8'))
    client.close()

def run_server(server):
    client, _ = server.accept()
    handle_client(client)

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 15555))
server.listen(8)

loop = asyncio.get_event_loop()
asyncio.async(run_server(server))
try:
    loop.run_forever()
except KeyboardInterrupt:
    server.close()
Run Code Online (Sandbox Code Playgroud)

如何以最佳方式调整此方法并使用async等待关键字.

use*_*342 14

线程代码的最接近的字面翻译将像以前一样创建套接字,并使用asyncio 低级套接字操作来实现服务器.这是一个例子,坚持更相关的服务器部分(客户端是单线程的,可能很好):

import asyncio, socket

async def handle_client(client):
    request = None
    while request != 'quit':
        request = (await loop.sock_recv(client, 255)).decode('utf8')
        response = str(eval(request)) + '\n'
        await loop.sock_sendall(client, response.encode('utf8'))
    client.close()

async def run_server():
    while True:
        client, _ = await loop.sock_accept(server)
        loop.create_task(handle_client(client))

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 15555))
server.listen(8)
server.setblocking(False)

loop = asyncio.get_event_loop()
loop.run_until_complete(run_server())
Run Code Online (Sandbox Code Playgroud)

以上工作,但不是预期的使用方式asyncio.虽然正确的抽象取决于应用程序,但您可能希望至少使用asyncio.start_server而不是使用原始套接字.这大大减少了代码的行数:

async def handle_client(reader, writer):
    request = None
    while request != 'quit':
        request = (await reader.read(255)).decode('utf8')
        response = str(eval(request)) + '\n'
        writer.write(response.encode('utf8'))
        await writer.drain()
    writer.close()

loop = asyncio.get_event_loop()
loop.create_task(asyncio.start_server(handle_client, 'localhost', 15555))
loop.run_forever()
Run Code Online (Sandbox Code Playgroud)

有关其他详细信息,请参阅文档

  • 它工作得很好,即使我仍然困惑为什么异步和套接字似乎如此复杂地包装在同一个库中...... (4认同)
  • @srjjio 网络(套接字)编程首先构成了需要异步 IO 的一大块_motivation_。不提供对套接字支持的 asyncio 库没有多大用处。 (4认同)
  • @DevPlayer 不,“run_server”是一个协程函数,“run_until_complete”需要一个可等待的函数。要创建等待,您必须实际调用协程函数。这类似于期望可迭代的函数 - 你不能只向它们传递一个生成器函数,你必须实际上“调用”生成器来获取可迭代。 (2认同)

Fox*_*Fox 12

我已阅读上面的答案和评论,试图弄清楚如何使用asyncio套接字的库。正如 Python 中经常发生的那样,官方文档和示例是有用信息的最佳来源。我从支持文章末尾提供的示例中了解了Transports and Protocols(低级 API)和Streams(高级 API)。

例如,TCP Echo 服务器:

import asyncio


class EchoServerProtocol(asyncio.Protocol):
    def connection_made(self, transport):
        peername = transport.get_extra_info('peername')
        print('Connection from {}'.format(peername))
        self.transport = transport

    def data_received(self, data):
        message = data.decode()
        print('Data received: {!r}'.format(message))

        print('Send: {!r}'.format(message))
        self.transport.write(data)

        print('Close the client socket')
        self.transport.close()


async def main():
    # Get a reference to the event loop as we plan to use
    # low-level APIs.
    loop = asyncio.get_running_loop()

    server = await loop.create_server(
        lambda: EchoServerProtocol(),
        '127.0.0.1', 8888)

    async with server:
        await server.serve_forever()


asyncio.run(main())
Run Code Online (Sandbox Code Playgroud)

和 TCP 回显客户端:

import asyncio


class EchoClientProtocol(asyncio.Protocol):
    def __init__(self, message, on_con_lost):
        self.message = message
        self.on_con_lost = on_con_lost

    def connection_made(self, transport):
        transport.write(self.message.encode())
        print('Data sent: {!r}'.format(self.message))

    def data_received(self, data):
        print('Data received: {!r}'.format(data.decode()))

    def connection_lost(self, exc):
        print('The server closed the connection')
        self.on_con_lost.set_result(True)


async def main():
    # Get a reference to the event loop as we plan to use
    # low-level APIs.
    loop = asyncio.get_running_loop()

    on_con_lost = loop.create_future()
    message = 'Hello World!'

    transport, protocol = await loop.create_connection(
        lambda: EchoClientProtocol(message, on_con_lost),
        '127.0.0.1', 8888)

    # Wait until the protocol signals that the connection
    # is lost and close the transport.
    try:
        await on_con_lost
    finally:
        transport.close()


asyncio.run(main())
Run Code Online (Sandbox Code Playgroud)

希望它可以帮助那些寻找简单解释的人asyncio

  • 请注意,粘贴的示例当然**不是**使用 asyncio 的好示例,因为它们显示了较旧的基于回调的样式,而不是 async/await ,后者更易于使用并且现在更具代表性(在 python 中以及在其他语言)。 (3认同)