TCP连接中,最重要的是连接TCP连接上,两个方向之间的各个状态及各个系统调用与状态之间的关系。往往可以以两种图表示,第一种是状态转换图,第二种是连接时序图。如下:
状态图:
时序图:
 
 
 
 
可见,listen状态是服务器接收连接建立的必经之路。调用listen后,服务器即进入了LISTEN状态。
listen为:

int listen(int sockfd, int backlog);
其backlog是一个建议值,用于指定内部的队列大小,以控制同时建立的连接请求数量。
 
针对控制连接这个需求,有两种方法实现这个backlog:
  1. 单一队列来控制连接。队列中既包含了SYN_RCVD的状态,也包含了ESTABLISHED状态。accept只处理后面一种状态。如果三次握手中的ACK到来,则会在队列中直接改其状态。显然,这时backlog为这一队列的长度。
  2. 两个单独队列来控制。两种状态分别实现单独的队列。显然这种情况下,两个队列都必须有明确的大小限制,backlog只能限制其中一个。
 
在UNP中,这个值是这样描述的:
 
内核维护两个队列,一个是未完成队列,一个是已建立连接的队列。在第一个sync到达时,先将连接塞入第一个队列,再回复ACK+SYNC。此时,连接状态变为SYN_RCVD;
第二个是完成队列。客户端的ACK上来后,对应的连接移入完成队列。此状态的连接会被accept系统调用返回,状态变为ESTABLISHED。
 
另一方面,如果请求上来时队列已满,则TCP忽略之。客户端来重试。
 
可见,其实现实际是第一种单队列的形式,即backlog控制两种状态连接数的总和。
 
而对于linux:
man listen可见:
The behavior of the backlog argument on TCP sockets changed with Linux 2.2. Now it specifies the queue length forcompletely established sockets waiting to be accepted, instead of the number of incomplete connection requests. The maximum length of the queue for incomplete sockets can be set using /proc/sys/net/ipv4/tcp_max_syn_backlog.
 
可见,与UNP书中描述并不一致。是上述的第二种实现方法:两个队列,其中未完成队列长度限制以内核参数来表示,而已完成队列才是backlog。
cat /proc/sys/net/ipv4/tcp_max_syn_backlog
2048
 
具体代码:
TCP收到最终ACK后,重建一个sock:
 
 
tcp_v4_syn_recv_sock函数中:
    if (sk_acceptq_is_full(sk))
        goto exit_overflow;
 
    newsk = tcp_create_openreq_child(sk, req, skb);
    if (!newsk)
        goto exit_nonewsk;
 
可见,在新生成一个socket时,先判断是否full。
 
static inline bool sk_acceptq_is_full(const struct sock *sk)
{
    return sk->sk_ack_backlog > sk->sk_max_ack_backlog;
}
 
 
/*
 *    Move a socket into listening state.
 */
int inet_listen(struct socket *sock, int backlog)中:
    sk->sk_max_ack_backlog = backlog;
 
 
backlog传给了sk_max_ack_backlog。可见,此backlog用作判断complete queue。
 
在往外层,调用处:
    child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL,
                             req, &own_req);
    if (!child)
        goto listen_overflow;
 
    sock_rps_save_rxhash(child, skb);
    tcp_synack_rtt_meas(child, req);
    return inet_csk_complete_hashdance(sk, child, req, own_req);
 
listen_overflow:
    if (!sysctl_tcp_abort_on_overflow) {
        inet_rsk(req)->acked = 1;
        return NULL;
    }
embryonic_reset:
    if (!(flg & TCP_FLAG_RST)) {
        /* Received a bad SYN pkt - for TFO We try not to reset
         * the local connection unless it's really necessary to
         * avoid becoming vulnerable to outside attack aiming at
         * resetting legit local connections.
         */
        req->rsk_ops->send_reset(sk, skb);
    } else if (fastopen) { /* received a valid RST pkt */
        reqsk_fastopen_remove(sk, req, true);
        tcp_reset(sk);
    }
    if (!fastopen) {
        inet_csk_reqsk_queue_drop(sk, req);
        NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_EMBRYONICRSTS);
    }
    return NULL;
 
可见,如果backlog满了,需要关注sysctl_tcp_abort_on_overflow,而些值是/proc下配置。
如果没有配,则啥都没干,直接忽略,这会使server端一定时间段重发SYNC/ACK。如果配置了,则走到下面发RST的流程。
 
另一点,客户端在发送完最后一个ACK,则进入了ESTABLISHED状态,但如上所述,这个ACK可能因为server端的backlog问题使得被忽略。但客户端并不知道,因为它是ESTABLISHED,它这段时间可以发数据!而后续重发来的SYNC/ACK,会使得客户端重发已经发出的数据,赞成带宽浪费。而TCP slow start会缓减这种情况。
 
 
backlog已满的情况下,不仅对于complete queue,对于ack queue也会有处理,适当限速(?)以丢掉一部分的sync,避免太拥堵。在函数中:
int tcp_conn_request(struct request_sock_ops *rsk_ops,
             const struct tcp_request_sock_ops *af_ops,
             struct sock *sk, struct sk_buff *skb)
 
。。。
    /* Accept backlog is full. If we have already queued enough
     * of warm entries in syn queue, drop request. It is better than
     * clogging syn queue with openreqs with exponentially increasing
     * timeout.
     */
    if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) {
        NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
        goto drop;
    }
 
