Tkinter - многопоточность / многопроцессорность - какая из них лучше подходит для этого кода? - PullRequest
0 голосов
/ 06 апреля 2020

Я вроде как новичок ie здесь, так что извините, если я не понимаю свою точку зрения так, как должно быть.

Я пытаюсь улучшить сервер, который несколько месяцев писал go в сокетах с Tkinter. Я использовал шаблон, который я нашел на inte rnet. Кажется, все работает гладко и нормально, пока я не дохожу до части server.listening (). В этот момент Tkinter всегда будет зависать. Я знаю, что это как-то связано с многопоточностью, многопроцессорностью и тем фактом, что этот поток находится вне основного l oop, но пока не могу найти никакого решения.

Сервер действительно работает, хотя Tkinter зависает каждый раз, когда я нажимаю кнопку «прослушивания». Итак, вот моя точка зрения. Не имеет значения, полностью ли он замораживает Tkinter на пару секунд, так как этот сервер не сможет многое сделать, пока к нему не подключится хотя бы один человек. Я только хочу, чтобы программа могла активировать «прослушивание», когда нажата соответствующая кнопка, и деактивировать ее, когда она мне нужна.

Не могли бы вы пролить свет на мои сомнения? Я как 2 дня постоянно боролся с этим и только смог добиться этого. Я оставлю полный код в конце этого поста, на всякий случай, если вы считаете необходимым запустить его в первую очередь. Итак, вот код, некоторые части которого удалены для простоты:

from socket import AF_INET, socket, SOCK_STREAM
from threading import Thread
from datetime import date
from tkinter import messagebox
from tkinter import *
from tkinter import ttk, font
from multiprocessing import Process


import getpass
import os
import time

global msgs
global opt
global ADDR
global MK1



def broadcast(msg, prefix=""):  # prefix is for name identification.
    """Broadcasts a message to all the clients."""

    for sock in clients:

        sock.send(bytes(prefix, "utf8")+msg)


class threads():



    def accept_incoming_connections():
        """Sets up handling for incoming clients."""

        while True:
            global client_address
            client, client_address = SERVER.accept()
            client.send(bytes("Introduce un nombre y pulsa enter ", "utf8"))

            client.send(bytes("Asegurate de borrar 'Escribe...' antes de enviar tu nombre", "utf8"))
            addresses[client] = client_address
            Thread(target=threads.handle_client, args=(client,)).start()




    def handle_client(client):  # Takes client socket as argument.
        """Handles a single client connection."""
        name = client.recv(BUFSIZ).decode("utf8")
        welcome = 'Bienvenido %s! Pulsa el botón "Salir" para salir. No pulses X.' % name
        client.send(bytes(welcome, "utf8"))
        msg = "%s se ha unido al chat!" % name
        broadcast(bytes(msg, "utf8"))
        clients[client] = name

        while True:
            msg = client.recv(BUFSIZ)
            if msg != bytes("{quit}", "utf8"):
                broadcast(msg, name+": ")

            else:
                client.send(bytes("{quit}", "utf8"))
                client.close()
                del clients[client]
                broadcast(bytes("%s se ha ido del chat." % name, "utf8"))
                break




class IP():

    def __init__(self):
        # Tkinter part for defining the IP of the server. This works smoothly.

        global ip1
        ip1 = 1

    def aceptar(self):
        global HOST
        global PORT
        HOST = self.dip.get() 
        PORT = self.port.get()
        if not PORT:
            PORT = 33000

        PORT = int(PORT)
        self.raiz.destroy()

        global host1
        host1 = 1


        global ADDR
        ADDR = (HOST, PORT)

        global SERVER
        SERVER = socket(AF_INET, SOCK_STREAM)
        SERVER.bind(ADDR)



    def borrar_mensa(self, evento):
        self.dip.set("")
        self.port.set("")



