0

I'm trying to setup a threaded server where multiple clients can connect at the same time. This is a bit long, please do bear with me. I've already read this helpful realpython article on sockets as well as the socket and socketserver docs.

Python provides facilities to create a server and the socketserver documentation even shows us:

import socket
import threading
import socketserver

class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):

    def handle(self):
        data = str(self.request.recv(1024), 'ascii')
        cur_thread = threading.current_thread()
        response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')
        self.request.sendall(response)

class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass

def client(ip, port, message):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect((ip, port))
        sock.sendall(bytes(message, 'ascii'))
        response = str(sock.recv(1024), 'ascii')
        print("Received: {}".format(response))

if __name__ == "__main__":
    # Port 0 means to select an arbitrary unused port
    HOST, PORT = "localhost", 0

    server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
    with server:
        ip, port = server.server_address

        # Start a thread with the server -- that thread will then start one
        # more thread for each request
        server_thread = threading.Thread(target=server.serve_forever)
        # Exit the server thread when the main thread terminates
        server_thread.daemon = True
        server_thread.start()
        print("Server loop running in thread:", server_thread.name)

        client(ip, port, "Hello World 1")
        client(ip, port, "Hello World 2")
        client(ip, port, "Hello World 3")

        server.shutdown()

This works fine on my machine but what I'd like to de-couple the client implementation from the server. So I split the implementations and I have.

server.py:

import socketserver
import threading


class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
    def handle(self):
        data = str(self.request.recv(1024), 'ascii')
        cur_thread = threading.current_thread()
        response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')
        self.request.sendall(response)


class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass


def main():
    HOST, PORT = "localhost", 8080 # localhost aka 127.0.0.1
    server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
    with server:
        server_thread = threading.Thread(target=server.serve_forever)
        server_thread.daemon = False  # Do not stop and keep serving
        server_thread.start()
        print("Server loop running in thread: ", server_thread.name)


if __name__ == '__main__':
    main()

and client.py

import socket

class Client:
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port

    def start(self, message):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
            sock.connect((self.ip, self.port))
            sock.sendall(bytes(message, "ascii"))
            response = str(sock.recv(1024), "ascii")
            print("Received: {}".format(response))


def main():
    client = Client("localhost", 8080)
    client.start("Hello World!")


if __name__ == '__main__':
    main()

Then I open two different shell occurences and I do:

$ python3 -m server

The server outputs its message and the shell hangs, which is the expected behavior since I turned daemon mode off and it is supposed to serve forever.

Pinging localhost works fine, all packages are received with no loss. On the other hand, using netstat, I can't seem to find the port I'm opening (8080).

and in the other:

$ python3 -m client

Fails with a ConnectionRefusedError which is the typical error when there is nothing to connect to.

My conclusion for now is that the server is closing the port at some point, I suppose when the main thread dies?

I'm not sure how to fix it though? What's wrong with the current implementation.

Thanks for the help.

Nathan Furnal
  • 2,236
  • 3
  • 12
  • 25
  • Probably when the `with` block ends it shuts down the server. What if you do `server_thread.join()` before that? – user253751 Apr 26 '22 at 14:52
  • @user253751 Yep that was it! The end of the context manager `with` implicitly shuts down the server. Thanks for the help! – Nathan Furnal Apr 26 '22 at 14:56

0 Answers0