场景设定

  • 客户端发送:GET /index.html HTTP/1.1
  • nginx监听80端口
  • nginx使用epoll处理连接

完整流程

阶段0:nginx启动初始化

1. nginx master进程启动
2. 创建监听socket: listen_fd = socket(AF_INET, SOCK_STREAM, 0)
3. 绑定80端口: bind(listen_fd, server_addr, sizeof(server_addr))
4. 开始监听: listen(listen_fd, backlog)
5. 创建epoll实例: epfd = epoll_create1(0)
6. 将监听socket加入epoll: epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev)
7. fork出worker进程

阶段1:nginx进程等待连接

nginx worker进程状态: RUNNING
├── 执行事件循环
├── 调用 epoll_wait(epfd, events, MAX_EVENTS, -1)
└── 进程状态变为: SLEEPING (阻塞等待)

CPU状态: 调度其他进程运行
内核状态: nginx进程在epoll的等待队列中

阶段2:客户端建立连接

客户端动作:
├── socket() 创建客户端socket
├── connect() 连接nginx的80端口
└── 发送SYN包

网络层面:
├── 三次握手完成
├── 内核为新连接创建socket (client_fd)
└── listen_fd变为可读状态 (有新连接待accept)

内核动作:
├── 检查epoll监听列表: "listen_fd有epoll在监听"
├── 将事件加入epoll就绪队列
├── 唤醒nginx进程: wake_up_process(nginx_worker)
└── nginx进程状态: SLEEPING → READY

阶段3:nginx处理新连接

nginx进程状态: READY → RUNNING (被CPU调度执行)

nginx执行流程:
├── epoll_wait() 返回: nfds = 1, events[0].fd = listen_fd
├── 检查事件类型: events[0].events & EPOLLIN (可读)
├── 识别为监听socket: if (fd == listen_fd)
├── 接受新连接: client_fd = accept(listen_fd, &client_addr, &len)
├── 设置非阻塞: fcntl(client_fd, F_SETFL, O_NONBLOCK)
├── 创建连接对象: connection_t *c = create_connection(client_fd)
├── 加入epoll监听: epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &client_ev)
└── 继续事件循环: 回到epoll_wait()

nginx进程状态: RUNNING → SLEEPING (再次等待事件)

阶段4:客户端发送HTTP请求

客户端动作:
├── send() 发送HTTP请求
└── "GET /index.html HTTP/1.1\r\nHost: localhost\r\n\r\n"

网络传输:
├── TCP数据包传输
├── 服务器网卡接收数据
├── 硬件中断: IRQ触发
└── 网卡DMA传输数据到内存

内核中断处理:
├── 网卡中断处理程序执行
├── 更新client_fd的socket接收缓冲区
├── 缓冲区内容: "GET /index.html HTTP/1.1\r\nHost: localhost\r\n\r\n"
├── 标记client_fd为可读状态
├── 查找监听client_fd的epoll实例
├── 将读事件加入epoll就绪队列
├── 唤醒nginx进程: wake_up_process(nginx_worker)
└── nginx进程状态: SLEEPING → READY

阶段5:nginx读取和解析请求

nginx进程状态: READY → RUNNING

nginx执行流程:
├── epoll_wait() 返回: nfds = 1, events[0].fd = client_fd
├── 检查事件类型: events[0].events & EPOLLIN
├── 识别为客户端socket: if (fd != listen_fd)
├── 获取连接对象: connection_t *c = get_connection(client_fd)
├── 读取数据: 
│   ├── bytes = read(client_fd, buffer, sizeof(buffer))
│   ├── 系统调用陷入内核
│   ├── 内核从socket缓冲区复制数据到nginx内存
│   └── 返回用户空间,buffer现在包含HTTP请求
├── 解析HTTP请求:
│   ├── 解析请求行: "GET /index.html HTTP/1.1"
│   ├── 解析头部: "Host: localhost"
│   └── 构建request对象
├── 处理请求:
│   ├── 查找文件: /var/www/html/index.html
│   ├── 检查权限和存在性
│   └── 准备响应数据
└── 生成HTTP响应:
    ├── "HTTP/1.1 200 OK\r\n"
    ├── "Content-Type: text/html\r\n"
    ├── "Content-Length: 1024\r\n\r\n"
    └── "<html>...</html>"

阶段6:nginx发送响应

nginx发送流程:
├── write(client_fd, response_buffer, response_length)
├── 系统调用陷入内核
├── 内核将数据复制到client_fd的发送缓冲区
├── TCP层处理数据发送
├── 网卡发送数据包给客户端
└── 如果发送缓冲区满,则:
    ├── write()返回EAGAIN
    ├── 将client_fd注册EPOLLOUT事件
    └── 等待下次可写事件继续发送

连接处理:
├── 检查Connection头: "keep-alive" or "close"
├── 如果keep-alive:
│   ├── 重置连接状态
│   ├── 继续监听client_fd的读事件
│   └── 准备处理下一个请求
└── 如果close:
    ├── epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, NULL)
    ├── close(client_fd)
    └── 释放连接对象

nginx状态: 继续事件循环 → SLEEPING

状态转换总结

时间轴    nginx状态    CPU在做什么           内核socket状态
─────    ─────────    ──────────────       ─────────────
T0       RUNNING      nginx初始化           监听socket创建
T1       SLEEPING     执行其他进程          等待连接
T2       READY        准备调度nginx         新连接到达
T3       RUNNING      处理accept()          连接建立
T4       SLEEPING     执行其他进程          等待数据
T5       READY        准备调度nginx         HTTP数据到达
T6       RUNNING      读取+解析+响应        处理请求
T7       SLEEPING     执行其他进程          等待下一个请求

关键性能要点

1. 异步非阻塞

  • nginx永远不会因为I/O而阻塞
  • 一个worker进程可以处理数万个连接

2. 零拷贝优化

  • sendfile(): 文件直接从磁盘到网卡,不经过用户空间
  • 减少内存拷贝次数

3. 事件批处理

  • epoll_wait一次可能返回多个事件
  • nginx批量处理,减少系统调用开销

4. 连接复用

  • HTTP Keep-Alive: 一个连接处理多个请求
  • 减少连接建立/销毁开销

这就是nginx能够以极低的内存占用处理大量并发连接的核心原理!