class main ():

        def __init__(self):



            def send(event=None):  # event is passed by binders.
                """Handles sending of messages."""
                msg1 = my_msg.get()
                my_msg.set("")  # Clears input field.
                msg_list.insert(tkinter.END, msg1)
                if msg1 == "{quit}":
                    client_socket.close()
                    top.quit()


            def on_closing(event=None):
                """This function is to be called when the window is closed."""
                my_msg.set("{quit}")
                send()


            top = tkinter.Tk()
            top.title("Servidor - Aristoi.................. V. 1.0")
            top.geometry("650x450")

            messages_frame = tkinter.Frame(top)
            my_msg = tkinter.StringVar()  # For the messages to be sent.
            my_msg.set("Introduzca función...")
            scrollbar = tkinter.Scrollbar(messages_frame)  # To navigate through past messages.

            # Following will contain the messages.
            global msg_list
            msg_list = tkinter.Listbox(messages_frame, height=23, width=95, yscrollcommand=scrollbar.set)

            #  Following will contain buttons functions.
            def SERVERclose():
                SERVER.close()

            def EXIT():
                top.destroy()

            def def_IP():
                if ip1 is None:
                    IP()
                else:
                    tkinter.messagebox.showerror(title="Error", message="IP ya definida. Reinicie")

            def listening():
                if host1 is None:
                    tkinter.messagebox.showerror(title="Error", message="No ha definido una IP para el servidor")

                else:
                    SERVER.listen(5)
                    INPUT = Thread(target=threads.accept_incoming_connections)
                    INPUT.start()
                    tkinter.messagebox.showinfo(title="Conectando", message="Esperando conexiones entrantes")
                    INPUT.join()
                    SERVER.close()
                    global host2
                    host2 = 1                             


            # BUTTONS
            separl = ttk.Separator(top, orient=HORIZONTAL)
            entry_field = tkinter.Entry(top, textvariable=my_msg, width = 95)
            entry_field.bind("<Return>", send)
            send_button = tkinter.Button(top, text="    Enviar    ", fg="black", command=send)
            salir = tkinter.Button(top, text="    Salir    ",bg="black", fg="white", command=EXIT)
            host = tkinter.Button(top, text="    IP    ",bg="black", fg="white", command=def_IP)
            connect = tkinter.Button(top, text="    Conectar    ",bg="black", fg="white", command=listening)
            servcls = tkinter.Button(top, text="    ServerCLose    ",bg="black", fg="white", command=SERVERclose)

            #STETICS

            scrollbar.pack(side=tkinter.RIGHT, fill=tkinter.Y)
            msg_list.pack(side=tkinter.LEFT, fill=tkinter.BOTH)
            messages_frame.pack()
            entry_field.pack()
            send_button.pack()
            entry_field.place(x=29, y=380)
            send_button.place(x=29, y=410)
            salir.place(x=580, y=410)
            host.place(x=480, y = 410)
            connect.place(x=380, y = 410)
            servcls.place(x=280, y = 410)

            top.protocol("WM_DELETE_WINDOW", on_closing)
            top.mainloop()





clients = {}
addresses = {}

PORT = 33000
BUFSIZ = 1024

opt = int(0)
MK1 = 1
global host1
global host2
global ip1

host1 = None
host2 = None
ip1 = None


if __name__ == "__main__":
    main()

Вот полная версия кода:

from socket import AF_INET, socket, SOCK_STREAM
from threading import Thread
from datetime import date
from tkinter import messagebox
from tkinter import *
from tkinter import ttk, font
from multiprocessing import Process


import tkinter
import getpass
import os
import time

global msgs
global opt

global ADDR
global MK1



def broadcast(msg, prefix=""):  # prefix is for name identification.
    """Broadcasts a message to all the clients."""

    for sock in clients:

        sock.send(bytes(prefix, "utf8")+msg)


class threads():



    def accept_incoming_connections():
        """Sets up handling for incoming clients."""

        while True:
            global client_address
            client, client_address = SERVER.accept()
            client.send(bytes("Introduce un nombre y pulsa enter ", "utf8"))

            client.send(bytes("Asegurate de borrar 'Escribe...' antes de enviar tu nombre", "utf8"))
            addresses[client] = client_address
            Thread(target=threads.handle_client, args=(client,)).start()




    def handle_client(client):  # Takes client socket as argument.
        """Handles a single client connection."""
        name = client.recv(BUFSIZ).decode("utf8")
        welcome = 'Bienvenido %s! Pulsa el botón "Salir" para salir. No pulses X.' % name
        client.send(bytes(welcome, "utf8"))
        msg = "%s se ha unido al chat!" % name
        broadcast(bytes(msg, "utf8"))
        clients[client] = name

        while True:
            msg = client.recv(BUFSIZ)
            if msg != bytes("{quit}", "utf8"):
                broadcast(msg, name+": ")

            else:
                client.send(bytes("{quit}", "utf8"))
                client.close()
                del clients[client]
                broadcast(bytes("%s se ha ido del chat." % name, "utf8"))
                break




