0

I have an application which runs multiple servers all on their own threads. I want to be able to tell a thread to stop running. To do this though I would need to tell the thread to stop, the thread would then need to tell the server to stop and the server would then close its own socket (which is in a receiving loop, getting data from all the connected clients). How would I do this?

I have tried using passed stop variables, however I think the issue is in the socket needing to be closed. I can't find a way to tell the server to close the socket without sending a direct message to the server telling it to do so, which seems inefficient.

Here is my server code:

import socket
import threading

class Server:
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    connections = []

    def __init__(self, port):
        self.sock.bind(('0.0.0.0', port))
        self.sock.listen(1)

    def handler(self, c, a):
        while True:
            try:
                data = c.recv(1024) #loop won't run until recieved dat
            except:
                c.shutdown(socket.SHUT_RDWR)
                c.close()
                break

            print("Chat:    ", str(data, 'utf-8'))

            if not data:
                c.close()
                break

    def run(self):
        self._stop = False
        while not self._stop: 
            c, a = self.sock.accept()  ##c is client and a is address
            cThread = threading.Thread(target=self.handler, args=(c,a))
            cThread.daemon = True
            cThread.start()
            self.connections.append(c)
            print("Server:    ", str(a[0]) + ':' + str(a[1]), "connected")
        self.close()

    def shutdownServer(self):
        self._stop = True 

    def close(self):
        print('Closing server')
        if self.sock:
            self.sock.close()
            self.sock = None

def serverRun(port, stop):
    while True:
        print("server port: " + str(port))
        actual_server = Server(port)
        actual_server.run()
        if(stop):
            print("Stopping server thread")
            break

Here is the code which sets up the thread and runs the server:

def main():
        stopThreads = False
        thread = threading.Thread(target = server.serverRun, args=(1, lambda : stopThreads,))
        thread.start()
        time.sleep(1)
        stopThreads = True
        thread.join()
        print("server thread killed")
main()

Any help would be appreciated.

Edit: Edited to clarify the problem is less so closing the thread and more so passing a variable to the class being run in the thread, so it can close its socket when the thread is trying to be stopped.

6Willows
  • 43
  • 7
  • Follow this answer [close a thread on multithreading](https://stackoverflow.com/a/43686996/7414759) a community – stovfl Apr 28 '20 at 10:49
  • @stovfl This doesn't work, it continuously waits at the thread.join() because of the server's receive thread – 6Willows Apr 28 '20 at 11:03
  • That's true, I have added the while running.is_set to all the while loops. Still doesn't seem to work – 6Willows Apr 28 '20 at 11:12
  • It points out, you are asking B but need A. This: `data = c.recv(...` is blocking. Either close the connection or send a **TERMINATION** message and break the `while ...`. – stovfl Apr 28 '20 at 12:32
  • @stovfl Apologies, yes, I need to know how to tell a thread to stop and when I do that, have the connection close so that the thread can join. Unfortunately, I can't find a way to tell the connection to close without sending a message to it, as it blocks anything else from running in the while loop – 6Willows Apr 28 '20 at 13:44
  • ***close without sending a message***: Read up on [`socket.close()`](https://docs.python.org/3/library/socket.html?highlight=socket#socket.socket.close) – stovfl Apr 28 '20 at 14:01
  • @stovfl Yes, sorry I know how to use socket.close. The problem is telling the thread that the server needs to close its socket, and then getting the thread to tell the server that so that the server then can close said socket. I will edit my post to make this clear – 6Willows Apr 28 '20 at 14:18
  • Wrong direction! You can't tell a Thread anything. You have to call `.close()` on the `socket` in question to return from the **blocking** `recv(...` and break the `while ...`which ends the `Thread`. – stovfl Apr 28 '20 at 16:43
  • @stovfl To do that the socket would need to know when to close. So the only way to do this then would be to tell the socket to close by receiving a close message? – 6Willows Apr 28 '20 at 16:58

1 Answers1

0

Okay, so I figured out the blocker was the socket.accept() function. So for anyone who may have the same issue with terminating server threads, you can just use a sock.select() before your sock.accept() to check if there are any incoming connections. If you use a sock.select() and add a timeout to it, the whole loop will run after the allotted time it waits for connections, so the thread can be killed if the event has told it to do so and if it hasn't, it will look for connections again.

You can use the thread event function (which stovfl mentioned in comments on the main thread) to tell the thread when to stop.

Here is how I changed my code so it can now self terminate:

def run(self, running):
        while running.is_set(): 
            timeout = 2
            readable, writable, errored = select.select([self.sock], [], [], timeout)
            for s in readable:
                if s is self.sock:
                    client_socket, a = self.sock.accept()  ##c is client and a is address
                    cThread = threading.Thread(target=self.handler, args=(client_socket, a))
                    cThread.daemon = True
                    cThread.start()
                    self.connections.append(client_socket)
                    print("Server:    ", str(a[0]) + ':' + str(a[1]), "connected")

        self.close()

def serverRun(running, port):
    while running.is_set():
        print("server port: " + str(port))
        actual_server = Server(port)
        actual_server.run(running)

And main was changed to:

def main(): 
    running = threading.Event()
    running.set()

    thread = threading.Thread(target=server.serverRun, args=(running, 1))
    thread.start()

    time.sleep(30)
    print("Event running.clear()")
    running.clear()

    print('Wait until Thread is terminating')
    thread.join()
    print("EXIT __main__")

main()
6Willows
  • 43
  • 7