精华内容
下载资源
问答
  • Linux TCP 回收与重用

    千次阅读 2014-03-31 11:12:19
    Linux 内核有两个重要的设置,对于每秒处理上千个连接的高流量站点而言,是很有用处的,这两个设置就是tcp_tw_recycle 和tcp_tw_reuse。这些内核设置允许我们在有新的客户端连接时重新使用套接字(文件描述符)。...
    Linux 内核有两个重要的设置,对于每秒处理上千个连接的高流量站点而言,是很有用处的,这两个设置就是tcp_tw_recycle 和tcp_tw_reuse。这些内核设置允许我们在有新的客户端连接时重新使用套接字(文件描述符)。尽管这两个设置看起来很相似,其实却是截然不同的,若不进行慎重的管理,内核会很快用完所有套接字并且会丢失相关的用户数据。由于客户端连接和断开有许多方式,所以,正如您所知道的,TCP/IP 也有多种状态。这些状态在netstat中是很明显的,如SYN_SENT、ESTABLISHED,
     还有很流行的TIME_WAIT, 还有其它许多不常见的状态。

    TIME_WAIT 状态有一个关键问题,就是它的默认值为120秒时长,它会用完所有有价值的iptables和TCP本身的资源。这样做是为了确保所有在线或通过差的网络在传输额外的数据包或重新发送的数据包时不会错误地附加到新的连接中。但是,关键的问题是,任何新的连接在这两分钟内无法使用任何套接字。尽管您可以调整窗口大小,将其设置为15-30秒,但是对于高流量系统而言,这仍旧是一个问题,因为这些站点每秒需要1000-5000个套接字,这些套接字就会处于等待状态。在此,我们来看一下两个非常重要的内核设置。

    tcp_tw_reuse 设置允许在TIME_WAIT 状态下重复使用一对套接字,由内核确保不会有类似于重复序列号的问题。由于两端都使用了时间戳,所以能够避免序列号重复。也可以基于某些关闭标识如FIN(表示不会有新的流量),来重复使用套接字。

    与tcp_tw_reuse设置相对的是tcp_tw_recycle,这是个设置不是很慎重并且完全取决于客户端的时间戳。这种做法对于NAT系统而言会产生问题,因为这个系统会共享套接字和时间戳,这会导致内核丢弃SYN包并忽略连接意图。这会导致随机连接错误,造成如公司或大学的NAT使用人员随机胡乱连接,甚至导致拥有许多无线连接设备的家庭也出现连接故障。


    所以,从本质上来说,可以使用tcp_tw_reuse 来释放TIME_WAIT中的套接字,但是不要使用tcp_tw_recycle,因为这个设置会给管理员和用户带来无数问题。当然,对于大型站点而言,还有其它需要关注的关键内核设置,最重要的设置包括iptables conntrack 设置和TCP 内存设置。


    (  Authored  by  Steve  Mushero  |  ChinaNetCloud  CEO & CTO  本博客英文原文请点击查看 )

    展开全文
  • 由于我最近的工作与TCP有关,顺便又想起了很久之前遇到的一个问题:明明在接收端有8192字节的接收缓存,为什么了不到8000字节的数据就ZeroWindow了呢?当时我的解决方案是直接扩大接收缓存完事,然后就没有然后了...
    关于TCP的接收缓存以及通告窗口,一般而言懂TCP的都能说出个大概,但是涉及到细节的话可能理解就不那么深入了。由于我最近的工作与TCP有关,顺便又想起了很久之前遇到的一个问题:
    明明在接收端有8192字节的接收缓存,为什么收了不到8000字节的数据就ZeroWindow了呢?
    当时我的解决方案是直接扩大接收缓存完事,然后就没有然后了。后来深挖了一下细节,发现了很多曾经不知道的东西,如今对TCP的理解想必又深入了一些,趁着国庆假期顺便就把很多想法整理成一篇文章了。

    0.network buffer & application buffer

    深入接收缓存管理机制的过程中,你可能会在代码的注释中看到这样的分割,将接收缓存分割成了所谓的network buffer和application buffer,具体参见__tcp_grow_window的注释:
    /* 2. Tuning advertised window (window_clamp, rcv_ssthresh)
     *
     * All tcp_full_space() is split to two parts: "network" buffer, allocated
     * forward and advertised in receiver window (tp->rcv_wnd) and
     * "application buffer", required to isolate scheduling/application
     * latencies from network.
     * window_clamp is maximal advertised window. It can be less than
     * tcp_full_space(), in this case tcp_full_space() - window_clamp
     * is reserved for "application" buffer. The less window_clamp is
     * the smoother our behaviour from viewpoint of network, but the lower
     * throughput and the higher sensitivity of the connection to losses. 8)
     *
     * rcv_ssthresh is more strict window_clamp used at "slow start"
     * phase to predict further behaviour of this connection.
     * It is used for two goals:
     * - to enforce header prediction at sender, even when application
     *   requires some significant "application buffer". It is check #1.
     * - to prevent pruning of receive queue because of misprediction
     *   of receiver window. Check #2.
     *
     * The scheme does not work when sender sends good segments opening
     * window and then starts to feed us spagetti. But it should work
     * in common situations. Otherwise, we have to rely on queue collapsing.
     */
    然后,几乎所有的分析接收缓存的文章都采用了这种说法,诚然,说法并不重要,关键是要便于人们去理解。因此我尝试用一种不同的说法去解释它,其实本质上是相同的,只是更加啰嗦一些。
            和我一向的观点一样,本文不会去大段大段分析源码,也就是说不会去做给源码加注释的工作,而是希望能绘制一个关于这个话题的蓝图,就像之前分析OpenVPN以及Netfilter的时候那样。

    1.通告窗口与接收缓存

    在TCP的配置中,有一个接收缓存的概念,另外在TCP滑动窗口机制中,还有一个接收窗口的概念,毋庸置疑,接收窗口所使用的内存必须分配自接收缓存,因此二者是包容的关系。
            但这不是重点,重点是:接收窗口无法完全占完接收缓存的内存,即接收缓存的内存并不能完全用于接收窗口!Why?
            这是因为接收窗口是TCP层的概念,仅仅描述TCP载荷,然而这个载荷之所以可以收到,必须使用一个叫做数据包的载体,在Linux中就是skb,另外为了让协议运行,必须为载荷封装TCP头,IP头,以太头...等等。
            我用下图来解释接收缓存以及其和TCP数据包的关系:




    【注意,当我说“TCP数据包”的时候,我的意思是这是一个带有以太头的完整数据包,当我说“TCP数据段”的时候,我想表达的则是我并不关系IP层及以下的东西。】
    图示的最后,我特意标红了一个“极力要避免”的警示,确实,如果直接把可用的窗口都通告出去了,且发送端并不按照满MSS发送的话,是存在溢出风险的,这要怎么解决呢?

    附:如何确定通告窗口可以使用的接收缓存

    在代码中,我们注意一个函数tcp_fixup_rcvbuf:
    static void tcp_fixup_rcvbuf(struct sock *sk)
    {
        u32 mss = tcp_sk(sk)->advmss;
        u32 icwnd = TCP_DEFAULT_INIT_RCVWND;
        int rcvmem;
    
        /* Limit to 10 segments if mss <= 1460,
         * or 14600/mss segments, with a minimum of two segments.
         */
        if (mss > 1460)
            icwnd = max_t(u32, (1460 * TCP_DEFAULT_INIT_RCVWND) / mss, 2);
    
        rcvmem = SKB_TRUESIZE(mss + MAX_TCP_HEADER);
        // 将rcvbuf按比例缩放到其(n-1)/n可以完全容纳TCP纯载荷的程度,n由系统参数net.ipv4.tcp_adv_win_scale来确定。
        while (tcp_win_from_space(rcvmem) < mss)
            rcvmem += 128;
    
        rcvmem *= icwnd;
    
        if (sk->sk_rcvbuf < rcvmem)
            sk->sk_rcvbuf = min(rcvmem, sysctl_tcp_rmem[2]);
    }

    以上函数确定了接收缓存,其中有3个要点:
    1).初始通告窗口的大小
    默认是10个MSS满1460字节的段,这个数值10来自google的测试,与拥塞窗口的初始值一致,然而由于MSS各不同,其会按照1460/mss的比例进行缩放来适配经验值10。
    2).TCP载体开销的最小值128
    展开宏SKB_TRUESIZE会发现其最小值就是128,这对通告窗口慢启动过程定义了一个安全下界,载荷小于128字节的TCP数据段将不会增加通告的上限大小。
    3).参数tcp_adv_win_scale的含义
    对比我上面的图示,上述代码的注释,我们知道tcp_adv_win_scale就是控制“载荷/载体”比例的,我们看一下其Kernel DOC
    tcp_adv_win_scale - INTEGER Count buffering overhead as bytes/2^tcp_adv_win_scale
        (if tcp_adv_win_scale > 0) or bytes-bytes/2^(-tcp_adv_win_scale),
        if it is <= 0.
        Possible values are [-31, 31], inclusive.
        Default: 1

    这个参数曾经的default值是2而不是1,这意味着以往TCP的载荷占比由3/4变成了1/2,好像是开销更大了,这是为什么呢?以下是该Change的patch描述:


    From: Eric Dumazet <edum...@google.com>

    [ Upstream commit b49960a05e32121d29316cfdf653894b88ac9190 ]

    tcp_adv_win_scale default value is 2, meaning we expect a good citizen
    skb to have skb->len / skb->truesize ratio of 75% (3/4)

    In 2.6 kernels we (mis)accounted for typical MSS=1460 frame :
    1536 + 64 + 256 = 1856 'estimated truesize', and 1856 * 3/4 = 1392.
    So these skbs were considered as not bloated.

    With recent truesize fixes, a typical MSS=1460 frame truesize is now the
    more precise :
    2048 + 256 = 2304. But 2304 * 3/4 = 1728.
    So these skb are not good citizen anymore, because 1460 < 1728

    (GRO can escape this problem because it build skbs with a too low
    truesize.)

    This also means tcp advertises a too optimistic window for a given
    allocated rcvspace : When receiving frames, sk_rmem_alloc can hit
    sk_rcvbuf limit and we call tcp_prune_queue()/tcp_collapse() too often,
    especially when application is slow to drain its receive queue or in
    case of losses (netperf is fast, scp is slow). This is a major latency
    source.

    We should adjust the len/truesize ratio to 50% instead of 75%

    This patch :

    1) changes tcp_adv_win_scale default to 1 instead of 2

    2) increase tcp_rmem[2] limit from 4MB to 6MB to take into account
    better truesize tracking and to allow autotuning tcp receive window to
    reach same value than before. Note that same amount of kernel memory is

    consumed compared to 2.6 kernels.



    单纯从TCP载荷比来讲,开销的增加意味着效率的降低,然而注意到这部分开销的增加并非网络协议头所为,而是skb_shared_info结构体被计入开销以及skb结构体等系统载体的膨胀所导致:
    我们分别来看一下2.6.32和3.10两个版本的sk_buff的大小,怎么看呢?不要想着写一个模块然后打印sizeof,直接用slabtop去看即可,里面信息很足。
    a).2.6.32版本的sk_buff大小
    slabtop的结果是:
    skbuff_head_cache    550    615    256   15    1 : tunables  120   60    8 : slabdata     41     41      0
    我们看到其大小是256字节。
    b).3.10版本的sk_buff大小
    slabtop的结果是:
    skbuff_head_cache   3675   3675    320   25    2 : tunables    0    0    0 : slabdata    147    147      0
    我们看到其大小是320字节。
            差别并不是太大!这不是主要因素,但确实会有所影响。
            除了skb的膨胀之外,系统中还有别的膨胀,比如为了效率的“对齐开销”,但更大的开销增加是skb_shared_info结构体的计入(个人认为以前开销中不计入skb_shared_info结构体是错误的)等,最终导致新版本(以3.10+为例)的内核计算TRUESIZE的方法改变:
    packet_size = mss + MAX_TCP_HEADER + SKB_DATA_ALIGN(sizeof(struct sk_buff)) + SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))
    然而以往的老内核(以2.6.32为例),其开销的计算是非常鲁莽的,少了很多东西:
    packet_size = mss + MAX_TCP_HEADER + 16 + sizeof(struct sk_buff);
    虽然这种开销的膨胀在TCP层面几乎看不到什么收益(反而付出了代价,你不得不配置更大的rcvbuf...),然而skb等并不单单服务于TCP,这种膨胀的收益可能被调度,中断,IP路由,负载均衡等机制获取了,记住两点即可:首先,Linux内核各个子系统是一个整体,其次,内存越来越便宜而时间一去不复返,空间换时间,划得来!

    2.如何规避接收缓存溢出的风险

    在谈如何规避溢出风险之前,我必须先说一下这个风险并不是常在的,如果应用程序非常迅速的读取TCP数据并释放skb,那么几乎不会有什么风险,问题在于应用程序并不受TCP层的控制,所以我说的“溢出风险”指的是一种合理但很极端的情况,那就是应用程序在TCP层收满一窗数据前都不会去读取数据,这虽然很极端,但是符合TCP滑动窗口的规范:通告给发送端的窗口表示发送端可以一次性发送这么多的数据,至于应用程序什么时候来读取,滑动窗口机制并不控制。
            在阐明了风险的来源后,我们就可以畅谈何以规避风险了。
            我们知道,TCP拥塞控制通过慢启动来规避突发造成的网络缓存溢出的的风险,事实上拥塞控制也是一种流量控制,作为标准的方案,慢启动几乎是规避溢出的标配方案!这很好理解,慢启动的含义是“快速地从起点试探到稳态”,并非其字面含义所说的“慢慢地启动”,之所以有“慢”字是因为与进入稳定状态后相比,它的起点是低的。这和开车是一样的道理,静止的汽车从踩下油门开始一直到匀速,是一个快速加速的过程,达到100km/h的时间也是一个重要的指标,当然,很多情况下是越小越好!
            所以说,通告窗口也是采用慢启动方式逐步张开的。

    2.0.收到极小载荷的TCP数据包时的慢启动

    比如说收到了一个只包含1个字节载荷的数据包时,此时仅仅skb,协议头等开销就会超过几百字节,通告窗口增加是非常危险的。Linux TCP实现中,将128字节定为下限,凡是收到小于128字节载荷的数据包,接收一大窗的数据非常有可能造成缓存溢出,因此不执行慢启动。

    2.1.收到满MSS的TCP数据包时的慢启动

    如果能保证发送端一直发送满MSS长度的TCP数据包,那么接收缓存是不会溢出的,因为整个通告窗口可以使用的内存就是通过这个满MSS长度和接收缓存按照比例缩放而生成的,但是谁也不能保证发送端会一直发送满MSS长度的TCP数据包,所以就不能允许发送端一下子发送所有可用的窗口缓存那么大的数据量,因此慢启动是必须的。
            收到满MSS长度的数据或者大于MSS长度的数据,窗口可以毫无压力地增加2个MSS大小。

    2.2.收到非满MSS

    这里的情况比较复杂了。虽然收到数据长度比MSS小的TCP数据包有缓存溢出的风险,但是受限于当前的通告窗口上限(由于慢启动的功劳)小于整个可用的通告窗口内存,这种情况下即便是发送一整窗的数据,也不会造成整个接收缓存的溢出。这就是说某些时候,当当前的接收窗口上限未达到整个可用的窗口缓存时,长度小于MSS的TCP数据包的额外高于(n-1)/n比例的开销可以暂时“借用”剩余的窗口可用的缓存,只要不会造成溢出,管它是不是借用,都是可以接受的。
            如此复杂的情况,我画了一个稍微复杂点的图来展示,以节省文字篇幅:




    看懂了上图之后,我来补充一个动态过程,如果持续收到小包的情况下,会怎样?
            如果持续收到小于MSS的小包,假设长度都相等,那么从慢启动开始,通告窗口的最大值,即rcv_ssthresh将会在每收到一个数据包后从初始值开始按照2倍数据段长度的增量持续增长,直到其达到小于所有可用通告窗口内存的某个值停止再增长,增长到该值的位置时,一整窗的数据连同其开销将会完全占满整个rcvbuf。

    3.一个差异:通告窗口大小与通告窗口上限

    为什么拥塞窗口的慢启动是直接增加的拥塞窗口的值,通告窗口的慢启动并不直接增加通告窗口而是增加的通告窗口的上限呢?
            这是因为通告窗口的实际值并非单单由接收缓存溢出检测这么一个因素控制,这个因素事实上反而不是主导因素,主导因素是应用程序是不是即时腾出了接收缓存。我们从代码中如何确定通告窗口的逻辑中可以看出:
    u32 __tcp_select_window(struct sock *sk)
    {
        struct inet_connection_sock *icsk = inet_csk(sk);
        struct tcp_sock *tp = tcp_sk(sk);
        /* MSS for the peer's data.  Previous verions used mss_clamp
         * here.  I don't know if the value based on our guesses
         * of peer's MSS is better for the performance.  It's more correct
         * but may be worse for the performance because of rcv_mss
         * fluctuations.  --SAW  1998/11/1
         */
        int mss = icsk->icsk_ack.rcv_mss;
        // free_space就是应用程序和TCP层合力确定的通告窗口基准值,它简单来讲就是(rcvbuf - sk_rmem_alloc)中的纯数据部分,缩放比例就是本文开始提到的(n-1)/n。
        int free_space = tcp_space(sk);
        int full_space = min_t(int, tp->window_clamp, tcp_full_space(sk));
        int window;
        
        if (mss > full_space)
            mss = full_space;
        
        // 这里是为了防止接收缓存溢出的最后防线,当free_space小于全部rcvbuf按纯数据比例缩放后的大小的一半时,就要小心了!
        if (free_space < (full_space >> 1)) {
            icsk->icsk_ack.quick = 0;
    
            if (sk_under_memory_pressure(sk))
                tp->rcv_ssthresh = min(tp->rcv_ssthresh,
                               4U * tp->advmss);
    
            // 我们要多大程度上信任mss,取决于发送端mss的波动情况,如注释中所提到的“It's more correct but may be worse for the performance because of rcv_mss fluctuations.”
            if (free_space < mss)
                return 0;
        }    
        
        // 这里的核心是,虽然应用程序为TCP接收缓存腾出了free_space这么大小的空间,但是并不能全部通告给发送端,需要一点点通告并增加通告的大小,这就是慢启动了。
        // 注意这里,free_space不能超过ssthresh,这便是通告窗口上限慢启动的根本了。    
        if (free_space > tp->rcv_ssthresh)
            free_space = tp->rcv_ssthresh;
    
        ...// 这里的窗口计算详细过程反而不是本文关注的,可以参见其它源码分析的文章和书籍
        ...// 最终free_space要落实到window,为了便于理解核心,可以认为free_space就是window。
    
        return window;
    }

    在本文的最后,我来总结一幅图,将上面谈到的所有这些概念与Linux内核协议栈TCP实现关联起来:




    好了,这篇国庆假期期间没有写完的文章到此终于写完了!
            温州老板坠马落水。

    展开全文
  • Linux TCP数据包接收流程

    千次阅读 2010-02-23 20:30:00
    TCP接收方存在3种队列:1 Backlog Queue (sk->backlog)2 Prequeue Queue (tp->ucopy.prequeue)3 Receive Queue (sk->receive_queue) 然后来看3个队列的区别。 首先sk_backlog队列是当当前的sock在进程上下文中被...

       TCP接收方存在3种队列:

    1 Backlog Queue (sk->backlog)

    2 Prequeue Queue (tp->ucopy.prequeue)

    3 Receive Queue (sk->receive_queue)

     

    然后来看3个队列的区别。

    首先sk_backlog队列是当当前的sock在进程上下文中被使用时,如果这个时候有数据到来,则将数据拷贝到sk_backlog.

    prequeue则是数据buffer第一站,一般都是这里,如果prequeue已满,则会拷贝数据到receive_queue队列种。

    最后一个receive_queue也就是进程上下文第一个取buffer的队列


    这里为什么要有prequeue呢,直接放到receive_queue不就好了.因为receive_queue的处理比较繁琐

    (看tcp_rcv_established的实现就知道了,分为slow path和fast path),而软中断每次只能处理一个数据包

    (在一个cpu上),因此为了软中断能尽快完成,我们就可以先将数据放到prequeue中(tcp_prequeue),然后软

    中断就直接返回. 而处理prequeue就放到进程上下文(tcp_recvmsg调用中)去处理了.

    最后在分析tcp_v4_rcv和tcp_recvmsg之前,我们要知道tcp_v4_rcv还是处于软中断上下文,

    而tcp_recvmsg是处于进程上下文,因此比如socket_lock_t才会提供一个owned来锁住对应的sock。

    而我们也就是需要这3个队列来进行软中断上下文和进程上下文之间的通信。最终当数据拷贝到对应队列,

    则软中断调用返回。这里要注意的是相同的函数在软中断上下文和进程上下文种调用是不同的,我们下面就会看到(比如tcp_rcv_established函数) 。

    首先数据包进入软中断上下文的tcp_v4_rcv函数

     

    该函数的处理过程是:

    首先bh_lock_sock_nested调用加自旋锁

    然后判断当前sock是否被用户进程占用(sock_owned_by_user函数判断)。如果没有的话,就调用tcp_prequeue将数据包加入

    prequeue队列中;否则调用sk_add_backlog将它加入backlog队列中。

     

    tcp_prequeue调用流程如下

     

    该函数的本意是将数据包加入prequeue队列,以待tcp_recvmsg函数调用(通过函数tcp_prequeue_process),这时就返回0;

    如果ucopy.task为NULL的话,表示当前没有pending的进程,函数返回1,数据包在软中断(函数tcp_v4_do_rcv)中处理。

    还有一种情况是prequeue已满,则在软中断上下文中处理该队列中的所有数据包(函数 sk_backlog_rcv)。

    最后,如果发现该skb使得prequeue从空变为非空,则调用wake_up_interruptible(sk->sk_sleep)唤醒在该sock上的等待进程

    (该进程在tcp_recvmsg函数中通过sk_wait_data调用进入该sock的等待队列)。

     

    不管是软中断中的数据包处理还是系统调用中的数据包的处理,都是调用tcp_v4_do_rcv。在连接建立后,该函数的作用是处理数据包,

    数据包加入receive queue中。

     

    先分析数据包如何接收到用户进程的——tcp_recvmsg函数。

     

     

    下一步从receive queue中读取数据包

    receive queue的特征是

    (1) already acked
    (2) guaranteed in order
    (3) contain no holes but
    (4) apparently may contain overlapping data(数据可能重叠)

     

    当receive queue没有可用数据或已经读取完后,进入下面流程

    接下来程序调用函数

    该函数的主要作用是发送一个通告窗口更新的ACK,因为用户进程消费了读缓存中的数据。

     

    流程到此的条件是:

    ● the receive queue is empty, 
    ● no serious errors or state changes were noted and
    ● we haven't consumed sufficient data to return to the caller.

     

    分析sk_wait_data函数

     

     

    接下来的是receive queue中skb的数据读取

     

    最后在跳出循环后,prequeue队列又一次被处理(因为其中可能还有数据,可以读取到本进程中)

     

    数据包处理函数将在后面分析

    展开全文
  • Linux TCP数据包接收处理

    万次阅读 2010-02-25 14:54:00
    在接收流程一节中可以看到数据包在读取到用户空间前,都要经过tcp_v4_do_rcv处理,从而在receive queue中排队。在该函数中,我们只分析当连接已经建立后的数据包处理流程,也即tcp_rcv_established函数。 tcp_rcv_...

    在接收流程一节中可以看到数据包在读取到用户空间前,都要经过tcp_v4_do_rcv处理,从而在receive queue中排队。

    在该函数中,我们只分析当连接已经建立后的数据包处理流程,也即tcp_rcv_established函数。

     

    tcp_rcv_established函数的工作原理是把数据包的处理分为2类:fast path和slow path,其含义显而易见。这样分类

    的目的当然是加快数据包的处理,因为在正常情况下,数据包是按顺序到达的,网络状况也是稳定的,这时可以按照fast path

    直接把数据包存放到receive queue了。而在其他的情况下则需要走slow path流程了。

     

    在协议栈中,是用头部预测来实现的,每个tcp sock有个pred_flags成员,它就是判别的依据。

    可以看出头部预测依赖的是头部长度字段和通告窗口。也就是说标志位除了ACK和PSH外,如果其他的存在的话,就不能用

    fast path处理,其揭示的含义如下:

    1 Either the data transaction is taking place in only one direction (which means that we are the receiver

    and not transmitting any data) or in the case where we are sending out data also, the window advertised

     from the other end is constant. The latter means that we have not transmitted any data from our side for

    quite some time but are receiving data from the other end. The receive window advertised by the other end is constant.

     

    2. Other than PSH|ACK flags in the TCP header, no other flag is set (ACK is set for each TCP segment). 

    This means that if any other flag is set such as URG, FIN, SYN, ECN, RST, and CWR, we
    know that something important is there to be attended and we need to move into the SLOW path.

     

    3. The header length has unchanged. If the TCP header length remains unchanged,
    we have not added/reduced any TCP option and we can safely assume that
    there is nothing important to be attended, if the above two conditions are TRUE.

     

    fast path工作的条件

     

    1 没有乱序数据包

    2 接收窗口不为0

    3 还有接收缓存空间

    4 没有紧急数据

     

    反之,则进入slow path处理;另外当连接新建立时处于slow path。

     

    从fast path进入slow path的触发条件(进入slow path 后pred_flags清除为0):

    1 在tcp_data_queue中接收到乱序数据包

    2 在tcp_prune_queue中用完缓存并且开始丢弃数据包

    3 在tcp_urgent_check中遇到紧急指针

    4 在tcp_select_window中发送的通告窗口下降到0.

     

    从slow_path进入fast_path的触发条件:

    1 When we have read past an urgent byte in tcp_recvmsg() . Wehave gotten an urgent byte and we remain

      in the slow path mode until we receive the urgent byte because it is handled in the slow path in
      tcp_rcv_established() .

    2 当在tcp_data_queue中乱序队列由于gap被填充而处理完毕时,运行tcp_fast_path_check。

    3 tcp_ack_update_window()中更新了通告窗口。

     

    fast path处理流程

    A 判断能否进入fast path

    TCP_HP_BITS的作用就是排除flag中的PSH标志位。只有在头部预测满足并且数据包以正确的顺序(该数据包的第一个序号就是下个要接收

    的序号)到达时才进入fast path。

    该代码段是依据时戳选项来检查PAWS(Protect Against Wrapped Sequence numbers)。

     

    如果发送来的仅是一个TCP头的话(没有捎带数据或者接收端检测到有乱序数据这些情况时都会发送一个纯粹的ACK包)

    主要的工作如下:

    1 保存对方的最近时戳 tcp_store_ts_recent。通过前面的if判断可以看出tcp总是回显2次时戳回显直接最先到达的数据包的时戳,

      rcv_wup只在发送数据(这时回显时戳)时重置为rcv_nxt,所以接收到前一次回显后第一个数据包后,rcv_nxt增加了,但是

      rcv_wup没有更新,所以后面的数据包处理时不会调用该函数来保存时戳。

    2 ACK处理。这个函数非常复杂,包含了拥塞控制机制,确认处理等等。

    3 检查是否有数据待发送 tcp_data_snd_check。

     

    如果该数据包中包含了数据的话

     

    tcp_event_data_recv函数

    rcv_ssthresh是当前的接收窗口大小的一个阀值,其初始值就置为rcv_wnd。它跟rcv_wnd配合工作,

    当本地socket收到数据报,并满足一定条件时,增长rcv_ssthresh的值,在下一次发送数据报组建TCP首部时,

    需要通告对方当前的接收窗口大小,这时需要更新rcv_wnd,此时rcv_wnd的取值不能超过rcv_ssthresh的值。

    两者配合,达到一个滑动窗口大小缓慢增长的效果。

    __tcp_ack_snd_check用来判断ACK的发送方式

    注释很清楚,无需解释。

     

     

    这里有个疑问,就是当ucopy应用读到需要读取到的数据包后,也即在一次处理中

     

    的第二个条件的等号为真 len - tcp_header_len == tp->ucopy.len,然后执行流程到后面eaten为1,所以函数以释放skb结束,没有

    调用sk_data_ready函数。假设这个处理调用流程如下:

    tcp_recvmsg-> sk_wait_data  -> sk_wait_event -> release_sock -> __release_sock-> sk_backlog_rcv-> tcp_rcv_established

    那么即使此时用户得到了所需的数据,但是在tcp_rcv_established返回前没有提示数据已得到,

     

    但是在回到sk_wait_event后,由于__condition为 !skb_queue_empty(&sk->sk_receive_queue),所以还是会调用schedule_timeout

    来等待。这点显然是浪费时间,所以这个condition应该考虑下这个数据已经读满的情况,而不能光靠观察receive queue来判断是否等待。

     

    接下来分析slow path

     

    先看看tcp_validate_incoming函数,在slow path处理前检查输入数据包的合法性。

     

    第一步:检查PAWS tcp_paws_discard

     

     PAWS丢弃数据包要满足以下条件

    1 The difference between the timestamp value obtained in the current segmentand last seen timestamp on

    the incoming TCP segment should be more than TCP_PAWS_WINDOW (= 1), which means that if the segment that was
    transmitted 1 clock tick before the segment that reached here earlier TCP seq should be acceptable.

    It may be because of reordering of the segments that the latter reached earlier.

    2 the 24 days have not elapsed since last time timestamp was stored,

    3 tcp_disordered_ack返回0.

     

    以下转载自CU论坛http://linux.chinaunix.net/bbs/viewthread.php?tid=1130308

     


     

     

     

    在实际进行PAWS预防时,Linux是通过如下代码调用来完成的
    tcp_rcv_established
        |
        |-->tcp_paws_discard
              |
              |-->tcp_disordered_ack
    其中关键是local方通过tcp_disordered_ack函数对一个刚收到的数据分段进行判断,下面我们对该函数的判断逻辑进行下总结:
    大前提:该收到分段的TS值表明有回绕现象发生
    a)若该分段不是一个纯ACK,则丢弃。因为显然这个分段所携带的数据是一个老数据了,不是local方目前希望接收的(参见PAWS的处理依据一节)
    b)若该分段不是local所希望接收的,则丢弃。这个原因很显然
    c)若该分段是一个纯ACK,但该ACK并不是一个重复ACK(由local方后续数据正确到达所引发的),则丢弃。因为显然该ACK是一个老的ACK,并不是由于为了加快local方重发而在每收到一个丢失分段后的分段而发出的ACK。
    d)若该分段是一个ACK,且为重复ACK,并且该ACK的TS值超过了local方那个丢失分段后的重发rto,则丢弃。因为显然此时local方已经重发了那个导致此重复ACK产生的分段,因此再收到此重复ACK就可以直接丢弃。
    e)若该分段是一个ACK,且为重复ACK,但是没有超过一个rto的时间,则不能丢弃,因为这正代表peer方收到了local方发出的丢失分段后的分段,local方要对此ACK进行处理(例如立刻重传)

    这里有一个重要概念需要理解,即在出现TS问题后,纯ACK和带ACK的数据分段二者是显著不同的,对于后者,可以立刻丢弃掉,因为从一个窗口的某个seq到下一个窗口的同一个seq过程中,一定有窗口变化曾经发生过,从而TS记录值ts_recent也一定更新过,此时一定可以通过PAWS进行丢弃处理。但是对于前者,一个纯ACK,就不能简单丢弃了,因为有这样一个现象是合理的,即假定local方的接收缓存很大,并且peer方在发送时很快就回绕了,于是在local方的某个分段丢失后,peer方需要在每收到的后续分段时发送重复ACK,而此时该重发ACK的ack_seq就是这个丢失分段的序号,而该重发ACK的seq已经是回绕后的重复序号了,尽管此时到底是回绕后的那个重复ACK还是之前的那个同样序号seq的重复ACK,对于local方来都需要处理(立刻启动重发动作),而不能简单丢弃掉。

     


     

     第2步 检查数据包的序号是否正确,该判断失败后调用tcp_send_dupack发送一个duplicate acknowledge(未设置RST标志位时)。

    由rcv_wup的更新时机(发送ACK时的tcp_select_window)可知位于序号rcv_wup前面的数据都已确认,所以待检查数据包的结束序号至少

    要大于该值;同时开始序号要落在接收窗口内。

     

    第3步 如果设置了RST,则调用tcp_reset处理

    第4步 更新ts_recent,

    第5步 检查SYN,因为重发的SYN和原来的SYN之间不会发送数据,所以这2个SYN的序号是相同的,如果不满足则reset连接。

     

    接下来重点分析tcp_data_queue函数,这里就是对数据包的处理了。

     

     

     

    如果该数据包刚好是下一个要接收的数据,则可以直接copy到用户空间(如果存在且可用),否则排队到receive queue

    下面看看函数tcp_ofo_queue,也即out-of-order queue的处理

    这里DSACK的处理中为什么即使dsack比end_seq大,还是用dsack作为右边界呢

     

     

     

     

     

    展开全文
  • linux 内核tcp接收数据的实现

    千次阅读 2014-09-16 11:15:57
    接收数据这里和3层的接口是tcp_v4_rcv(我前面的blog有介绍3层和4层的接口的实现).而4层和用户空间,也就是系统调用是socket_recvmsg(其他的读取函数也都会调用这个函数).而这个系统调用会调用__sock_recvmsg.下面...
  • LinuxTCP的回收和重用

    千次阅读 2014-04-24 20:13:32
    Linux 内核有两个重要的设置,对于每秒处理上千个连接的高流量站点而言,是很有用处的,这两个设置就是tcp_tw_recycle 和tcp_tw_reuse。这些内核设置允许我们在有新的客户端连接时重新使用套接字(文件描述符)。...
  • linux tcp repair及tcp热迁移

    千次阅读 2017-06-28 12:16:39
    概念 比如docker等容器在不同的机器之间无缝迁移...linux也在3.5版本中引入TCP_REPAIR socket选项来支持热迁移 获取状态及还原 当需要迁移的时候,为迁移的socket进入repair模式 setsockopt设置TCP_PRE
  • 全面了解linux TCP/IP协议栈

    万次阅读 多人点赞 2017-09-03 19:17:03
    简要说明 自从熟悉了linux socket编程(主要做posix socket的TCP/IP)之后,就一直以来就想写一篇对TCP/IP有一个比较全面的涵盖用户空间、内核以及网卡的文章,以便帮助大家在遇到基于socket的TCP/IP问题或困惑时能...
  • linux tcp select 超时 自查文档

    千次阅读 2016-05-19 17:06:30
    tcp.c#include #include #include #include <unistd.h>#include #include #include #include <arpa/inet.h>#include #inclu
  • linux TCP连接配置

    千次阅读 2013-11-15 13:58:56
    tcp_syn_retries :INTEGER 默认值是5 对于一个新建连接,内核要发送多少个 SYN 连接请求才决定放弃。不应该大于255,默认值是5,对应于180秒左右时间。(对于大负载而物理通信良好的网络而言,这个值偏高,可修改为2...
  • 高流量大并发Linux TCP 性能调优

    千次阅读 2016-08-31 18:58:15
    优化Linux下的内核TCP参数来提高服务器负载能力 Linux Tuning 本文所面对的情况为:  高并发数  高延迟高丢包(典型的美国服务器) 值得注意的是,因为openvz的VPS权限比较低,能够修改的地方比较少...
  • Linux TCP 协议栈数据流走读

    千次阅读 2016-08-24 22:34:39
    1. 综述LinuxTCP协议非常复杂,看了几天Linux内核的tcp实现犹如雾里看花。在这里主要是根据书籍《The Linux Networking Architecture》 24章,以及结合网上的资料,走读一遍tcp协议的数据流的发送和接收。而内核...
  • LInux Tcp 延迟确认问题

    万次阅读 2011-09-09 17:01:40
    案例一:同事随手写个压力测试程序,其实现逻辑为:每秒钟先连续发N个132字节的包,然后连续N个由后台服务回显回来的132字节包。其代码简化如下: char sndBuf[132]; char rcvBuf[132]; while (1) { ...
  • linux TCP发送源码学习(1)--tcp_sendmsg

    千次阅读 2014-01-02 20:22:15
    一、tcp_sendmsg()函数分析: int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,  size_t size) {
  • Linux TCP/IP 协议栈之 Socket的实现分析

    千次阅读 2011-06-30 09:48:00
    Linux TCP/IP 协议栈之 Socket的实现分析(一 套接字的创建) [size=6]Linux TCP/IP 协议栈之 Socket的实现分析[/size] 内核版本:2.6.12 作者:kendo 版权所有,转载请注明出处[[url]www.skynet.org.cn[/url]];...
  • linux TCP协议栈内存管理总结

    千次阅读 2015-03-17 19:00:44
    本次内存分析基于linux-2.6.35.2 tcp协议栈内存控制主要由3个数组变量控制,分别是sysctl_tcp_mem[3]、sysctl_tcp_rmem[3]、 sysctl_tcp_wmem[3],这几个数组的值都可以通过proc文件系统的修改来改变,主要有两种...
  • 高流量大并发Linux TCP性能调优

    千次阅读 2018-01-14 20:15:32
    其实主要是手里面的跑openvpn服务器。因为并没有明文禁p2p... 优化Linux下的内核TCP参数来提高服务器负载能力  Linux Tuning  本文所面对的情况为:  高并发数  高延迟高丢包(典型的美国服务器)  值得注意的
  • Linux TCP/IP大合集

    千次阅读 2015-09-10 00:14:31
    linux 和 os: netstat : 显示网络状态 Netstat 命令用于显示各种网络相关信息,如网络连接,路由表,接口状态 (Interface Statistics),masquerade 连接,多播成员 (Multicast Memberships) 等等。 从整体上看...
  • Linux TCP/UDP小例子

    万次阅读 2013-03-31 17:33:16
    1、网络中进程之间如何通信? 2、Socket是什么? 3、socket的基本操作 3.1、socket()函数 3.2、bind()函数 3.3、listen()、connect()函数 ...4、socket中TCP的三次握手建立连接详解
  • 要说linux tcp服务器编程,首先需要讲一下网络编程中一些使用到的重要又基本的结构体和函数。\ 结构体: IPv4套接字地址结构struct sockaddr_in,定义在头文件中,详细如下: typedef uint32_t in_addr_t;  ...
  • Linux下进行TCP简单通信

    千次阅读 2018-05-09 23:03:34
    体会TCP与UDP编程的不同,UDP编程:http://blog.csdn.net/yueguanghaidao/article/details/7055985二、实验平台Linux操作系统三、实验内容编写LinuxTCP服务器套接字程序,程序运行时服务器等待客户的连接,一旦...
  • Linux TCP协议栈中的预分配缓存

    千次阅读 2013-12-17 14:33:49
    虽然这个成员在内核文档和《Linux内核源码剖析---TCP/IP实现中》都描述为预分配缓存的长度,但是在代码中并没有看到使用这个成员来预先分配一段内存,更多地是通过这个成员来控制TCP协议栈使用的内存。其实sk_...
  •  服务器在调用listen和accept后,就会阻塞在accept函数上,accpet函数返回后循环调用accept函数等待客户的TCP连接。如果这时候又大量的用户并发发起connect连接,那么在listen有队列上限(最大可接受TCP的连接数)的...
  • 但是,目前整个linux源码文件大小是360M,相当于100个哈利波特全集-_-,看的都是神仙了。所以我只准备粗略地看下它的TCP/IP协议栈,然后记点心得。 嗯,我研究的内核版本是目前最新的,linux-2.6.33.1,其实应该都...
  • LinuxTCP连接关闭情况分析

    千次阅读 2012-08-20 23:58:11
    LinuxTCP连接关闭情况分析 2012-04-23 19:10 33人阅读 评论(0) 收藏 举报 一、TCP连接关闭的几种方式: 1、“正常”关闭:调用close()关闭socket、没close但进程正常结束(当然这是不应该的做法)、...
  • linux安全之TCP Wrappers

    千次阅读 2016-03-12 11:12:27
    之前有篇文章讲述了linux服务器的安装增强(http://blog.csdn.net/cracker_zhou/article/details/50392594)。在大牛看来这或许不够专业但是我会不断的好好学习,向大牛看起。让csdn的每篇blog见证我的成长。今天主要...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 63,409
精华内容 25,363
关键字:

linuxtcp收完

linux 订阅