1

I want to send multi messages using socket module without waiting the respond from the client or server. However the codes that are below can not do this. What are your suggestions in order to do that? Thanks in advance.

Here are the codes:

server.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("", 12345))
s.listen(1)
print(s)

c, addr = s.accept()
print('{} connected.'.format(addr))

while True:
    respond = input("Server: ").encode("utf-8")
    if respond == b"q":
        exit()
    else:
        c.sendall(bytes(respond))
        data = str(c.recv(1024))[1:]
        if data:
            print("Client: {}".format(data))

client.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("", 12345))

while True:
    respond = input("Client: ").encode("utf-8")
    if respond == b"q":
        exit()
    else:
        s.sendall(bytes(respond))
        data = str(s.recv(1024))[1:]
        if data:
            print("Server: {}".format(data))
dildeolupbiten
  • 1,314
  • 1
  • 15
  • 27

1 Answers1

2

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.

ElmoVanKielmo
  • 10,907
  • 2
  • 32
  • 46