Python SSL TCP Socket持续运行24小时后无响应问题求助
我编写的Python服务器脚本运行约24小时后,SSL TCP服务器会完全停止监听和响应,但进程仍处于运行状态。故障发生前服务器状态一切正常,排查日志未发现任何异常。
- 非SSL TCP Socket在单独线程运行,从未出现挂起情况,当主线程的SSL TCP Socket挂起时,非SSL线程仍能正常接收并响应客户端请求
- 两者共用同一个
listener()函数,推测问题可能与Python SSL wrapper有关,但无法确定
核心代码如下:
def ssl_server(): #logger("MAIN", "ssl_server", "SERVER", "") #_logger_("MAIN", "ssl_server", "SERVER", "") global run run = True non_ssl_thread = Thread(target = non_ssl_server, args = (server_config["enable-https"]["force-ssl"]["force"],server_config["enable-https"]["force-ssl"]["true-path"])) SERVER_HOST = server_config["server-host"] tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) try: if not(server_config["enable-https"]["enabled"]): raise Exception("HTTPS Disabled") SERVER_PORT = 443 context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) context.load_cert_chain(server_config["cert-location"]) tcp_socket.bind((SERVER_HOST, SERVER_PORT)) non_ssl_thread.daemon = True non_ssl_thread.start() tcp_socket.listen(server_config["queue-limit"]) ssl_tcp_socket = context.wrap_socket(tcp_socket, server_side=True) listener(ssl_tcp_socket) except: #logger("MAIN", "ssl_server", "SERVER", "EXCEPTION: " + traceback.format_exc().replace('"', '"').replace('<', "<").replace('>', ">").replace('\n', "<br>").replace(' ', " ")) run = False tcp_socket.close() non_ssl_server() ssl_tcp_socket.close() try: tcp_socket.close() except: #logger("MAIN", "ssl_server", "SERVER", "EXCEPTION: " + traceback.format_exc().replace('"', '"').replace('<', "<").replace('>', ">").replace('\n', "<br>").replace(' ', " ")) pass #END ssl_server() def listener(_socket_, redirect_to_ssl = False, true_path = False): #logger("MAIN", "listener", "SERVER", "") #_logger_("MAIN", "listener", "SERVER", "") global run while run: try: (client_connection, client_address) = _socket_.accept() client_request = client_connection.recv(server_config["socket-buffer-size"]).decode() server_response = initialized_handler.respond_to_request(client_request, client_address, redirect_to_ssl, true_path) client_connection.sendall(server_response) client_connection.close() except: #logger("MAIN", "listener", "SERVER", "EXCEPTION: " + traceback.format_exc().replace('"', '"').replace('<', "<").replace('>', ">").replace('\n', "<br>").replace(' ', " ")) pass #END listener()
可能的原因分析
SSL握手阶段阻塞:
SSL连接需要完成握手流程才能进入数据传输,若客户端发起连接后中途断开、恶意挂起或网络异常,可能导致accept()后的SSL握手在底层无限阻塞,直接卡住整个listener循环。而普通TCP连接accept()完成后即可处理数据,没有额外的握手环节,因此不会出现这类阻塞。未妥善处理SSL异常与资源泄漏:
当前代码的except块笼统捕获所有异常但仅注释了日志逻辑,未做任何连接清理或错误恢复。如果SSL相关异常(如握手超时、证书验证失败)导致客户端连接未被正常关闭,会逐渐耗尽Socket资源,最终导致服务器无法接受新连接。全局变量
run的线程安全问题:
多线程环境下直接读写全局变量run未加同步锁,可能导致主线程和非SSL线程对run的状态认知不一致,极端情况下可能让SSL的listener循环意外进入假死状态。
修复建议
1. 为SSL客户端连接添加超时限制
在accept()后立即设置客户端连接的超时时间,避免握手或数据接收阶段无限阻塞:
(client_connection, client_address) = _socket_.accept() # 设置10秒超时,可根据实际场景调整 client_connection.settimeout(10.0)
2. 细化异常处理,确保资源释放
拆分异常类型,针对性处理,同时用finally块确保无论是否出错都关闭客户端连接:
def listener(_socket_, redirect_to_ssl = False, true_path = False): global run while run: client_connection = None try: (client_connection, client_address) = _socket_.accept() client_connection.settimeout(10.0) client_request = client_connection.recv(server_config["socket-buffer-size"]).decode() server_response = initialized_handler.respond_to_request(client_request, client_address, redirect_to_ssl, true_path) client_connection.sendall(server_response) except ssl.SSLError as e: print(f"SSL错误来自 {client_address}: {str(e)}") except socket.timeout: print(f"连接超时来自 {client_address}") except Exception as e: print(f"通用错误: {str(e)}") finally: # 确保连接被关闭,避免资源泄漏 if client_connection: try: client_connection.close() except Exception: pass
3. 保障全局变量run的线程安全
使用threading.Lock同步对run的读写操作:
import threading # 初始化锁和全局变量 run_lock = threading.Lock() run = True # 在修改run时加锁 with run_lock: run = False # 在listener循环中检查run状态时加锁 while True: with run_lock: if not run: break # 处理连接逻辑
4. 优化SSLContext配置
确保SSL上下文启用了安全的TLS版本,避免因协议兼容性问题导致握手失败:
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) # 限制TLS版本范围,提高安全性和兼容性 context.minimum_version = ssl.TLSVersion.TLS_1_2 context.maximum_version = ssl.TLSVersion.TLS_1_3 context.load_cert_chain(server_config["cert-location"])
5. 增加运行时监控
定期用系统工具(如netstat -an)查看服务器的连接状态,排查是否有大量未释放的连接;也可在代码中添加连接计数统计,便于定位资源泄漏问题。
内容的提问来源于stack exchange,提问作者user18215985

