首页 > 解决方案 > 使用 asyncio 和 Python 3.6,如何构建处理/提交多个顺序请求的 TCP 服务器/客户端?

问题描述

pydocs中的示例服务器如下:

import asyncio                                                        

@asyncio.coroutine                                                    
def handle_echo(reader, writer):                                      
    data = yield from reader.read(100)                                
    message = data.decode()                                           
    addr = writer.get_extra_info('peername')                          
    print("Received %r from %r" % (message, addr))                    

    print("Send: %r" % message)                                       
    writer.write(data)                                                
    yield from writer.drain()                                         

    print("Close the client socket")                                  
    writer.close()                                                    

loop = asyncio.get_event_loop()                                       
coro = asyncio.start_server(handle_echo, '127.0.0.1', 8889, 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:                                             
    pass                                                              

# Close the server                                                    
server.close()                                                        
loop.run_until_complete(server.wait_closed())                         
loop.close()                                                          

我调整了示例客户端以发出两个连续的请求:

import asyncio

async def tcp_echo_client(loop):
    reader, writer = await asyncio.open_connection('127.0.0.1', 8889,
                                                        loop=loop)
    await make_request(reader, writer, "Foo")
    await make_request(reader, writer, "Bar")

    print('Close the socket')
    writer.close()

async def make_request(reader, writer, message):
    print('Send: %r' % message)
    writer.write(message.encode())

    data = await reader.read(100)
    print('Received: %r' % data.decode())

loop = asyncio.get_event_loop()
loop.run_until_complete(tcp_echo_client(loop))
loop.close()

观察到的行为是客户端获得了对第一个请求的响应,但套接字在处理第二个请求之前关闭。因此,客户端没有收到对第二个请求的响应。

服务器日志:

Received 'Foo' from ('127.0.0.1', 58112)
Send: 'Foo'                             
Close the client socket                 

客户端日志:

Send: 'Foo'
Received: 'Foo'
Send: 'Bar'
Received: ''
Close the socket

期望的行为是客户端接收Bar响应第二个请求,在请求之间保持套接字打开。

如果我注释掉关闭服务器上的套接字,它会阻塞以使客户端永远不会读取对第一个请求的响应,即使缓冲区应该已被write.drain().

指导将不胜感激;提前致谢。

标签: pythontcppython-3.6python-asyncio

解决方案


我想我想通了。查看服务器代码:

  • asyncio.start_server不会重复调用handle_echo它的事件循环。我负责在该处理函数中编写一个循环,重用reader每个writer请求。
  • 而不是抛出异常,而是yield from reader.read(100)在客户端关闭它的套接字结束后返回 0 个字节。

因此,这是新的服务器代码,它按预期工作:

import asyncio

@asyncio.coroutine
def handle_echo(reader, writer):
    while True:
        data = yield from reader.read(100)
        if len(data) > 0:
            message = data.decode()
            addr = writer.get_extra_info('peername')
            print("Received %r from %r" % (message, addr))

            print("Send: %r" % message)
            writer.write(data)
            yield from writer.drain()
        else:
            print("Close the client socket")
            writer.close()
            break

loop = asyncio.get_event_loop()
coro = asyncio.start_server(handle_echo, '127.0.0.1', 8889, 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:
    pass

# Close the server
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()

推荐阅读