我需要创建一个client-server注册表系统。客户端应该输入其用户名和密码,服务器应确认。 GUI应在tkinter中制作。 问题是,在检查服务器的命令之后,我需要添加标签。 如果我对这是错误的理解,我非常抱歉,因为我习惯了pygame,其中一切都在1 wher(true)循环中起作用。

这是客户端代码。协议是一个不同的文件,其中包含某种命令检查。 服务器不是问题的一部分。确认客户端请求时,它将发送"registered"。

import socket
import sys
import tkinter as tk
from tkinter.simpledialog import askstring
from tkinter import *
from tkinter import messagebox
import time
from queue import Queue
import threading
import protocol


data_queue = Queue()  # queue where threads can send data from one to another
IP = "127.0.0.1"
PORT = 1234
BIG_BUFFER = 256
stop_event = threading.Event()


def packed(cmd):
    return cmd.encode()


def on_closing(top, client, data_thread):
    if messagebox.askokcancel("Quit", "Do you want to close the application?"):
        client.send(packed("exit"))
        stop_event.set()
        top.destroy()
        data_thread.join()
        client.close()


def receive_data_from_server(client):  # receives data from server through another thread
    while not stop_event.isSet():
        try:
            server_cmd = client.recv(BIG_BUFFER).decode()
            if server_cmd:
                print(server_cmd)
                if protocol.check_cmd(server_cmd):
                    data_queue.put(server_cmd)  # sends to main thread
                else:
                    print("invalid cmd: " + server_cmd)
        except Exception as err:
            print(err)


def password_err():
    print("h")


def register_user(client, username, password1, password2):
    if password1 == password2:
        client.send(packed("add-" + username + "-" + password1))
    else:
        password_err()


def success_reg(frame):
    frame.insert(tk.END, "Success!")
    frame.pack()


def main():
    try:
        client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        client.connect((IP, PORT))  # connect to server

    except Exception as error:
        print(error)  # could be an error connecting to server or establishing the socket

    top = tk.Tk()
    top.title("Registration")
    top.geometry("800x400")
    top.protocol("WM_DELETE_WINDOW", lambda: on_closing(top, client, data_thread))

    registration_frame = tk.Frame(top)
    registration_frame.pack()

    register_label = tk.Label(registration_frame, text="Registration")
    register_label.pack()

    username_label = tk.Label(registration_frame, text="Enter username:")
    username_label.pack()
    username_entry = tk.Entry(registration_frame)
    username_entry.pack()

    password_label = tk.Label(registration_frame, text="Enter password:")
    password_label.pack()
    password_entry = tk.Entry(registration_frame, show="*")  # "show" hides the password
    password_entry.pack()

    check_password_label = tk.Label(registration_frame, text="Confirm password:")
    check_password_label.pack()
    check_password_entry = tk.Entry(registration_frame, show="*")  # "show" hides the password
    check_password_entry.pack()

    # creates register button
    register_button = tk.Button(registration_frame, text="Register", command=lambda: register_user(
        client, username_entry.get(), password_entry.get(), check_password_entry.get()))
    register_button.pack()

    # data thread listening to server
    data_thread = threading.Thread(target=receive_data_from_server, args=(client,))
    data_thread.daemon = True
    data_thread.start()

    top.mainloop() 

    while not stop_event.isSet():
        if not data_queue.empty():
            server_cmd = data_queue.get()
            if server_cmd == "registered":
                success_reg(registration_frame) 


if __name__ == '__main__':
    main()

提前致谢。

分析解答

在您的main()功能中,您只需创建所有GUI元素,然后使用top.mainloop()启动GUI应用程序。没有用于接收和处理服务器消息的循环。而且它不能存在,因为在调用top.mainloop()之后,main()函数在该线路完全冻结。

为了使GUI对来自服务器的消息反应,通常您应该单独进行线程,在此中将在无限循环中读取队列。

main()功能中,您已经创建了线程data_thread。但是,在receive_data_from_server内部,而不是收集到data_queue的消息,您可以简单地为GUI编写逻辑以对消息做出反应。另外,您应该将GUI元素传递给receive_data_from_server,您想对消息做出反应(以使其可用于功能)。

这是快速示例应该如何工作的(我还通过重写代码,保持逻辑相同的几个缩进级别):

def receive_data_from_server(client, window):  # receives data from server through another thread
    while not stop_event.isSet():
        try:
            server_cmd = client.recv(BIG_BUFFER).decode()

            # If nothing recieved - go to next loop iteration
            if not server_cmd:
                continue
            # If recieved invalid command - go to next loop iteration
            if protocol.check_cmd(server_cmd):
                print("invalid cmd: " + server_cmd)
                continue

            # At this point server_cmd is validated
            # Now we can write logic to different server commands

            # If revieved "REGISTER" from server
            if server_cmd == "REGISTER":
                # Create new label
                new_label = tk.Label(window, text="You've successfully registered!")

            # If revieved "ANYTHING_ELSE" from server
            elif server_cmd == "ANYTHING_ELSE":
                # Create new label
                new_label = tk.Label(window, text="Something happened...")

            # If revieved "SOMETHING_ELSE" from server
            elif server_cmd == "SOMETHING_ELSE":
                # Create new label
                new_label = tk.Label(window, text="Something else happened...")

            # After new label is created, just pack it to GUI
            new_label.pack()

        except Exception as err:
            print(err)

        # A little sleep is good to prevent infinite loop from spamming        
        # You should import it: from time import sleep
        sleep(0.5)

之后,您应该更改data_thread创建过程,并通过应达到的GUI元素。在这里,我仅通过了应用程序的top窗口:

data_thread = threading.Thread(target=receive_data_from_server, args=(client, top))