Tkinter窗口中http.server线程启动/终止时程序冻结问题求助
Tkinter代理服务器终止时程序冻结问题修复
问题说明
在Tkinter应用中,配置IP为127.0.0.1、端口8080的代理服务器可正常启动,但点击终止按钮时程序会发生冻结。当前仅需完善代理的终止逻辑,暂未测试代理转发功能。使用threading.Event作为终止信号,通过httpd.handle_request()循环处理请求,预期实现:点击启动按钮时代理正常运行,点击终止按钮时程序无冻结地停止代理。
原代码
import tkinter as tk import http.server import socketserver import urllib.request import threading # Global stop event to signal the server thread stop_event = threading.Event() httpd_instance = None class ProxyHandler(http.server.BaseHTTPRequestHandler): def do_GET(self): target_url = self.path print(f"Forwarding GET request to: {target_url}") try: with urllib.request.urlopen(target_url) as response: self.send_response(response.status) for key, value in response.getheaders(): # TODO(martin-montas) Take this get headers functio # and display it in the Text area of the repeaters self.send_header(key, value) self.end_headers() self.wfile.write(response.read()) except Exception as e: self.send_error(500, f"Error forwarding request: {e}") def do_POST(self): target_url = self.path print(f"Forwarding POST request to: {target_url}") try: content_length = int(self.headers.get("Content-Length", 0)) body = self.rfile.read(content_length) req = urllib.request.Request(target_url, data=body, method="POST") for key in self.headers: req.add_header(key, self.headers[key]) with urllib.request.urlopen(req) as response: self.send_response(response.status) for key, value in response.getheaders(): # TODO(martin-montas) Take this get headers # and display it in the Text area of the repeaters self.send_header(key, value) self.end_headers() self.wfile.write(response.read()) except Exception as e: self.send_error(500, f"Error forwarding request: {e}") def run_proxy_server(proxy_ip="127.0.0.1", proxy_port=8080): global httpd_instance global stop_event with socketserver.TCPServer((proxy_ip, proxy_port), ProxyHandler) as httpd: httpd_instance = httpd print(f"Serving proxy at {proxy_ip}:{proxy_port}") try: while not stop_event.is_set(): httpd.handle_request() # Process one request at a time except Exception as e: print(f"Server stopped: {e}") finally: httpd_instance = None def stop_proxy_server(): global httpd_instance global stop_event if httpd_instance: print("Stopping the proxy server...") stop_event.set() # Signal the thread to stop httpd_instance.shutdown() httpd_instance = None print("[-]") else: print("Proxy server is not running.") class SettingsLayout: def __init__(self, root) -> None: self.root = root tk.Label(self.root, text="Proxy IP").grid(row=0, column=0, padx=10, pady=10) self.proxy_ip_entry = tk.Entry(self.root) self.proxy_ip_entry.grid(row=1, column=0, padx=10, pady=10) tk.Label(self.root, text="Proxy port").grid(row=2, column=0, padx=10, pady=10) self.proxy_port_entry = tk.Entry(self.root) self.proxy_port_entry.grid(row=3, column=0, padx=10, pady=10) tk.Button( self.root, text="Set Proxy", bg="orange", command=self.button_command ).grid(row=4, column=0, padx=10, pady=10) tk.Button( self.root, text="Disable Proxy", bg="orange", command=stop_proxy_server, ).grid(row=5, column=0, padx=10, pady=10) def button_command(self): ip = self.proxy_ip_entry.get() port = self.proxy_port_entry.get() try: # Validate port input port = int(port) print(f"Starting proxy at IP: {ip}, Port: {port}") # Run the proxy server in a separate thread threading.Thread( target=run_proxy_server, args=(ip, port), daemon=True ).start() except ValueError: print("Invalid port number. Please enter a valid integer.")
问题原因
httpd.shutdown()会阻塞调用它的线程(即Tkinter主线程),直到服务器的serve_forever()循环退出,但当前代码使用的是handle_request()循环,shutdown()无法正确触发退出逻辑,导致主线程冻结。handle_request()会一直阻塞等待新请求,stop_event.set()无法中断这个阻塞状态,导致循环无法及时检测到终止信号。
修复方案
修改后的核心代码
调整run_proxy_server函数
def run_proxy_server(proxy_ip="127.0.0.1", proxy_port=8080): global httpd_instance global stop_event stop_event.clear() # 重置终止信号 with socketserver.TCPServer((proxy_ip, proxy_port), ProxyHandler) as httpd: httpd_instance = httpd httpd.timeout = 1 # 设置超时,让handle_request定期返回 print(f"Serving proxy at {proxy_ip}:{proxy_port}") try: while not stop_event.is_set(): httpd.handle_request() # 超时后自动返回,检查终止信号 except Exception as e: print(f"Server stopped: {e}") finally: httpd_instance = None print("Proxy server stopped.")
调整stop_proxy_server函数
def stop_proxy_server(): global httpd_instance global stop_event if httpd_instance: print("Stopping the proxy server...") stop_event.set() # 触发终止信号 # 主动连接服务器,打破handle_request的阻塞状态 try: urllib.request.urlopen( f"http://{httpd_instance.server_address[0]}:{httpd_instance.server_address[1]}/__stop__", timeout=1 ) except: pass # 忽略连接错误,仅用于触发请求 httpd_instance = None else: print("Proxy server is not running.")
关键改动说明
- 给
TCPServer设置timeout=1,让handle_request()最多阻塞1秒就返回,确保循环能定期检查stop_event。 - 在终止时主动连接服务器,触发一次无效请求,让
handle_request()立即返回,无需等待超时,加快终止速度。 - 移除
httpd.shutdown(),改用stop_event+超时机制控制循环退出,避免主线程被阻塞。
内容的提问来源于stack exchange,提问作者Martin Montas
相关产品推荐
相关产品推荐

