listen() 函数详解
listen()
函数详解
listen()
是 TCP 服务器编程中的核心函数,用于将套接字置于被动监听状态,准备接受客户端连接请求。它在 bind()
之后、accept()
之前调用。
函数原型
#include <sys/socket.h>int listen(int sockfd, int backlog);
参数说明:
sockfd
:
已绑定的套接字文件描述符(通过socket()
创建并已调用bind()
)backlog
:
等待连接队列的最大长度(内核为该套接字排队的最大连接数)
返回值:
- 成功:返回
0
- 失败:返回
-1
并设置errno
工作原理图解
客户端连接请求 内核维护的队列│▼
┌───────────────┐ ┌──────────────────┐
│ SYN_RCVD │◀──────│ 未完成队列 │◀── 新连接请求(SYN)
│ (半连接状态) │ │ (SYN_RCVD状态) │ backlog 限制
└───────────────┘ └──────────────────┘│ ││ 完成三次握手 │▼ ▼
┌───────────────┐ ┌──────────────────┐
│ ESTABLISHED │◀──────│ 已完成队列 │◀── `accept()` 取走的连接
│ (全连接状态) │ │ (ESTABLISHED状态)│
└───────────────┘ └──────────────────┘
-
未完成队列 (SYN队列)
- 存储收到 SYN 但未完成三次握手的连接
- 大小由系统参数决定(非
backlog
控制)
-
已完成队列 (Accept队列)
- 存储已完成三次握手的连接
- 最大长度 =
min(backlog, net.core.somaxconn)
使用案例:TCP 服务器
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>#define PORT 8080
#define BACKLOG 5 // 等待队列长度int main() {// 1. 创建套接字int server_fd = socket(AF_INET, SOCK_STREAM, 0);if (server_fd < 0) {perror("socket failed");return 1;}// 2. 绑定地址struct sockaddr_in address;memset(&address, 0, sizeof(address));address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {perror("bind failed");close(server_fd);return 1;}// 3. 开始监听if (listen(server_fd, BACKLOG) < 0) {perror("listen failed");close(server_fd);return 1;}printf("Server listening on port %d\n", PORT);// 4. 接受连接int addrlen = sizeof(address);while(1) {int new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);if (new_socket < 0) {perror("accept failed");continue;}printf("New connection accepted\n");// 处理客户端请求...char *response = "HTTP/1.1 200 OK\nContent-Type: text/plain\n\nHello from server!";send(new_socket, response, strlen(response), 0);close(new_socket); // 关闭客户端套接字}close(server_fd);return 0;
}
关键注意事项
1. backlog
参数的最佳实践
- 现代系统:实际队列长度 =
min(backlog, net.core.somaxconn)
- 查看系统限制:
cat /proc/sys/net/core/somaxconn # 通常默认128
- 推荐值:
- 轻负载:5-10
- 高并发:1000+(需同步调整系统参数)
- 生产环境:根据压力测试调整
2. 错误处理
常见错误码:
- EADDRINUSE:端口已被占用(检查是否重用套接字)
- EBADF:无效套接字描述符
- ENOTSOCK:文件描述符不是套接字
- EOPNOTSUPP:套接字类型不支持监听
3. 完整连接建立流程
高级主题
1. SYN Flood 攻击防护
当未完成队列溢出时:
// 检查半连接状态
cat /proc/net/stat/syn_recv
防护措施:
- 启用内核参数
net.ipv4.tcp_syncookies = 1
- 减少
net.ipv4.tcp_synack_retries
(默认5)
2. 非阻塞模式下的 listen()
使用 fcntl()
设置非阻塞:
fcntl(server_fd, F_SETFL, O_NONBLOCK);
此时 accept()
立即返回:
- 有连接:返回有效描述符
- 无连接:返回 -1 且
errno = EAGAIN/EWOULDBLOCK
3. 多线程/多进程模型
while(1) {int client_fd = accept(...);if (fork() == 0) { // 子进程处理close(server_fd); handle_client(client_fd);exit(0);}close(client_fd); // 父进程关闭客户端fd
}
常见问题解答
Q:backlog
设置为 0 会怎样?
A:行为取决于系统,Linux 中会允许至少1个连接(实际值 = max(backlog, 1)
)
Q:客户端在 accept()
前完成连接会怎样?
A:连接存入完成队列,等待 accept()
取出,不会丢失
Q:如何监控队列状态?
# 查看Accept队列溢出
netstat -s | grep "listen queue"# 查看SYN队列溢出
netstat -s | grep "SYNs to LISTEN"
Q:为什么需要 listen()
而 UDP 不需要?
A:TCP 是面向连接的协议,需要维护连接状态;UDP 是无连接的。
💡 最佳实践:
listen()
是TCP服务器启动的最后一步准备,合理的backlog
设置对高并发系统至关重要,需结合系统参数优化。加粗样式