I'm using asyncio to run a straightforward server and a client. The server is a simple echo server with two "special" commands sent by the client, "quit" and "timer". The quit command closes the connection, and the timer command starts a timer that will print a message in the console (server and client) every second. The timer will stop when the client sends the "quit" command.
Unfortunately, I'm having some problems with the timer. It blocks the server and the client.
How can I solve this problem?
Server
import asyncio
import time
HOST = '127.0.0.1'
PORT = 9999
async def timer():
while True:
print('tick')
await asyncio.sleep(1)
async def handle_echo(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
'''Handle the echo protocol.'''
data = None
while True:
if(data == b'quit'):
writer.close() # Close the connection
await writer.wait_closed() # Wait for the connection to close
if(data == b'timer'):
timertask = asyncio.create_task(timer())
await timertask #<-- This line freezes the server and the client
else:
data = await reader.read(1024) # Read 256 bytes from the reader. Size of the message
msg = data.decode() # Decode the message
addr, port = writer.get_extra_info('peername') # Get the address of the client
print(f"Received {msg!r} from {addr}:{port!r}")
send_message = 'Message received: ' msg
writer.write(send_message.encode()) # Echo the data back to the client
await writer.drain() # This will wait until everything is clear to move to the next thing.
async def run_server() -> None:
# Our awaitable callable.
# This callable is ran when the server recieves some data
# Question: Does it run when a client connects?
server = await asyncio.start_server(handle_echo, HOST, PORT)
async with server:
await server.serve_forever()
if __name__ == '__main__':
loop = asyncio.new_event_loop() # new_event_loop() is for python 3.10. For older versions, use get_event_loop()
loop.run_until_complete(run_server())
Client
import asyncio
import time
HOST = '127.0.0.1'
PORT = 9999
async def run_client() -> None:
# It's a coroutine. It will wait until the connection is established
reader, writer = await asyncio.open_connection(HOST, PORT)
while True:
message = input('Enter a message: ')
writer.write(message.encode())
await writer.drain()
data = await reader.read(1024)
if not data:
raise Exception('Socket not communicating with the client')
print(f"Received {data.decode()!r}")
if(message == 'quit'):
writer.write(b"quit")
writer.close()
await writer.wait_closed()
exit(2)
#break # Don't know if this is necessary
if __name__ == '__main__':
loop = asyncio.new_event_loop()
loop.run_until_complete(run_client())
CodePudding user response:
I slightly updated your server code: removed the await timertask
line (this task never ends, can be only canceled):
Server.py
import asyncio
import time
HOST = "127.0.0.1"
PORT = 9999
async def timer():
while True:
print("tick")
await asyncio.sleep(1)
async def handle_echo(
reader: asyncio.StreamReader, writer: asyncio.StreamWriter
) -> None:
# timer task that the client can start:
timer_task = None
while True:
data = await reader.read(1024)
msg = data.decode()
addr, port = writer.get_extra_info("peername")
print(f"Received {msg!r} from {addr}:{port!r}")
send_message = "Message received: " msg
writer.write(send_message.encode())
await writer.drain()
if data == b"quit":
# cancel the timer_task (if any)
if timer_task:
timer_task.cancel()
await timer_task
writer.close()
await writer.wait_closed()
elif data == b"timer" and timer_task is None:
timer_task = asyncio.create_task(timer())
async def run_server() -> None:
server = await asyncio.start_server(handle_echo, HOST, PORT)
async with server:
await server.serve_forever()
if __name__ == "__main__":
loop = asyncio.new_event_loop()
loop.run_until_complete(run_server())
Client.py
import asyncio
import time
HOST = "127.0.0.1"
PORT = 9999
async def run_client() -> None:
# It's a coroutine. It will wait until the connection is established
reader, writer = await asyncio.open_connection(HOST, PORT)
while True:
message = input("Enter a message: ")
writer.write(message.encode())
await writer.drain()
data = await reader.read(1024)
if not data:
raise Exception("Socket not communicating with the client")
print(f"Received {data.decode()!r}")
if message == "quit":
writer.write(b"quit")
await writer.drain()
writer.close()
await writer.wait_closed()
exit(2)
if __name__ == "__main__":
loop = asyncio.new_event_loop()
loop.run_until_complete(run_client())
The server waits for a client's messages. If message timer
arrives, starts a new timer task and continues to read other messages.
When quit
arrives, server cancels the timer_task
(if exists) and exits.