总结:
  1. 服务端的连接建立有两个地方需要排队,一是sync包上来时,另一个是ACK上来时。对于linux来说,前者长度由配置项决定,机器上是2048;后者由listen带入的backlog指定;
  2. 如果backlog对应的队列满后,判断配置项,如果配了,rst,否则直接忽略,等待超时重发;
  3. 客户端只要发送了ACK后,即进入established状态,而服务端则要accept后。两者不对等,且客户端建立后立即可发送数据。
  4. sync队列也会对complete队列是否满进行参考。如果满了,限制收包速度,适当丢弃一些包。
 
 

tcp连接listen的backlog剖析的更多相关文章

  1. 在实战中使用nginx-rtmp遇到的TCP连接问题分析

    在实战中使用nginx-rtmp遇到的TCP连接问题分析 背景 前段时间公司做了一次体育赛事的现场直播,网络由某通信公司负责搭建,主要测试5G CPE上行网络的带宽和稳定性,为了做到万无一失,他们同时 ...

  2. 【转】TCP连接突然断开的处理方法

    TCP是因特网中的传输层协议,使用三次握手协议建立连接,下面是TCP建立连接的全过程. TCP断开连接的过程:TCP四次挥手. TCP/IP 协议簇分层结构 数据链路层主要负责处理传输媒介等众多的物理 ...

  3. 从Linux源码看Socket(TCP)的listen及连接队列

    从Linux源码看Socket(TCP)的listen及连接队列 前言 笔者一直觉得如果能知道从应用到框架再到操作系统的每一处代码,是一件Exciting的事情. 今天笔者就来从Linux源码的角度看 ...

  4. TCP连接的状态转换图深度剖析

    转载请注明来源:https://www.cnblogs.com/hookjc/ 在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接,如图1所示. (1)第一次握手:建立连接时 ...

  5. 我为 Netty 贡献源码 | 且看 Netty 如何应对 TCP 连接的正常关闭,异常关闭,半关闭场景

    欢迎关注公众号:bin的技术小屋,本文图片加载不出来的话可查看公众号原文 本系列Netty源码解析文章基于 4.1.56.Final版本 写在前面..... 本文是笔者肉眼盯 Bug 系列的第三弹,前 ...

  6. libevent (二) 接收TCP连接

    libevent 接收TCP连接 Evconnlistener 机制为您提供了侦听和接受传入的 TCP 连接的方法.下面的函数全部包含在`<event2/listener.h>`中. ev ...

  7. 《TCP/IP具体解释》读书笔记(18章)-TCP连接的建立与中止

    TCP是一个面向连接的协议.不管哪一方向还有一方发送数据之前.都必须在两方之间建立一条连接.这样的两端间连接的建立与无连接协议UDP不同.UDP向还有一端发送数据报时,无需不论什么预告的握手. 1.建 ...

  8. TCP/IP协议中backlog参数

    TCP建立连接是要进行三次握手,但是否完成三次握手后,服务器就处理(accept)呢? backlog其实是一个连接队列,在Linux内核2.2之前,backlog大小包括半连接状态和全连接状态两种队 ...

  9. 不可不知的socket和TCP连接过程

    html { font-family: sans-serif } body { margin: 0 } article,aside,details,figcaption,figure,footer,h ...

随机推荐

  1. 关于Java异常

    此处讲运行时异常和非运行时异常地区别,并分别举例 运行时异常一般为程序逻辑错误引起的,可选择捕获处理或不处理,如:IndexOutOfBoundException, NullPointerExcept ...

  2. 初用idea建立javaweb遇到的问题与心得

    1.直接用idea建立的web项目,其自动生成的web.xml里version=3.1,这样的话建立servlet-name等标签会报错(因为3.1不支持这种做法,更提倡用注解的办法),解决办法是将w ...

  3. JSF标签的使用2

    n  事件监听器是用于解决只影响用户界面的事件 Ø  特别地,在beans的form数据被加载和触发验证前被调用 •    用immediate=“true”指明这个行为不触发验证 Ø  在监听器调用 ...

  4. myString操作符重载

    写在前面的话: 重载是C++的重要内容,在自定义一个类的时候,需要对类中的方法进行重载,才能方便的实现相应的功能,比如一些运算符,构造,析构函数,一些功能函数等等,而C++语言自带的这些东西只使用于基 ...

  5. USACO翻译:USACO 2014 JAN三题(2)

    USACO 2014 JAN 一.题目概览 中文题目名称 队伍平衡 滑雪录像 滑雪场建设 英文题目名称 bteams recording skicourse 可执行文件名 bteams recordi ...

  6. 算法系列(1):Google方程式

    有一个字符组成的等式:WWWDOT – GOOGLE = DOTCOM,每个字符代表一个0-9之间的数字,WWWDOT.GOOGLE和DOTCOM都是合法的数字,不能以0开头.请找出一组字符和数字的对 ...

  7. a chip multiprocessor

    COMPUTER OR GANIZATION AND ARCHITECTURE DESIGNING FOR PERFORMANCE NINTH EDITION A multicore computer ...

  8. SET QUOTED_IDENTIFIER ON和SET ANSI_NULLS ON

    distinct是sqlserver的标识符,如果想以distinct为表时,在QUOTED_IDENTIFIER为off的情况下,是不能创建表名为distinct的表的,因为在QUOTED_IDEN ...

  9. 对Live Writer支持的继续改进:设置随笔地址别名(EntryName)

    在我们发布[功能改进]Live Writer发博支持"建分类.加标签.写摘要"之后,Artech提了一个很好的建议:希望在Live Writer发布随笔时可以设置EntryName ...

  10. Linux下查看Nginx安装目录、版本号信息?

    Linux环境下,怎么确定Nginx是以那个config文件启动的? 输入命令行: ps  -ef | grep nginx 摁回车,将出现如下图片: master process 后面的就是 ngi ...