揭开Linux 系统上TCP的 bind 和 listen函数的神秘面纱(下)
字数 1266 2025-08-19 12:41:50
Linux TCP bind和listen函数深入解析
1. 网络命名空间与backlog参数
backlog参数受具体网络命名空间限制,可以通过以下方式验证:
# 检查默认网络命名空间的somaxconn限制
cat /proc/sys/net/core/somaxconn
# 创建新网络命名空间
ip netns add mynamespace
# 在新命名空间中检查并修改somaxconn
ip netns mynamespace exec cat /proc/sys/net/core/somaxconn
ip netns mynamespace exec /bin/sh -c "echo 1024 > /proc/sys/net/core/somaxconn"
# 验证修改只影响新命名空间
ip netns mynamespace exec cat /proc/sys/net/core/somaxconn
cat /proc/sys/net/core/somaxconn
2. 使用procfs收集TCP套接字信息
/proc/net/tcp文件提供了TCP套接字的详细信息:
sl local_address rem_address st tx_queue rx_queue
0: 00000000:0016 00000000:0000 0A 00000000:00000000
local_address: 本地IP:端口(16进制)rem_address: 远程IP:端口(16进制)st: 连接状态(16进制),如0A表示LISTENtx_queue/rx_queue: 发送/接收队列大小
TCP状态对应关系(来自include/net/tcp_states.h):
enum {
TCP_ESTABLISHED = 1,
TCP_SYN_SENT,
TCP_SYN_RECV,
TCP_FIN_WAIT1,
TCP_FIN_WAIT2,
TCP_TIME_WAIT,
TCP_CLOSE,
TCP_CLOSE_WAIT,
TCP_LAST_ACK,
TCP_LISTEN, // 10 (0x0A)
TCP_CLOSING,
TCP_NEW_SYN_RECV,
TCP_MAX_STATES
};
3. 检查侦听套接字的backlog参数
使用ss命令可以查看侦听套接字的backlog信息:
ss --info --tcp --listen --extended
输出示例:
State Recv-Q Send-Q
LISTEN 0 128
Recv-Q: 等待被接受的连接数Send-Q: backlog的最大长度
ss通过netlink而非procfs获取信息,使用sock_diag子系统,特别是UDIAG_SHOW_RQLEN标志:
udiag_rqueue: 对于侦听套接字,表示挂起连接数udiag_wqueue: 对于侦听套接字,表示backlog长度(即listen()的第二个参数值)
4. IPv4协议族中listen函数的内部机制
4.1 inet_listen函数
int inet_listen(struct socket *sock, int backlog) {
struct sock *sk = sock->sk;
unsigned char old_state;
int err, tcp_fastopen;
// 检查套接字状态和类型
err = -EINVAL;
if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
goto out;
// TCP Fast Open相关处理(省略)
// 初始化侦听套接字
err = inet_csk_listen_start(sk, backlog);
if (err)
goto out;
// 设置backlog值
sk->sk_max_ack_backlog = backlog;
err = 0;
out:
return err;
}
4.2 inet_csk_listen_start函数
int inet_csk_listen_start(struct sock *sk, int backlog) {
struct inet_connection_sock *icsk = inet_csk(sk);
struct inet_sock *inet = inet_sk(sk);
int err = -EADDRINUSE;
// 初始化接受队列
reqsk_queue_alloc(&icsk->icsk_accept_queue);
// 设置backlog参数
sk->sk_max_ack_backlog = backlog;
sk->sk_ack_backlog = 0;
inet_csk_delack_init(sk);
// 设置套接字状态为LISTEN
sk_state_store(sk, TCP_LISTEN);
// 尝试绑定端口
if (!sk->sk_prot->get_port(sk, inet->inet_num)) {
inet->inet_sport = htons(inet->inet_num);
sk_dst_reset(sk);
err = sk->sk_prot->hash(sk);
if (likely(!err))
return 0;
}
// 出错处理
sk->sk_state = TCP_CLOSE;
return err;
}
4.3 未绑定端口的情况
如果listen前未绑定端口,内核会通过inet_csk_get_port自动选择临时端口:
int inet_csk_get_port(struct sock *sk, unsigned short snum) {
// ...
if (!port) { // 如果未指定端口(port == 0)
head = inet_csk_find_open_port(sk, &tb, &port);
if (!head)
return ret;
if (!tb)
goto tb_not_found;
goto success;
}
// ...
}
5. 连接队列监控指标
5.1 队列溢出指标
当接收队列满时,内核会记录ListenOverflows:
# 查看监听队列溢出次数
cat /proc/net/netstat | grep ListenOverflows
# 或使用netstat
netstat --statistics | grep 'times the listen queue of a socket overflowed'
内核中相关代码(tcp_v4_syn_recv_sock):
if (sk_acceptq_is_full(sk))
goto exit_overflow;
exit_overflow:
NET_INC_STATS(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
5.2 SYN队列监控
当SYN队列接近满载时,会触发TCPReqQFullDrop或TCPReqQFullDoCookies(取决于是否启用SYN cookie),相关代码在tcp_conn_request中。
查看SYN-RECV状态的连接数:
ss --numeric state syn-recv sport = :1337
6. 关键概念总结
-
两个队列:
- SYN队列:存储未完成三次握手的连接
- Accept队列:存储已完成三次握手但未被accept的连接
-
backlog参数:
- 由
listen()函数的第二个参数指定 - 受
/proc/sys/net/core/somaxconn限制 - 每个网络命名空间可以有不同的设置
- 由
-
监控工具:
/proc/net/tcp:基础TCP套接字信息ss:更详细的套接字信息,包括backlog/proc/net/netstat:系统级TCP统计信息
-
错误处理:
ListenOverflows:Accept队列满导致的错误TCPReqQFullDrop/TCPReqQFullDoCookies:SYN队列满导致的错误
通过理解这些底层机制,可以更好地调优TCP服务器性能和诊断相关问题。