You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

点击按钮启动聊天GUI与服务器却遇Connection Refused错误求助

问题:点击登录按钮后客户端无法连接服务器(Errno 61)

需求是点击登录按钮后,从输入框获取IP和端口,启动服务器并打开聊天客户端,但客户端始终报Errno 61(连接被拒绝),单独运行服务器时客户端可正常连接。

核心错误原因

  1. 服务器启动阻塞主线程:start_server()包含无限循环监听逻辑,直接在主线程调用会阻塞后续客户端代码执行,导致客户端尝试连接时服务器还未完成监听。
  2. 参数传递错误:server_boot()中调用set_address_and_port(ip, port)时,未调用ip()和port()函数获取输入值,而是直接传入函数对象,导致服务器绑定无效地址。
  3. 聊天GUI接收逻辑冗余且错误:同时在子线程和自定义while循环中接收消息,且子线程接收后直接关闭socket,导致后续通信中断;自定义while循环替代mainloop()会引发UI异常。
  4. 全局变量未初始化:服务器中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()

验证步骤

  1. 确保三个文件的导入路径正确(如from CHAT_GUI_FUNCTIONS import chat_gui对应聊天GUI文件的位置)。
  2. 运行启动GUI程序,输入IP(如127.0.0.1)、端口(如1234)和用户名,点击登录按钮。
  3. 此时服务器会在后台子线程启动,聊天客户端窗口打开并成功连接服务器,可正常发送接收消息。

内容的提问来源于stack exchange,提问作者AlexanderWaller

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 22:49:49