揭开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表示LISTEN
  • tx_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队列接近满载时,会触发TCPReqQFullDropTCPReqQFullDoCookies(取决于是否启用SYN cookie),相关代码在tcp_conn_request中。

查看SYN-RECV状态的连接数:

ss --numeric state syn-recv sport = :1337

6. 关键概念总结

  1. 两个队列

    • SYN队列:存储未完成三次握手的连接
    • Accept队列:存储已完成三次握手但未被accept的连接
  2. backlog参数

    • listen()函数的第二个参数指定
    • /proc/sys/net/core/somaxconn限制
    • 每个网络命名空间可以有不同的设置
  3. 监控工具

    • /proc/net/tcp:基础TCP套接字信息
    • ss:更详细的套接字信息,包括backlog
    • /proc/net/netstat:系统级TCP统计信息
  4. 错误处理

    • ListenOverflows:Accept队列满导致的错误
    • TCPReqQFullDrop/TCPReqQFullDoCookies:SYN队列满导致的错误

通过理解这些底层机制,可以更好地调优TCP服务器性能和诊断相关问题。

Linux TCP bind和listen函数深入解析 1. 网络命名空间与backlog参数 backlog参数受具体网络命名空间限制,可以通过以下方式验证: 2. 使用procfs收集TCP套接字信息 /proc/net/tcp 文件提供了TCP套接字的详细信息: local_address : 本地IP:端口(16进制) rem_address : 远程IP:端口(16进制) st : 连接状态(16进制),如 0A 表示 LISTEN tx_queue / rx_queue : 发送/接收队列大小 TCP状态对应关系(来自 include/net/tcp_states.h ): 3. 检查侦听套接字的backlog参数 使用 ss 命令可以查看侦听套接字的backlog信息: 输出示例: 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函数 4.2 inet_ csk_ listen_ start函数 4.3 未绑定端口的情况 如果listen前未绑定端口,内核会通过 inet_csk_get_port 自动选择临时端口: 5. 连接队列监控指标 5.1 队列溢出指标 当接收队列满时,内核会记录 ListenOverflows : 内核中相关代码( tcp_v4_syn_recv_sock ): 5.2 SYN队列监控 当SYN队列接近满载时,会触发 TCPReqQFullDrop 或 TCPReqQFullDoCookies (取决于是否启用SYN cookie),相关代码在 tcp_conn_request 中。 查看SYN-RECV状态的连接数: 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服务器性能和诊断相关问题。