Ok, so for what you want to achieve, you need to change the approach completely and use asyncio
equivalents of socket methods as well as replacement for bare standard input handling.
The following code works on Python >= 3.5 and requires aioconsole
Python package which can be installed with pip install aioconsole
.
server.py
import asyncio
import aioconsole
class StreamWrapper(object):
"""This class is used to make socket stream created by server available for send_messgaes() function"""
def __init__(self):
self.reader = None
self.writer = None
async def send_messages(stream_wrapper, stdin):
# Wait asynchronously until server part initializes socket stream
while stream_wrapper.writer is None:
await asyncio.sleep(0.1)
writer = stream_wrapper.writer
# Asynchronusly read from standard input
async for message in stdin:
if message.decode().strip() == "q":
writer.close()
exit()
else:
# Send message through the socket
writer.write(message)
def receive_messages_wrapper(stream_wrapper, stdout):
"""Wrapper function which adds stream_wrapper and stdout to the scope of receive_messages()"""
async def receive_messages(reader, writer):
# Copy socket stream reference to stream wrapper
stream_wrapper.reader = reader
stream_wrapper.writer = writer
# Asynchronusly read messages from the socket
async for message in reader:
stdout.write('\nClient: {}'.format(message.decode()))
stdout.write("Server: ")
# Wrapper returns receive_messages function with enhanced scope - receive_messages() can "see" stream_wrapper and stdout
return receive_messages
async def run_server(loop):
"""Initialize stdin and stdout asynchronous streams and start the server"""
stdin, stdout = await aioconsole.get_standard_streams()
stream_wrapper = StreamWrapper()
# Asynchronously execute send_messages and start_server()
await asyncio.gather(
send_messages(stream_wrapper, stdin),
asyncio.start_server(receive_messages_wrapper(stream_wrapper, stdout), '127.0.0.1', 8888, loop=loop)
)
# Run the server on the event loop
loop = asyncio.get_event_loop()
loop.run_until_complete(run_server(loop))
loop.close()
client.py
import asyncio
import aioconsole
async def send_messages(writer, stdin):
# Asynchronusly read from standard input
async for message in stdin:
if message.decode().strip() == "q":
writer.close()
exit()
else:
# Send message through the socket
writer.write(message)
async def receive_messages(reader, stdout):
# Asynchronusly read messages from the socket
async for message in reader:
stdout.write('\nServer: {}'.format(message.decode()))
stdout.write("Client: ")
async def run_client(loop):
"""Initialize stdin and stdout asynchronous streams and open the client connection, then start exchanging messages"""
stdin, stdout = await aioconsole.get_standard_streams()
reader, writer = await asyncio.open_connection('127.0.0.1', 8888)
stdout.write("Client: ")
# Asynchronously execute send_messages and receive_messages()
await asyncio.gather(
send_messages(writer, stdin),
receive_messages(reader, stdout)
)
# Run the client on the event loop
loop = asyncio.get_event_loop()
loop.run_until_complete(run_client(loop))
loop.close()
This might seem complicated if you never used asyncio
before but it does exactly what you want. You can spam multiple messages from any end (client or server) and the other end will receive it and print, while waiting for user input. I've provided comments but if you want to fully understand it, you should get familiar with asyncio documentation.
The other possible approaches involve using threads or multiprocessing. I wouldn't say they are easier than asyncio.