Ed Noepel
Ed Noepel

Reputation: 442

Using asyncio and Python 3.6, how can I build a TCP server/client which handles/submits multiple sequential requests?

The example server in pydocs is as follows:

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()                                                          

I adapted the example client to make two consecutive requests:

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()

The observed behavior is that the client gets a response to the first request, but the socket is closed before the second request is handled. As such, the client receives no response to the second request.

Server logs:

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

Client logs:

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

Desired behavior is for the client to receive Bar in response to the second request, keeping the socket open between requests.

If I comment out closing the socket on the server, it blocks such that the client never reads the response to the first request, even though the buffer should have been flushed by the write.drain().

Guidance would be most appreciated; thanks in advance.

Upvotes: 0

Views: 3604

Answers (1)

Ed Noepel
Ed Noepel

Reputation: 442

I think I figured this out. Looking at the server code:

  • asyncio.start_server doesn't repeatedly call handle_echo in it's event loop. I'm responsible for writing a loop within that handler function, reusing reader and writer for each request.
  • Rather than throwing an exception, yield from reader.read(100) returns 0 bytes after the client closes it's end of the socket.

As such, here's the new server code, which works as intended:

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()

Upvotes: 1

Related Questions