class IP():

    def __init__(self):

        self.raiz = Tk()
        self.raiz.geometry("450x200")
        self.raiz.resizable(0,0)
        self.raiz.title("Ventana de direcciones")

        fuente = font.Font(weight="bold")

        self.etiq1 = ttk.Label(self.raiz, text="Dirección IP", font=fuente)
        self.etiq2 = ttk.Label(self.raiz, text="Puerto", font=fuente)

        self.dip = StringVar()
        self.port = StringVar()

        self.ctext1 = ttk.Entry(self.raiz, textvariable=self.dip , width=40)
        self.ctext2 = ttk.Entry(self.raiz, textvariable=self.port, width= 10)

        self.separ1 = ttk.Separator(self.raiz, orient=HORIZONTAL)

        self.boton1 = ttk.Button(self.raiz, text="Aceptar", command=self.aceptar)

        self.etiq1.place(x=30, y=40)
        self.etiq2.place(x=30, y=80)

        self.ctext1.place(x=150, y=42)
        self.ctext2.place(x=150, y=82)
        self.separ1.place(x=5, y=145, bordermode=OUTSIDE,height=10, width=420)

        self.boton1.place(x=170, y=160)

        self.ctext1.focus_set()

        self.ctext1.bind("<Button-1>", self.borrar_mensa)
        self.ctext2.bind("<Button-1>", self.borrar_mensa)
        global ip1
        ip1 = 1

    def aceptar(self):
        global HOST
        global PORT
        HOST = self.dip.get() 
        PORT = self.port.get()
        if not PORT:
            PORT = 33000

        PORT = int(PORT)
        self.raiz.destroy()

        global host1
        host1 = 1


        global ADDR
        ADDR = (HOST, PORT)

        global SERVER
        SERVER = socket(AF_INET, SOCK_STREAM)
        SERVER.bind(ADDR)



    def borrar_mensa(self, evento):
        self.dip.set("")
        self.port.set("")



class main ():

        def __init__(self):


        # accept_incoming_connections ---------------------------------------------------------------


            def host_log(self):
                msgs = "%s:%s se ha conectado a las " + current_time + " del día " + today
                msg_list.insert(tkinter.END, msgs)



        #  --------------------------------------------------------------------------------------------



        #Parte del Tkinter
        #-----------------------------------------


            def send(event=None):  # event is passed by binders.
                """Handles sending of messages."""
                msg1 = my_msg.get()
                my_msg.set("")  # Clears input field.
                msg_list.insert(tkinter.END, msg1)
                if msg1 == "{quit}":
                    client_socket.close()
                    top.quit()


            def on_closing(event=None):
                """This function is to be called when the window is closed."""
                my_msg.set("{quit}")
                send()


            top = tkinter.Tk()
            top.title("Servidor - Aristoi.................. V. 1.0")
            top.geometry("650x450")

            messages_frame = tkinter.Frame(top)
            my_msg = tkinter.StringVar()  # For the messages to be sent.
            my_msg.set("Introduzca función...")
            scrollbar = tkinter.Scrollbar(messages_frame)  # To navigate through past messages.

            # Following will contain the messages.
            global msg_list
            msg_list = tkinter.Listbox(messages_frame, height=23, width=95, yscrollcommand=scrollbar.set)

            def SERVERclose():
                SERVER.close()

            def EXIT():
                top.destroy()

            def def_IP():
                if ip1 is None:
                    IP()
                else:
                    tkinter.messagebox.showerror(title="Error", message="IP ya definida. Reinicie")

            def listening():
                if host1 is None:
                    tkinter.messagebox.showerror(title="Error", message="No ha definido una IP para el servidor")

                else:
                    SERVER.listen(5)
                    INPUT = Thread(target=threads.accept_incoming_connections)
                    INPUT.start()
                    tkinter.messagebox.showinfo(title="Conectando", message="Esperando conexiones entrantes")
                    INPUT.join()
                    SERVER.close()
                    global host2
                    host2 = 1                             


            # BOTONES
            separl = ttk.Separator(top, orient=HORIZONTAL)
            entry_field = tkinter.Entry(top, textvariable=my_msg, width = 95)
            entry_field.bind("<Return>", send)
            send_button = tkinter.Button(top, text="    Enviar    ", fg="black", command=send)
            salir = tkinter.Button(top, text="    Salir    ",bg="black", fg="white", command=EXIT)
            host = tkinter.Button(top, text="    IP    ",bg="black", fg="white", command=def_IP)
            connect = tkinter.Button(top, text="    Conectar    ",bg="black", fg="white", command=listening)
            servcls = tkinter.Button(top, text="    ServerCLose    ",bg="black", fg="white", command=SERVERclose)

            #ESTETICA

            scrollbar.pack(side=tkinter.RIGHT, fill=tkinter.Y)
            msg_list.pack(side=tkinter.LEFT, fill=tkinter.BOTH)
            messages_frame.pack()
            entry_field.pack()
            send_button.pack()
            entry_field.place(x=29, y=380)
            send_button.place(x=29, y=410)
            salir.place(x=580, y=410)
            host.place(x=480, y = 410)
            connect.place(x=380, y = 410)
            servcls.place(x=280, y = 410)

            top.protocol("WM_DELETE_WINDOW", on_closing)
            top.mainloop()

        # AQUI




