【BUG已解决】OSError: [Errno 98] Address already in use 解决方案(FastAPI/Uvicorn)
【BUG已解决】OSError: [Errno 98] Address already in use 解决方案FastAPI/Uvicorn1. 问题描述使用 Uvicorn 启动 FastAPI 应用时报错$ uvicorn main:app --reload --port 8000 INFO: Will watch for changes in these directories: [/home/user/myapp] ERROR: [Errno 98] Address already in use或者用 Python 的 socket 模块、Flask、Django 的开发服务器时看到类似报错OSError: [Errno 98] Address already in use在 Windows 上则常表现为OSError: [WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次这个报错在开发阶段极为常见——尤其是使用--reload热重载功能时或者 CtrlC 强制中断服务后端口没有正确释放。2. 原因分析Address already in use的字面意思很直白目标端口已经被另一个进程占用操作系统不允许两个进程同时监听同一个端口。常见的具体场景场景具体原因上次服务未正常关闭CtrlC 强制杀死终端但子进程仍在后台运行--reload 模式下的多进程Uvicorn 热重载会启动主进程worker进程异常退出时worker残留其他程序占用了该端口比如同端口跑了另一个Node.js/Java服务容器/宿主机端口映射冲突Docker容器内外都尝试绑定同一端口TIME_WAIT状态未释放TCP连接处于TIME_WAIT短时间内无法立即复用尤其Linux/macOS3. 解决方案方案一找到并结束占用端口的进程最直接Linux/macOS# 【BUG已解决】查找占用8000端口的进程 lsof -i :8000 # 输出示例: # COMMAND PID USER FD TYPE ... # python 12345 user 3u IPv4 ... # 结束该进程 kill -9 12345 # 或者一步到位谨慎使用会直接杀掉占用该端口的所有进程 lsof -ti :8000 | xargs kill -9Windows# 查找占用8000端口的进程 netstat -ano | findstr :8000 # 输出最后一列是PID例如 12345 taskkill /PID 12345 /F方案二更换端口号最快的临时方案uvicorn main:app --reload --port 8001如果只是开发调试阶段换个端口通常是最快的解决方式不需要纠结到底是谁占用了原端口。方案三正确处理 --reload 模式下的进程残留uvicorn --reload在监听到代码变化时会重启 worker 进程如果主进程被强制杀死比如终端窗口被直接关闭而不是 CtrlCworker 进程可能会成为孤儿进程残留# 找到所有uvicorn相关进程 ps aux | grep uvicorn # 批量结束 pkill -f uvicorn建议开发时用CtrlC正常退出而不是直接关闭终端窗口让 Uvicorn 有机会执行清理逻辑。方案四设置 SO_REUSEADDR允许端口快速复用如果是因为 TCP 连接处于TIME_WAIT状态导致的短暂占用服务确实已经停止但操作系统还在等待连接完全清理可以在代码层面允许地址复用import socket import uvicorn if __name__ __main__: uvicorn.run( main:app, host0.0.0.0, port8000, reloadTrue, )Uvicorn 本身默认已经设置了SO_REUSEADDR所以这个问题通常不是 TIME_WAIT 导致的而是真的有进程在占用。可以用以下命令确认端口状态# 查看端口具体处于什么状态LISTEN表示真的被占用TIME_WAIT表示等待释放 netstat -an | grep 8000方案五Docker/docker-compose 场景下的端口冲突# 查看当前正在运行的容器占用了哪些端口 docker ps # 如果发现之前启动过同一个服务的容器忘记停止 docker stop container_id docker rm container_id # 或者一次性停止所有容器谨慎使用 docker stop $(docker ps -aq)docker-compose.yml中也可以直接更换宿主机映射端口避免与本机其他进程冲突services: api: build: . ports: - 8001:8000 # 宿主机8001映射到容器内8000规避宿主机8000被占用的问题方案六写一个自动检测并释放端口的启动脚本#!/bin/bash # start_server.sh - 启动前自动清理占用的端口 PORT8000 PID$(lsof -ti :$PORT) if [ -n $PID ]; then echo 端口 $PORT 被进程 $PID 占用正在结束... kill -9 $PID sleep 1 fi echo 启动服务... uvicorn main:app --reload --port $PORT4. 各方案对比总结方案适用场景推荐指数结束占用进程明确知道是自己之前的服务残留⭐⭐⭐⭐⭐更换端口开发调试快速验证⭐⭐⭐⭐清理reload残留进程--reload模式频繁重启场景⭐⭐⭐⭐Docker容器清理容器化开发环境⭐⭐⭐⭐⭐自动化启动脚本长期开发场景避免反复手动排查⭐⭐⭐⭐5. 常见问题 FAQ5.1 kill -9 后立刻重启仍然报同样的错# 有可能进程使用了SO_REUSEPORT或存在多个worker需要确认全部清理 lsof -i :8000 # 如果还有残留多杀几次或者等待几秒 sleep 3 lsof -i :80005.2 生产环境部署GunicornUvicorn worker如何避免此问题生产环境推荐使用进程管理器如 systemd、Supervisor统一管理服务生命周期避免手动启动导致的进程残留问题# /etc/systemd/system/myapp.service [Unit] DescriptionFastAPI App Afternetwork.target [Service] Userappuser WorkingDirectory/home/appuser/myapp ExecStart/home/appuser/myapp/venv/bin/gunicorn main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 Restartalways [Install] WantedBymulti-user.targetsudo systemctl daemon-reload sudo systemctl restart myapp sudo systemctl status myapp用 systemd 管理的好处是重启服务时会先正确停止旧进程再启动新进程不会出现端口冲突。5.3 如何区分是自己的服务还是别的程序占用了端口# lsof输出的COMMAND列会显示进程名比如python、node、java等 lsof -i :8000 # 如果看到不熟悉的进程名先确认是否是系统关键服务避免误杀 ps -p PID -o comm,args5.4 macOS 上 AirPlay 接收器占用 5000 端口的特殊情况macOS Monterey 及之后版本系统的 AirPlay 接收功能默认占用了 5000 端口如果 Flask/FastAPI 默认用 5000 端口开发会遇到冲突# 关闭AirPlay接收器系统设置 → 通用 → 隔空播放接收器 → 关闭 # 或者直接换端口规避 uvicorn main:app --port 50015.5 团队协作时如何统一约定端口减少冲突建议在项目README.md或.env.example中明确写出各服务使用的端口约定# .env.example API_PORT8000 FRONTEND_PORT3000 REDIS_PORT6379 POSTGRES_PORT5432配合docker-compose.yml统一管理减少本机端口冲突的排查成本。5.6 Windows 上频繁遇到此问题的额外排查方向# Windows 有时会因为 Hyper-V 动态端口范围保留了一大批端口 netsh int ipv4 show excludedportrange protocoltcp # 如果发现常用端口被保留可以调整Hyper-V的端口范围 netsh int ipv4 set dynamicport tcp start49152 num163845.7 使用 NodemonNode.js生态时的类似端口残留问题如果团队中同时有 Node.js 服务nodemon 的自动重启机制也可能出现类似进程残留# 检查并清理nodemon残留进程 pkill -f nodemon # package.json中配置合理的重启延迟减少频繁重启导致的端口竞争 { scripts: { dev: nodemon --delay 1 index.js } }5.8 Django/Flask 开发服务器的相同问题排查# Django开发服务器同样可能遇到端口占用 python manage.py runserver 0.0.0.0:8001 # 直接换端口 # Flask同理 flask run --port 50015.9 编写跨平台通用的端口清理脚本Python实现# kill_port.py - 跨Windows/Linux/macOS通用的端口清理脚本 import subprocess import sys import platform def kill_process_on_port(port): system platform.system() if system Windows: result subprocess.run( fnetstat -ano | findstr :{port}, shellTrue, capture_outputTrue, textTrue ) for line in result.stdout.splitlines(): pid line.strip().split()[-1] subprocess.run(ftaskkill /PID {pid} /F, shellTrue) else: result subprocess.run( flsof -ti :{port}, shellTrue, capture_outputTrue, textTrue ) pids result.stdout.strip().split(\n) for pid in pids: if pid: subprocess.run(fkill -9 {pid}, shellTrue) print(f✅ 端口 {port} 已清理) if __name__ __main__: port sys.argv[1] if len(sys.argv) 1 else 8000 kill_process_on_port(port)python kill_port.py 80005.10 团队远程开发环境如Codespaces/云端IDE中的端口冲突特殊性云端开发环境通常有独立的端口转发机制本地占用和云端占用是两个独立的概念排查时需要分别确认# GitHub Codespaces中查看端口转发状态 gh codespace ports list # 云端容器内的端口占用排查方式与本文描述的Linux场景一致5.11 长期开发环境的进程管理最佳实践总结将本文提到的各种排查手段整合成一个统一的开发环境管理脚本是提升团队开发效率的有效方式#!/bin/bash # dev-manager.sh - 统一管理开发环境端口和进程 case $1 in clean) for port in 3000 8000 5432 6379; do lsof -ti :$port | xargs kill -9 2/dev/null done echo ✅ 已清理所有常用端口 ;; start) docker-compose up -d uvicorn main:app --reload --port 8000 ;; esac5.11.1 补充局域网内多台开发机共享同一测试服务器时的端口规划团队共用一台测试服务器进行联调时建议约定每个人使用不同的端口段避免互相覆盖# 按团队成员分配端口段例如 # 张三: 8001-8099 # 李四: 8101-8199 # 写入团队Wiki减少沟通成本5.11.2 补充使用 Honcho/Foreman 类工具统一管理多进程端口占用如果本地开发需要同时启动多个服务前端、后端、worker等使用进程管理工具统一编排可以更容易追踪端口占用情况# Procfile web: uvicorn main:app --port 8000 worker: celery -A tasks worker frontend: npm run dev # 一条命令启动所有服务CtrlC能统一正确终止全部进程减少残留 honcho start6. 排查清单速查表□ 1. lsof -i :端口号Linux/macOS或 netstat -ano | findstr 端口号Windows □ 2. 确认占用进程是自己的残留服务还是其他程序 □ 3. kill -9 结束残留进程或直接更换端口快速验证 □ 4. --reload模式下用 CtrlC 正常退出避免worker残留 □ 5. Docker场景检查 docker ps 是否有未停止的旧容器 □ 6. 生产环境用systemd/Supervisor管理进程生命周期 □ 7. 编写自动清理端口的启动脚本提升开发效率7. 总结Address already in use排查思路非常直接先找到谁占用了端口再决定杀掉它还是换个端口。开发调试→lsof -i :端口找到进程直接 kill或者简单粗暴换个端口--reload 模式→ 用 CtrlC 正常退出避免子进程残留生产环境→ 用 systemd/Supervisor 统一管理进程生命周期从架构上避免这个问题Docker环境→ 检查是否有忘记停止的旧容器占用了宿主机端口建议开发者写一个自动清理端口的启动脚本加入项目工具链长期使用能显著减少这类问题反复出现导致的时间浪费。