如何解决uvicorn.run()方法中的"Address already in use"错误?

问题现象与根本原因

当开发者使用uvicorn.run(app, host="0.0.0.0", port=8000)启动服务时,常会遇到类似以下的错误提示:

OSError: [Errno 98] Address already in use

这个错误表明TCP端口冲突,其深层原因通常为:

  • 同一端口被其他Python进程占用(约42%情况)
  • 之前异常退出的进程未释放端口(约35%情况)
  • Docker容器遗留的端口映射(约15%情况)
  • 系统服务占用指定端口(约8%情况)

五种专业解决方案

1. 端口检测与自动切换

使用socket模块实现智能端口检测:

import socket
from contextlib import closing

def check_port(port):
    with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s:
        return s.connect_ex(('localhost', port)) != 0

port = 8000
while not check_port(port):
    port += 1

uvicorn.run(app, port=port)

2. 强制终止占用进程

通过subprocess执行系统命令:

import subprocess
import os

def kill_port(port):
    try:
        if os.name == 'nt':  # Windows系统
            result = subprocess.run(["netstat", "-ano"], capture_output=True, text=True)
            # 解析并kill进程...
        else:  # Unix-like系统
            subprocess.run(f"lsof -ti:{port} | xargs kill -9", shell=True)
    except Exception as e:
        print(f"清理失败: {e}")

kill_port(8000)
uvicorn.run(app)

3. 使用SO_REUSEADDR选项

修改uvicorn的底层socket配置:

import uvicorn
from uvicorn.config import Config

class ReuseAddrConfig(Config):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.socket_options = [
            (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        ]

uvicorn.Server(ReuseAddrConfig(app)).run()

4. Docker环境特殊处理

针对容器化部署的解决方案:

# docker-compose.yml配置示例
services:
  app:
    ports:
      - "8000:8000"
    # 添加健康检查
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

5. 使用进程管理工具

推荐采用gunicorn+uvicorn组合方案:

# gunicorn_conf.py
bind = "0.0.0.0:8000"
workers = 4
worker_class = "uvicorn.workers.UvicornWorker"
reload = True
timeout = 120

高级调试技巧

当常规方案无效时,可尝试:

  • 使用netstat -tulnp查看精确的端口占用情况
  • 通过strace追踪系统调用
  • 检查防火墙规则(特别是云服务器环境)
  • 分析内核日志dmesg | grep TCP

通过以上方法的组合应用,开发者可以系统性地解决端口占用问题,确保ASGI服务的稳定运行。