python parallel socket connections

Asked by At

I'm writing basic client-server application. Here it is.

Server:

import socket
from multiprocessing import Process


def process_request(conn, addr):
    print('connected client:', addr)
    with conn:
        while True:
            data = conn.recv(1024)
            if not data:
                break
            print(data.decode('utf8'))


if __name__ == '__main__':
    with socket.socket() as sock:
        sock.bind(('', 10001))
        sock.listen()
        while True:
            conn, addr = sock.accept()
            p = Process(target = process_request, args = (conn, addr))
            print('starting new process')
            p.start()
            p.join()

Client:

import socket
from time import sleep
from multiprocessing import Pool


def create_socket_and_send_data(number):
    print(f'start sending data for {number}')

    with socket.create_connection(('127.0.0.1', 10001)) as sock:
        try:
            sock.sendall(f'sending data from {number} socket (1)\n'.encode('utf8'))
            sleep(2)
            sock.sendall(f'sending data from {number} socket (2)\n'.encode('utf8'))
        except socket.error as ex:
            print('send data error', ex)

    print(f'all data for {number} is sended')


if __name__ == '__main__':
    with Pool(processes=3) as pool:
        pool.map(create_socket_and_send_data, range(5))

Client output:

$ python 59_several_connection_client.py
start sending data for 0
start sending data for 1
start sending data for 2
all data for 0 is sended
all data for 1 is sended
all data for 2 is sended
start sending data for 3
start sending data for 4
all data for 3 is sended
all data for 4 is sended

Server output:

starting new process
connected client: ('127.0.0.1', 63476)
sending data from 0 socket (1)

sending data from 0 socket (2)

starting new process
connected client: ('127.0.0.1', 63477)
sending data from 1 socket (1)
sending data from 1 socket (2)

starting new process
connected client: ('127.0.0.1', 63478)
sending data from 2 socket (1)
sending data from 2 socket (2)

starting new process
connected client: ('127.0.0.1', 63479)
sending data from 3 socket (1)

sending data from 3 socket (2)

starting new process
connected client: ('127.0.0.1', 63480)
sending data from 4 socket (1)
sending data from 4 socket (2)

I have several questions. First of all, I don't understand why the sequence of output is like that. I thought that it will be like:

data from socket 0 (1)
data from socket 1 (1)
data from socket 2 (1)
# now the pull is full, processes are sleeping
data from socket 0 (2)
data from socket 3 (1)
data from socket 1 (2)
data from socket 4 (1)
data from socket 2 (2)
# pay attention that pool had contained 3 processes, 
# it finished them one by one, so new places in the pool 
# were occupied by fresh processes that started to execute
# 
# now old 3 processes have been finished 
# and pull contains 2 new processes, they are sleeping
data from socket 4 (2)
data from socket 3 (2)
# all ok, 2 processes are done

But unexpectedly the output is sequential like the code runs in the single thread. Why? Could somebody explain where is the gap in my thoughts?

And second tiny question is: why the output sometimes contains empty lines?

1 Answers

1
Arthur Havlicek On Best Solutions
  • You simply join() too soon. Your main server process wait for each son to finish before spawning a new one, hence the sequential execution. To exit cleanly, you can collect processes and joins them before ending execution of your main loop.
        processes = []
        while True:
            conn, addr = sock.accept()
            p = Process(target = process_request, args = (conn, addr))
            processes.append(p) 
            print('starting new process')
            p.start()
        for p in processes:
            p.join()

Careful that in this minimal example, processes list is growing forever. You may want to clean the list at regular intervals checking their exitcode as well. In this use case maintaining processes is a bit tedious, so I would also suggest alternative ways to spawn asynchronous tasks in python.

  • Empty lines are caused because your packets end with '\n' and print adds an extra '\n'. Thus when a pending packet containing messages is printed, it jumps an extra line. Processes which had their spawn delayed (1, 2, and 4) print two lines at once because their data got buffered server-side by the system, and only print one jump per two lines, while other processes (0 and 3) print their output line by line in time.