点击按钮启动聊天GUI与服务器却遇Connection Refused错误求助
问题:点击登录按钮后客户端无法连接服务器(Errno 61)
需求是点击登录按钮后,从输入框获取IP和端口,启动服务器并打开聊天客户端,但客户端始终报Errno 61(连接被拒绝),单独运行服务器时客户端可正常连接。
核心错误原因
- 服务器启动阻塞主线程:
start_server()包含无限循环监听逻辑,直接在主线程调用会阻塞后续客户端代码执行,导致客户端尝试连接时服务器还未完成监听。 - 参数传递错误:
server_boot()中调用set_address_and_port(ip, port)时,未调用ip()和port()函数获取输入值,而是直接传入函数对象,导致服务器绑定无效地址。 - 聊天GUI接收逻辑冗余且错误:同时在子线程和自定义while循环中接收消息,且子线程接收后直接关闭socket,导致后续通信中断;自定义while循环替代
mainloop()会引发UI异常。 - 全局变量未初始化:服务器中
SERVER_ADDRESS和SERVER_PORT未提前初始化,可能导致绑定操作失败。
修正后的关键代码
1. 启动GUI代码修正
from pathlib import Path import customtkinter import threading import time from PIL import Image, ImageTk from CHAT_GUI_FUNCTIONS import chat_gui from server import start_server, set_address_and_port def server_boot(): # 获取输入框中的IP和端口值 server_ip = ip() server_port = port() # 设置服务器地址和端口 set_address_and_port(server_ip, server_port) # 将服务器启动放到子线程,避免阻塞主线程 server_thread = threading.Thread(target=start_server, daemon=True) server_thread.start() def ip(): return entry_ip.get().strip() or "127.0.0.1" # 默认本地地址 def port(): ep = entry_port.get().strip() try: return int(ep) except ValueError: return 1234 # 默认端口 def usernames(): return entry_name.get().strip() or "Anonymous" # 默认用户名 def chat_gui_button_wrapper(): server_boot() # 短暂延迟确保服务器完成监听(可选,更严谨的方式是监听服务器启动信号) time.sleep(0.5) chat_gui(ip(), port(), usernames()) # 以下GUI布局代码保持不变 customtkinter.set_appearance_mode('light') root = customtkinter.CTk() root.geometry('300x500') root.title('Sign On') root.columnconfigure(0, weight=1) root.columnconfigure(1, weight=1) root.columnconfigure(2, weight=1) root.rowconfigure(0, weight=1) root.rowconfigure(1, weight=1) root.rowconfigure(2, weight=1) root.rowconfigure(3, weight=1) root.rowconfigure(4, weight=1) root.rowconfigure(5, weight=2) root.rowconfigure(6, weight=1) root.rowconfigure(7, weight=1) label = customtkinter.CTkLabel(root, text="COL Instant Messenger", font=("Times New Roman Pixel", 25)) canvas = customtkinter.CTkCanvas(root) directory = Path.home() im = Image.open(f"{directory}/PycharmProjects/Messaging-APP/image/colloginscreen.png").resize((300, 250)) logo = ImageTk.PhotoImage(im) canvas.create_image(150, 100, image=logo) canvas.grid(row=0, column=1, rowspan=3) entry_name = customtkinter.CTkEntry(master=root, placeholder_text="ScreenName...", font=("Times New Roman Pixel", 15), width=150) entry_name.grid(column=1, row=4) entry_ip = customtkinter.CTkEntry(master=root, placeholder_text="Enter IP...", font=("Times New Roman Pixel", 15), width=150) entry_ip.grid(column=1, row=5) entry_port = customtkinter.CTkEntry(master=root, placeholder_text="Enter Port...", font=("Times New Roman Pixel", 15), width=150) entry_port.grid(column=1, row=6) button = customtkinter.CTkButton(root, text='Sign On', fg_color="#d3d333", border_color="black", text_color="black", font=("Times New Roman Pixel", 15), command=chat_gui_button_wrapper) button.grid(column=1, row=7) root.mainloop()
2. 服务器代码修正
import socket import threading from threading import Lock import select # 提前初始化全局地址和端口变量 SERVER_ADDRESS = "" SERVER_PORT = 0 def set_address_and_port(ip, port): global SERVER_ADDRESS, SERVER_PORT SERVER_ADDRESS = ip SERVER_PORT = port return SERVER_ADDRESS, SERVER_PORT data_lock = Lock() connected_clients: list[(str, socket.socket, threading.Thread)] = [] def start_server(): server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: server_socket.bind((SERVER_ADDRESS, SERVER_PORT)) except socket.error as e: print(f"服务器绑定失败: {e}") return server_socket.listen(5) print(f"Server is listening on {SERVER_ADDRESS}:{SERVER_PORT}") while True: server_socket.settimeout(5.0) try: client_socket, client_address = server_socket.accept() except TimeoutError: pass else: print(f"Client connected: {client_address[0]}:{client_address[1]}") list_of_addresses = [addy for (addy, _, _) in connected_clients] if client_address[0] not in list_of_addresses: client_thread = threading.Thread(target=handle_client, args=(client_socket, client_address), daemon=True) connected_clients.append((client_address[0], client_socket, client_thread)) client_thread.start() else: for (addy, sock, thread) in connected_clients: if addy == client_address[0]: sock.close() connected_clients.remove((addy, sock, thread)) break # 清理已断开的客户端 with data_lock: connected_clients[:] = [item for item in connected_clients if item[2].is_alive()] def relay_message(client_address, message): print(f"Received from {client_address[0]}:{client_address[1]}: {message}") def send_to_one_client(user_sock): _, ready_write, in_error = select.select([], [user_sock], [user_sock]) if user_sock in ready_write and user_sock not in in_error: try: user_sock.send(message.encode('utf-8')) except socket.error as e: print(f"发送给客户端失败: {e}") with data_lock: for (_, user_socket, _) in connected_clients: send_to_one_client(user_socket) def handle_client(client_socket, client_address): while True: try: ready_read, _, in_error = select.select([client_socket], [], [client_socket]) if client_socket in in_error: print("客户端连接出错") break elif client_socket in ready_read: message = client_socket.recv(1024).decode('utf-8') if message: relay_message(client_address, message) else: print(f"客户端 {client_address[0]} 断开连接") break except select.error as e: print(f"Socket错误: {e}") break client_socket.close() if __name__ == '__main__': # 单独运行时手动设置地址和端口 set_address_and_port("127.0.0.1", 1234) start_server()
3. 聊天GUI代码修正
def chat_gui(ip, port, usernames): import customtkinter as ctk import socket import threading SERVER_ADDRESS = ip SERVER_PORT = port username = usernames # 初始化客户端socket并尝试连接 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: client_socket.connect((SERVER_ADDRESS, SERVER_PORT)) print("成功连接到服务器") except socket.error as e: print(f"连接服务器失败: {e}") return client_socket.setblocking(False) # 创建聊天窗口 chat = ctk.CTk() chat.geometry('900x600') chat.title('COL Instant Messenger') ctk.set_appearance_mode('light') # 绑定回车键发送消息 def handle_return_key(event): send_message() # 发送消息函数 def send_message(): message_text = message_entry.get().strip() if not message_text: return message = f'{username}: {message_text}' message_entry.delete(0, ctk.END) try: client_socket.send(message.encode('utf-8')) except socket.error as e: print(f"发送消息失败: {e}") # 窗口布局配置 chat.columnconfigure(0, weight=1) chat.columnconfigure(1, weight=1) chat.columnconfigure(2, weight=1) chat.columnconfigure(3, weight=0.5) chat.rowconfigure(0, weight=0) chat.rowconfigure(1, weight=1) chat.rowconfigure(2, weight=2) chat.rowconfigure(3, weight=3) chat.rowconfigure(4, weight=0) # 消息输入框 message_entry = ctk.CTkEntry(master=chat, placeholder_text="Message Here...", font=("Times New Roman Pixel", 15), width=300) message_entry.bind(sequence='<Return>', command=handle_return_key) message_entry.grid(row=4, column=2, columnspan=1, sticky="ew", pady=20, padx=10) # 发送按钮 send_button = ctk.CTkButton(chat, text="send", command=send_message, hover=True, width=100) send_button.grid(row=4, column=3, columnspan=1, padx=5, pady=5, sticky="ew") # 聊天内容框 textbox = ctk.CTkTextbox(chat) textbox.configure(font=("Times New Roman Pixel", 15)) textbox.grid(row=0, column=2, columnspan=2, rowspan=4, padx=20, pady=20, sticky='nsew') textbox.configure(state="disabled") # 设置为只读 # 客户端列表框 textbox_client_list = ctk.CTkTextbox(chat) textbox_client_list.configure(font=("Times New Roman Pixel", 15)) textbox_client_list.grid(row=1, column=0, columnspan=2, padx=20, pady=20, sticky='nsew') # 更新聊天内容的UI函数(必须在主线程执行) def update_chat_content(message): textbox.configure(state='normal') textbox.insert("end", f"{message}\n") textbox.configure(state="disabled") textbox.see("end") # 自动滚动到底部 # 接收消息线程 def receive_messages_loop(): while True: try: received_data = client_socket.recv(1024).decode('utf-8') if not received_data: print("服务器已断开连接") break # 通过after方法在主线程更新UI chat.after(0, update_chat_content, received_data) except BlockingIOError: # 无数据时的正常异常,跳过 continue except Exception as e: print(f"接收消息出错: {e}") break client_socket.close() chat.after(0, lambda: update_chat_content("服务器已断开连接")) # 启动接收线程 receive_thread = threading.Thread(target=receive_messages_loop, daemon=True) receive_thread.start() # 启动窗口主循环 chat.mainloop()
验证步骤
- 确保三个文件的导入路径正确(如
from CHAT_GUI_FUNCTIONS import chat_gui对应聊天GUI文件的位置)。 - 运行启动GUI程序,输入IP(如127.0.0.1)、端口(如1234)和用户名,点击登录按钮。
- 此时服务器会在后台子线程启动,聊天客户端窗口打开并成功连接服务器,可正常发送接收消息。
内容的提问来源于stack exchange,提问作者AlexanderWaller
相关产品推荐
相关产品推荐

