[Python] GUI "Stop Clients" button doesn't close server and clients in multithreaded Python...

Discussão em 'Python' iniciado por Stack, Maio 13, 2025.

  1. Stack

    Stack Membro Participativo

    I have a client-server project where the server collects Mandelbrot images computed by different clients using multithreading.

    It contains 4 files, and files 1 and 2 must be run in different consoles. (The interface is very simple and easy to use.)

    However, when I try to quit by clicking the red "STOP CLIENTS" button (on the server interface), the windows won't close (the only way to exit is to force-close the console for now...).

    Apparently, if you use PyCharm this problem doesn't happen, but our project is supposed to work on Spyder.

    IN SHORT: Without changing the current code too much — ideally by adding or modifying a few lines — how could we fix this issue (that the server and clients do not close when the red "STOP CLIENTS" button is clicked in the server interface)?

    Thanks in advance for your thoughts!

    FILE NUMBER 1

    import Server
    import tkinter as tk

    class Window(tk.Tk):
    def __init__(self):
    super().__init__()

    for i in range(6):
    self.columnconfigure(i, weight=1)
    for i in range(7):
    self.rowconfigure(i, weight=1)

    self.referencenbpixels = 400
    self.title('Mandelbrot Set')
    self.pixels = [[f'#FFFFFF'] * self.referencenbpixels] * self.referencenbpixels
    self.server = Server.Server(self.referencenbpixels)

    self.calculatebutton = tk.Button(self, text='Calculate', command=self.set_calculations, font=('Calibri', 18), bg='lawn green')
    self.calculatebutton.grid(row=6, column=0, columnspan=3, sticky='news')

    self.stopbutton = tk.Button(self, text='Stop Clients', command=self.end, font=('Calibri', 18), bg='red3')
    self.stopbutton.grid(row=6, column=3, columnspan=3, sticky='news')

    self.xcentervalue = tk.StringVar(value='-1')
    self.xcenterlabel = tk.Label(self, text='XCenter', borderwidth=0, relief="solid", font=('Calibri', 18),bg='ivory2')
    self.xcenterlabel.grid(row=2, column=0, columnspan=2, sticky="news", padx=10, pady=10)
    self.xcenter = tk.Entry(self, font=('Calibri', 15), textvariable=self.xcentervalue)
    self.xcenter.grid(row=3, column=0, columnspan=2, sticky="news", padx=10)

    self.ycentervalue = tk.StringVar(value='0')
    self.ycenterlabel = tk.Label(self, text='YCenter', borderwidth=0, relief="solid", font=('Calibri', 18), bg='ivory2')
    self.ycenterlabel.grid(row=2, column=2, columnspan=2, sticky="news", padx=10, pady=10)
    self.ycenter = tk.Entry(self, font=('Calibri', 15), textvariable=self.ycentervalue)
    self.ycenter.grid(row=3, column=2, columnspan=2, sticky="news", padx=10)

    self.zoomvalue = tk.StringVar(value='0')
    self.zoomlabel = tk.Label(self, text='Zoom', borderwidth=0, relief="solid", font=('Calibri', 18), bg='ivory2')
    self.zoomlabel.grid(row=2, column=4, columnspan=2, sticky="news", padx=10, pady=10)
    self.zoom = tk.Entry(self, font=('Calibri', 15), textvariable=self.zoomvalue)
    self.zoom.grid(row=3, column=4, columnspan=2, sticky="news", padx=10)

    self.xpixelsvalue = tk.StringVar(value='600')
    self.xpixelslabel = tk.Label(self, text='XPixels', borderwidth=0, relief="solid", font=('Calibri', 18), bg='ivory2')
    self.xpixelslabel.grid(row=4, column=0, columnspan=2, sticky="news", padx=10, pady=10)
    self.xpixels = tk.Entry(self, font=('Calibri', 15), textvariable=self.xpixelsvalue)
    self.xpixels.grid(row=5, column=0, columnspan=2, sticky="news", padx=10)

    self.ypixelsvalue = tk.StringVar(value='600')
    self.ypixelslabel = tk.Label(self, text='YPixels', borderwidth=0, relief="solid", font=('Calibri', 18), bg='ivory2')
    self.ypixelslabel.grid(row=4, column=2, columnspan=2, sticky="news", padx=10, pady=10)
    self.ypixels = tk.Entry(self, font=('Calibri', 15), textvariable=self.ypixelsvalue)
    self.ypixels.grid(row=5, column=2, columnspan=2, sticky="news", padx=10)

    self.maxitervalue = tk.StringVar(value='200')
    self.maxiterlabel = tk.Label(self, text='Max Iter', borderwidth=0, relief="solid", font=('Calibri', 18), bg='ivory2')
    self.maxiterlabel.grid(row=4, column=4, columnspan=2, sticky="news", padx=10, pady=10)
    self.maxiter = tk.Entry(self, font=('Calibri', 15), textvariable=self.maxitervalue)
    self.maxiter.grid(row=5, column=4, columnspan=2, sticky="news", padx=10)

    def set_calculations(self):
    zoom = float(self.zoomvalue.get())
    xcenter = float(self.xcentervalue.get())
    ycenter = -float(self.ycentervalue.get())
    xpixels = int(self.xpixelsvalue.get())
    ypixels = int(self.ypixelsvalue.get())
    maxiter = int(self.maxitervalue.get())
    self.width, self.height = xpixels, ypixels

    self.pixels = self.server.send_calculations(xcenter, ycenter, zoom, xpixels, ypixels, maxiter)
    self.construction()

    def construction(self):
    self.image = tk.PhotoImage(width=self.width, height=self.height)
    self.image.put(self.pixels)
    imagelabel = tk.Label(self, image=self.image)
    imagelabel.grid(row=0, column=0, columnspan=6)

    def end(self):
    self.server.Stopall()

    def quitting(self):
    self.server.Stopall()
    self.quit()

    fenetre = Window()
    fenetre.protocol("WM_DELETE_WINDOW", fenetre.quitting)
    fenetre.mainloop()
    fenetre.end()


    FILE NUMBER 2

    import tkinter as tk
    import tkinter as tk
    import threading
    import Client

    class Window(tk.Tk):
    def __init__(self):
    super().__init__()
    self.padx, self.pady = 10, 10
    self.client = None
    self.title('SLAVE DO YOUR WORK')

    self.IPEntry = tk.Entry(font=('Calibri', 13))
    self.IPEntry.grid(row=1, column=0, sticky="news", padx=self.padx, pady=(0, self.pady))
    self.IPEntry.insert(0, '127.0.0.1')
    self.IPLabel = tk.Label(self, text='IP Adress', font=('Calibri', 13))
    self.IPLabel.grid(row=0, column=0, sticky="news", padx=self.padx, pady=self.pady)

    self.PortEntry = tk.Entry(font=('Calibri', 13))
    self.PortEntry.grid(row=1, column=1, sticky="news", padx=self.padx, pady=(0, self.pady))
    self.PortEntry.insert(0, '50000')
    self.PortLabel = tk.Label(self, text='Port', font=('Calibri', 13))
    self.PortLabel.grid(row=0, column=1, sticky="news", padx=self.padx, pady=self.pady)

    self.connect = tk.Button(self, text='Connect', bg='lawn green', command=self.Connecting, font=('Calibri', 13))
    self.connect.grid(row=2, column=0, columnspan=2, sticky="news", padx=self.padx, pady=self.pady)

    def Connecting(self):
    ip = self.IPEntry.get()
    port = int(self.PortEntry.get())
    self.client = Client.Client(ip, port, (self, self.padx, self.pady))
    self.thread = threading.Thread(None, self.client.general, None, (), {}, daemon=True)
    self.thread.start()

    def quitting(self):
    if self.client:
    self.client.quitting()
    self.quit()

    def general():
    win = Window()
    win.protocol("WM_DELETE_WINDOW", win.quitting)
    win.mainloop()
    if __name__ == '__main__':
    general()


    FILE NUMBER 3

    import socket
    import threading
    import time

    class Server:
    def __init__(self, referencenbpixels):
    self.serveur = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    self.serveur.bind(('', 50000)) # Écoute sur le port 50000
    self.serveur.listen(5)
    self.threadsClients = []
    self.pixelsdict = {}
    self.nbclients = 0
    self.referencenbpixels = referencenbpixels
    self.clientlist = []
    self.stop = False


    threadlisten = threading.Thread(None, self.listen, None, (), {}, daemon=True)
    threadlisten.start()

    def listen(self):
    print('Listening')
    while not self.stop:
    pass
    client, infosClient = self.serveur.accept()
    client.send('Connexion réussie'.encode("utf-8"))
    self.clientlist.append(client)
    self.threadsClients.append(threading.Thread(None, self.instanceServeur, None, (client, infosClient), {}))
    self.nbclients += 1
    self.threadsClients[-1].start()
    self.serveur.close()

    def instanceServeur(self, client, infosClient):
    run = True
    adresseIP = infosClient[0]
    port = str(infosClient[1])
    print("Instance de serveur prêt pour " + adresseIP + ":" + port)
    while not self.stop and run:
    message = client.recv(100).decode("utf-8")
    if message == 'Résultat':
    le = int(client.recv(500).decode("utf-8"))
    re = client.recv(le)
    re = list(re)
    for key, value in self.pixelsdict.items():
    if value == client:
    self.pixelsdict[key] = re
    elif message == 'Quitte':
    self.clientlist.remove(client)
    self.nbclients -= 1
    client.close()
    run = False
    else:
    print(f'Got an unexpected message : {message}')

    print("Connexion fermée avec " + adresseIP + ":" + port)
    client.close()

    def send_calculations(self, xcenter, ycenter, zoom, xpixels, ypixels, maxiter):

    self.width = xpixels
    self.height = ypixels

    xmin = xcenter - xpixels / self.referencenbpixels / 2 ** zoom
    xmax = xcenter + xpixels / self.referencenbpixels / 2 ** zoom
    ymin = ycenter - ypixels / self.referencenbpixels / 2 ** zoom
    ymax = ycenter + ypixels / self.referencenbpixels / 2 ** zoom

    to_send = []
    step = ypixels / self.nbclients
    for nb, client in enumerate(self.clientlist):
    self.pixelsdict[nb] = client
    ymin_send = round(nb * step) * (ymax - ymin) / ypixels + ymin
    ymax_send = round((nb+1) * step) * (ymax - ymin) / ypixels + ymin
    to_send.append(f'{xmin}${xmax}${ymin_send}${ymax_send}${0}${0}${xpixels}${maxiter}')
    client.send('Calcule'.encode("utf-8"))

    time.sleep(0.5)
    for nb, client in enumerate(self.clientlist):
    client.send(to_send[nb].encode("utf-8"))

    return self.get_calculations()

    def check_responses(self):
    for i in range(self.nbclients):
    if type(self.pixelsdict) != list:
    return False
    return True

    def get_calculations(self):
    while not self.check_responses():
    pass
    data = []
    for i in range(self.nbclients):
    data = data + self.pixelsdict

    tup = []
    for i in range(len(data)//3):
    d = (data[3*i], data[3*i+1], data[3*i+2])
    tup.append(d)

    pixels = []
    for i in range(self.height):
    pixels.append(tup[i * self.width:(i+1) * self.width])
    for i in pixels:
    for j in range(len(i)):
    i[j] = change_color(i[j])
    return pixels

    def Stopall(self):
    for client in self.clientlist:
    client.send('FermeClient'.encode("utf-8"))


    def change_color(color):
    R, G, B = color
    return f"#{R:02x}{G:02x}{B:02x}"


    FILE NUMBER 4

    import socket
    import math as ma
    import time
    import tkinter as tk
    import multiprocessing

    class Client:
    palette = (
    (66, 30, 15),
    (25, 7, 26),
    (9, 1, 47),
    (4, 4, 73),
    (0, 7, 100),
    (12, 44, 138),
    (24, 82, 177),
    (57, 125, 209),
    (134, 181, 229),
    (211, 236, 248),
    (241, 233, 191),
    (248, 201, 95),
    (255, 170, 0),
    (204, 128, 0),
    (153, 87, 0),
    (106, 52, 3)
    )
    def __init__(self, ip="127.0.0.1", p=50000, window=None):
    self.ip, self.port, self.window = ip, p, window

    def Mandelbrot(self, client):
    message = client.recv(255).decode("utf-8").split('$')
    print(message)
    xmin, xmax, ymin, ymax, zoreal, zoim, xpoints, nbiter = map(float, message)
    xpoints = int(xpoints)
    ypoints = round(xpoints * (ymax - ymin) / (xmax - xmin))

    pixels = []
    args = [((xpoints, i / ypoints * (ymax - ymin) + ymin, xmin, xmax, nbiter), ) for i in range(ypoints)]
    pool = multiprocessing.Pool(processes=multiprocessing.cpu_count())
    for result in pool.starmap(calculate_row, args):
    pixels = pixels + result
    return pixels

    def quitting(self):
    self.cli.send('Quitte'.encode("utf-8"))


    def general(self):
    adresseIP = self.ip # Ici, le poste local
    port = self.port # Se connecter sur le port 50000

    self.cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    self.cli.connect((adresseIP, port))
    reponse = self.cli.recv(100).decode("utf-8")
    print(reponse)

    if self.window:
    connected = tk.Label(self.window[0], text='Connected', fg='green')
    connected.grid(row=2, column=0, columnspan=2, sticky="news", padx=self.window[1], pady=self.window[2])

    ip = tk.Label(self.window[0], text=self.window[0].IPEntry.get())
    ip.grid(row=1, column=0, sticky="news", padx=self.window[1], pady=(0, self.window[2]))

    po = tk.Label(self.window[0], text=self.window[0].PortEntry.get())
    po.grid(row=1, column=1, sticky="news", padx=self.window[1], pady=(0, self.window[2]))
    run = True
    while run:

    message = self.cli.recv(255).decode("utf-8")
    if message == "FermeClient":
    self.quitting()
    run = False
    elif message == 'Calcule':
    print('Calculating')
    pixels = self.Mandelbrot(self.cli)
    for i in range(len(pixels)):
    pixels = int(pixels)
    to_send = bytes(pixels)
    print('Done Calculating')
    self.cli.send('Résultat'.encode("utf-8"))
    time.sleep(0.5)
    self.cli.send(f'{len(to_send)}'.encode("utf-8"))
    time.sleep(0.5)
    self.cli.send(to_send)
    print('Infos Sent')
    else:
    print(f'We got an unexpected message : {message}')
    self.cli.close()
    self.window[0].quit()

    def calculate_row(args):
    width, yi, xmin, xmax, max_iterations = args
    pixels = []
    for i in range(width):
    xi = xmin + (xmax - xmin) * i / width
    x, y = 0, 0
    nb = 0
    while x * x + y * y <= 16 and nb < max_iterations:
    nb += 1
    x, y = x * x - y * y + xi, 2 * x * y + yi
    if nb == max_iterations:
    pixels = pixels + [0, 0, 0]
    else:
    color_val = (nb + 1 - ma.log(ma.log(x * x + y * y) / ma.log(4)) / ma.log(2)) / 3
    c2_index = ma.ceil(color_val) % len(Client.palette)
    t = color_val - ma.floor(color_val)

    c1 = Client.palette[c2_index - 1]
    c2 = Client.palette[c2_index]

    R = ma.floor(c1[0] + t * (c2[0] - c1[0]))
    G = ma.floor(c1[1] + t * (c2[1] - c1[1]))
    B = ma.floor(c1[2] + t * (c2[2] - c1[2]))

    pixels.append(R)
    pixels.append(G)
    pixels.append(B)
    return pixels

    Continue reading...

Compartilhe esta Página