clients = {}
addresses = {}

PORT = 33000
BUFSIZ = 1024

opt = int(0)
MK1 = 1
global host1
global host2
global ip1

host1 = None
host2 = None
ip1 = None





t = time.localtime()
current_time = time.strftime("%H:%M:%S", t)
d1 = date.today()
today = d1.strftime("%d/%m/%Y")




if __name__ == "__main__":
    main()

1 Ответ

0 голосов
/ 15 апреля 2020

Для кого это может касаться

Я наконец нашел рабочее решение моей проблемы. Мне не нужно было переписывать свой код, я просто переупорядочил его по шаблону, найденному по этой ссылке . Кредиты Джейкобу Халлену. Я оставлю структуру кода ниже на тот случай, если вы захотите быстро ее оценить.

import Tkinter
import time
import threading
import random
import Queue

class GuiPart:
    def _ _init_ _(self, master, queue, endCommand):
        self.queue = queue
        # Set up the GUI
        console = Tkinter.Button(master, text='Done', command=endCommand)
        console.pack(  )
        # Add more GUI stuff here depending on your specific needs

    def processIncoming(self):
        """Handle all messages currently in the queue, if any."""
        while self.queue.qsize(  ):
            try:
                msg = self.queue.get(0)
                # Check contents of message and do whatever is needed. As a
                # simple test, print it (in real life, you would
                # suitably update the GUI's display in a richer fashion).
                print msg
            except Queue.Empty:
                # just on general principles, although we don't
                # expect this branch to be taken in this case
                pass

class ThreadedClient:
    """
    Launch the main part of the GUI and the worker thread. periodicCall and
    endApplication could reside in the GUI part, but putting them here
    means that you have all the thread controls in a single place.
    """
    def _ _init_ _(self, master):
        """
        Start the GUI and the asynchronous threads. We are in the main
        (original) thread of the application, which will later be used by
        the GUI as well. We spawn a new thread for the worker (I/O).
        """
        self.master = master

        # Create the queue
        self.queue = Queue.Queue(  )

        # Set up the GUI part
        self.gui = GuiPart(master, self.queue, self.endApplication)

        # Set up the thread to do asynchronous I/O
        # More threads can also be created and used, if necessary
        self.running = 1
        self.thread1 = threading.Thread(target=self.workerThread1)
        self.thread1.start(  )

        # Start the periodic call in the GUI to check if the queue contains
        # anything
        self.periodicCall(  )

    def periodicCall(self):
        """
        Check every 200 ms if there is something new in the queue.
        """
        self.gui.processIncoming(  )
        if not self.running:
            # This is the brutal stop of the system. You may want to do
            # some cleanup before actually shutting it down.
            import sys
            sys.exit(1)
        self.master.after(200, self.periodicCall)

    def workerThread1(self):
        """
        This is where we handle the asynchronous I/O. For example, it may be
        a 'select(  )'. One important thing to remember is that the thread has
        to yield control pretty regularly, by select or otherwise.
        """
        while self.running:
            # To simulate asynchronous I/O, we create a random number at
            # random intervals. Replace the following two lines with the real
            # thing.
            time.sleep(rand.random(  ) * 1.5)
            msg = rand.random(  )
            self.queue.put(msg)

    def endApplication(self):
        self.running = 0

rand = random.Random(  )
root = Tkinter.Tk(  )

client = ThreadedClient(root)
root.mainloop(  )
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...