5、监听连接:listen()函数详解、backlog参数含义、被动套接字与主动套接字
好,咱们继续往下走。上一节我们把 socket 创建好了,bind 也绑上地址了。但这时候的套接字,说白了还是个「哑巴」——它不会主动等人来找它。今天要聊的 listen(),就是让这个套接字「支棱起来」的关键一步。
5.1 主动套接字 vs 被动套接字
先搞清楚一个基本概念。你想想看,我们平时写网络程序,套接字其实分两种性格:
- 主动套接字:它自己主动去连接别人。比如客户端程序里,我们调用
connect()去连服务器。这种套接字天生就是「进攻型」的。 - 被动套接字:它不主动出击,而是「守株待兔」,等着别人来连它。服务器端的套接字,在调用
listen()之后,就变成了被动套接字。
我记得刚入行那会儿,有个同事把客户端和服务器的套接字搞混了,客户端调了 listen(),服务器调了 connect(),结果折腾了一下午没连上。嗯,这种低级错误其实挺常见的。
简单来说:socket() 创建出来的套接字默认是主动的。只有调了 listen(),它才变成被动套接字,开始监听端口。
5.2 listen() 函数原型
直接看代码,不废话:
#include <sys/socket.h>
int listen(int sockfd, int backlog);
参数就两个:
sockfd:就是之前创建并 bind 好的那个套接字描述符。backlog:这个参数有点意思,咱们重点聊。
返回值:成功返回 0,失败返回 -1 并设置 errno。
5.3 backlog 参数到底是什么意思?
很多初学者以为 backlog 是「最大连接数」,其实不是。它表示的是「已完成连接队列」+「未完成连接队列」的总和上限。说白了,就是内核为这个监听套接字维护的两个队列的大小。
我画个图帮你理解一下:
看到这张图了吗?客户端发来连接请求,先进入「未完成连接队列」(三次握手还没完),等三次握手完成了,再挪到「已完成连接队列」。你的程序调 accept() 就是从已完成队列里取一个连接出来。
那 backlog 到底设多少?
- 在 Linux 2.2 之前,backlog 指的是两个队列的总和。
- 在 Linux 2.2 之后,backlog 只影响「已完成连接队列」的大小,未完成队列的大小由内核参数
net.ipv4.tcp_max_syn_backlog控制。
我个人习惯,一般服务器设 128 或 256。高并发场景可以设到 1024 甚至更大。但别设太大,否则内存占用会上去。
重要:backlog 不是最大连接数!最大连接数由 accept() 决定,backlog 只是排队等待 accept() 处理的连接数上限。
5.4 实际代码示例
来,看一段完整的服务器初始化代码:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
int main() {
int listen_fd;
struct sockaddr_in server_addr;
// 1. 创建套接字
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1) {
perror("socket");
return -1;
}
// 2. 绑定地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(8080);
if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
close(listen_fd);
return -1;
}
// 3. 监听 —— 关键一步
if (listen(listen_fd, 128) == -1) {
perror("listen");
close(listen_fd);
return -1;
}
printf("服务器已启动,监听端口 8080...\n");
// 4. 接受连接(后续章节详解)
// accept(listen_fd, ...);
return 0;
}
这段代码里,listen(listen_fd, 128) 就是让套接字进入监听模式。128 是 backlog 的值。我个人习惯用 128,大部分场景够用了。
5.5 常见问题与避坑指南
我曾经踩过一个坑:有一次写一个高并发服务器,backlog 设了 5,结果压测时大量连接失败。查了半天才发现是 backlog 太小,客户端连接请求被内核直接丢弃了。后来改成 1024,问题解决。
几个要点你记住:
- listen() 必须在 bind() 之后调用,顺序不能乱。
- listen() 不会阻塞,它只是告诉内核:「嘿,我开始监听了,有连接进来先排队。」
- backlog 的值受内核参数限制。Linux 下,
/proc/sys/net/core/somaxconn定义了 backlog 的上限,默认一般是 128 或 4096。你设得再大,内核也不买账。 - 如果 backlog 设成 0,理论上还能监听,但连接可能被拒绝。不建议这么干。
小技巧:可以用 ss -lnt 命令查看当前系统的监听套接字及其 backlog 值。比如 ss -lnt | grep 8080,能看到 Send-Q 那一列就是 backlog。
5.6 总结一下
listen() 这个函数,说白了就是让套接字「转正」——从主动变成被动,从默默无闻到开始监听端口。backlog 参数控制的是排队等待 accept 的连接数量,不是最大连接数。
嗯,到这里你应该对 listen() 有了比较清晰的认识。下一节我们讲 accept(),真正把连接「接」进来。
公众号:蓝海资料掘金营,微信deep3321