# PHP 连接泄漏问题修复 ## 问题描述 Python 后端调用 PHP 后端接口时出现超时错误: ``` HTTPConnectionPool(host='192.168.1.164', port=30100): Read timed out. (read timeout=5) ``` ## 问题根源 通过 `netstat -ano | findstr :30100` 检查发现: - PHP 服务(PID 23736 和 1416)有 30+ 个 `CLOSE_WAIT` 连接 - `CLOSE_WAIT` 状态表示:客户端已关闭连接,但服务器端未关闭 - 这是典型的**连接泄漏**问题 ### 为什么会出现 CLOSE_WAIT? 1. **PHP 内置开发服务器的限制** - `php -S` 是单线程服务器,设计用于开发测试 - 在处理大量并发请求时容易出现连接泄漏 - 长时间运行会导致资源耗尽 2. **连接未正确关闭** - 客户端(Python)发送请求后关闭连接 - 服务器端(PHP)没有正确关闭 socket - 连接进入 CLOSE_WAIT 状态并一直保持 3. **资源耗尽** - 大量 CLOSE_WAIT 连接占用系统资源 - 导致新请求无法处理或响应缓慢 - 最终导致超时错误 --- ## 临时解决方案:重启 PHP 服务 ### 方法 1:使用快速重启脚本(推荐) 双击运行 `restart_php_service.bat`: ```batch restart_php_service.bat ``` 这个脚本会: 1. 检查当前 PHP 服务状态 2. 停止所有 PHP 服务进程 3. 等待端口释放 4. 启动新的 PHP 服务 ### 方法 2:手动重启 ```bash # 1. 查看当前 PHP 进程 netstat -ano | findstr :30100 # 2. 停止所有 PHP 进程(替换 PID) taskkill /F /PID 23736 taskkill /F /PID 1416 # 3. 等待 2 秒 # 4. 启动新的 PHP 服务 cd C:\Users\Administrator\Desktop\Project\AI_GirlFriend\xunifriend_RaeeC\public php -S 192.168.1.164:30100 ``` ### 验证服务已重启 ```bash # 检查服务状态 netstat -ano | findstr :30100 # 应该只看到 LISTENING 状态,没有 CLOSE_WAIT ``` --- ## 监控连接状态 ### 使用监控脚本 双击运行 `monitor_php_connections.bat`: ```batch monitor_php_connections.bat ``` 这个脚本会每 5 秒刷新一次,显示: - 所有连接状态 - ESTABLISHED 连接数(正常活跃连接) - CLOSE_WAIT 连接数(连接泄漏) - TIME_WAIT 连接数(正常关闭中) ### 判断标准 - **正常**:CLOSE_WAIT < 10 个 - **注意**:CLOSE_WAIT 10-20 个(需要关注) - **警告**:CLOSE_WAIT > 20 个(建议立即重启) --- ## 长期解决方案 ### 方案 1:使用 Nginx + PHP-FPM(推荐) PHP 内置服务器不适合生产环境,建议使用 Nginx + PHP-FPM。 #### 安装步骤 1. **下载 Nginx for Windows** - 访问:https://nginx.org/en/download.html - 下载稳定版(Stable version) 2. **下载 PHP(非线程安全版本)** - 访问:https://windows.php.net/download/ - 下载 NTS (Non Thread Safe) 版本 3. **配置 PHP-FPM** 创建 `php-cgi.bat`: ```batch @echo off cd C:\php php-cgi.exe -b 127.0.0.1:9000 ``` 4. **配置 Nginx** 编辑 `nginx.conf`: ```nginx server { listen 30100; server_name 192.168.1.164; root C:/Users/Administrator/Desktop/Project/AI_GirlFriend/xunifriend_RaeeC/public; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } ``` 5. **启动服务** ```batch # 启动 PHP-FPM start php-cgi.bat # 启动 Nginx cd C:\nginx start nginx.exe ``` #### 优点 - 支持多进程,性能更好 - 连接管理更稳定 - 适合生产环境 - 不会出现连接泄漏 ### 方案 2:定期自动重启 PHP 服务 如果暂时无法切换到 Nginx,可以设置定时任务自动重启 PHP 服务。 #### 创建定时任务 1. 打开"任务计划程序"(Task Scheduler) 2. 创建基本任务 3. 设置触发器:每 4 小时 4. 操作:启动程序 `restart_php_service.bat` #### 或使用 Windows 计划任务命令 ```batch schtasks /create /tn "重启PHP服务" /tr "C:\path\to\restart_php_service.bat" /sc hourly /mo 4 ``` ### 方案 3:优化 Python 请求代码 在 `lover/deps.py` 中优化 HTTP 请求: ```python import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry # 创建带重试和连接池的 session def get_http_session(): session = requests.Session() # 配置重试策略 retry = Retry( total=3, backoff_factor=0.3, status_forcelist=[500, 502, 503, 504] ) # 配置连接池 adapter = HTTPAdapter( max_retries=retry, pool_connections=10, pool_maxsize=20, pool_block=False ) session.mount('http://', adapter) session.mount('https://', adapter) return session # 使用 session def _fetch_user_from_php(token: str) -> Optional[dict]: """通过 PHP/FastAdmin 接口获取用户信息。""" import logging logger = logging.getLogger(__name__) user_info_api = "http://192.168.1.164:30100/api/user_basic/get_user_basic" logger.info(f"用户中心调试 - 调用接口: {user_info_api}") try: session = get_http_session() resp = session.get( user_info_api, headers={ "token": token, "Connection": "close" # 明确关闭连接 }, timeout=10, # 增加超时时间 ) logger.info(f"用户中心调试 - 响应状态码: {resp.status_code}") # 确保连接关闭 resp.close() except requests.exceptions.Timeout: logger.error(f"用户中心调试 - 请求超时") raise HTTPException( status_code=status.HTTP_504_GATEWAY_TIMEOUT, detail="用户中心接口超时", ) except Exception as exc: logger.error(f"用户中心调试 - 请求异常: {exc}") raise HTTPException( status_code=status.HTTP_502_BAD_GATEWAY, detail="用户中心接口不可用", ) from exc # ... 其余代码 ``` --- ## 预防措施 ### 1. 监控连接状态 定期运行 `monitor_php_connections.bat` 检查连接状态。 ### 2. 设置告警 当 CLOSE_WAIT 连接数超过阈值时,发送告警通知。 ### 3. 日志记录 在 Python 代码中记录每次 PHP 调用的耗时: ```python import time start_time = time.time() resp = requests.get(...) elapsed_time = time.time() - start_time logger.info(f"PHP 接口调用耗时: {elapsed_time:.2f}秒") if elapsed_time > 3: logger.warning(f"PHP 接口响应缓慢: {elapsed_time:.2f}秒") ``` ### 4. 健康检查 添加健康检查端点,定期检查 PHP 服务状态: ```python @app.get("/health/php") async def check_php_health(): try: resp = requests.get( "http://192.168.1.164:30100/api/health", timeout=2 ) return { "status": "healthy" if resp.status_code == 200 else "unhealthy", "response_time": resp.elapsed.total_seconds() } except: return {"status": "down"} ``` --- ## 常见问题 ### Q1: 为什么会有两个 PHP 进程(PID 23736 和 1416)? **A**: 可能是之前启动了多次 PHP 服务,导致有多个进程在监听同一端口。建议: 1. 停止所有 PHP 进程 2. 只启动一个 PHP 服务 ### Q2: 重启后还是有 CLOSE_WAIT 怎么办? **A**: 1. 确认已停止所有旧的 PHP 进程 2. 检查是否有其他程序占用端口 3. 考虑更换端口或使用 Nginx ### Q3: 如何判断是 PHP 问题还是 Python 问题? **A**: 1. 使用 curl 直接测试 PHP 接口: ```bash curl -X GET "http://192.168.1.164:30100/api/user_basic/get_user_basic" -H "token: YOUR_TOKEN" ``` 2. 如果 curl 正常,说明是 Python 客户端问题 3. 如果 curl 也慢,说明是 PHP 服务器问题 ### Q4: 生产环境应该用什么? **A**: - **不推荐**:`php -S`(仅用于开发) - **推荐**:Nginx + PHP-FPM - **备选**:Apache + mod_php --- ## 快速参考 ### 检查连接状态 ```bash netstat -ano | findstr :30100 ``` ### 重启 PHP 服务 ```bash restart_php_service.bat ``` ### 监控连接 ```bash monitor_php_connections.bat ``` ### 停止所有服务 ```bash stop_all_services.bat ``` ### 启动所有服务 ```bash start_all_services.bat ``` --- ## 总结 1. **问题根源**:PHP 内置服务器连接泄漏 2. **临时方案**:定期重启 PHP 服务 3. **长期方案**:使用 Nginx + PHP-FPM 4. **监控措施**:使用监控脚本定期检查 建议尽快切换到 Nginx + PHP-FPM,彻底解决连接泄漏问题!