Linux TCP滑动窗口代码简述

前言:TCP的可靠性大致通过3类方法来保障:1.确认和重传。2.流量控制。3.拥塞避免。其中的流量控制中使用的滑动窗口,使得TCP的发送方和接收方速度得以匹配,从而为传输提供了可靠性支撑。本篇就介绍一下滑动窗口在Linux的大致代码,对于滑动窗口的基本知识已经有无数优秀的文章,更有TCP/IP卷一可参考,本篇不再赘述。代码基于 Linux 2.6.32。

1. 背景问题介绍

我们知道TCP是有确认机制的,就是对于发送方发送的每个字节,接收方都会显式的进行确认(连续确认实际也是确认了每个字节)。那么仔细想象一下,该如何处理这个确认过程呢?比如,sender发送完一定数据后,停下来等待receiver的确认,然后再继续发送,再继续等待... 因此这里存在一个传输效率的问题。

接着再想另外一个问题,如果发送端的发送速度快,而接收端的接收速度慢,此时,如果不能协调收发速度,将会导致接收不及时丢包,进而加重重传,丢更多的包,引发网络雪崩。

上面两个问题基本就是滑动窗口诞生要解决的问题,即提高传输效率和流量控制的功能。

2. 流量控制简单过程

TCP的流量控制主要是协调收发的速度,在发送端维护着发送和接收窗口,同样的,在接收端,也同样如此。在说具体的操作之前,先说几个相关的概念:发送队列,接收队列,重传队列,滑动窗口,发送窗口,拥塞窗口,通告窗口。

2.1 概念解释
  • 发送队列——我们知道协议栈在发送报文的时候,是要先放入到发送队列的,每个打开的socket都维护着一个接收队列和发送队列。
  • 重传队列——当报文从发送队列被发送后,会拷贝一份放到重传队列,重传队列用于超时定时器到期后,进行重传。
  • 滑动窗口——滑动窗口是一个可以滑动的区间。在发送时,通常就是指发送窗口;在接收时,就是指接收窗口。滑动窗口是一种提高传输效率的机制,因此在发送和接收过程中都有。
  • 拥塞窗口——因为TCP有拥塞避免机制,因此引出了一个拥塞窗口,他也是一个限制发送速度的东西,当出现拥塞的时候,调节发送窗口大小。通常发送窗口是取拥塞窗口和通告窗口的较小值。
  • 通告窗口——通告窗口是接收端传递给发送端的一个窗口值,表示接收端还有多大空余空间接收数据。所以,发送端的发送窗口就是根据通告窗口进行调节,使发送和接收速度匹配。
2.2 发送和接收
  • 当有数据需要发送时,会把数据挂在这个socket的发送队列中,在linux中这个队列使用的是双向链表实现的

    struct sk_buff_head {
    /* These two members must be first. */
    struct sk_buff  *next;
    struct sk_buff  *prev;
    
    __u32       qlen;
    spinlock_t  lock;
    };

    TCP接收报文的函数为tcp_v4_do_rcv(),当建立连接后,接收处理的函数为tcp_rcv_established(),进来后,会进行fast path和slow path的区分处理,快通道和慢通道是由首部预测来确认的,首部预测用于提高TCP的处理速度。通常网络上大多数报文都会走fast path,看处理细节

    if (len == tcp_header_len)
    {
    /* Predicted packet is in window by definition.
     * seq == rcv_nxt and rcv_wup <= rcv_nxt.
     * Hence, check seq<=rcv_wup reduces to:
     */
    if (tcp_header_len ==
        (sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
        tp->rcv_nxt == tp->rcv_wup)
        tcp_store_ts_recent(tp);
    
    /* We know that such packets are checksummed
     * on entry.
     */
    tcp_ack(sk, skb, 0);
    __kfree_skb(skb);
    tcp_data_snd_check(sk);
    return 0;
    }

    那么就进行ack报文的处理tcp_ack(),在这个函数中,会更新发送窗口,使窗口右移,这样就会有新的报文可以被发送,tcp_data_snd_check(sk);就把这些报文发送出去。

    最后调用tcp_write_xmit(),其函数说明如下:

    This routine writes packets to the network. It advances the

    send_head. This happens as incoming acks open up the remote

    window for us.

可以看出,这个确实是用于发送窗口扩大后的报文。从这里可以看出TCP报文的发送在窗口机制下是由接收的ack来

  • 当有数据需要接收时,依然看tcp_rcv_established()函数,先检查是否满足快速路径,如果没有乱序的话,就是这样。
if (tp->copied_seq == tp->rcv_nxt &&
                len - tcp_header_len <= tp->ucopy.len) {
#ifdef CONFIG_NET_DMA
                if (tcp_dma_try_early_copy(sk, skb, tcp_header_len)) {
                    copied_early = 1;
                    eaten = 1;
                }
#endif
                if (tp->ucopy.task == current &&
                    sock_owned_by_user(sk) && !copied_early) {
                    __set_current_state(TASK_RUNNING);

                    if (!tcp_copy_to_iovec(sk, skb, tcp_header_len))
                        eaten = 1;
                }

然后就把报文从内核态拷贝到用户态,如果拷贝成功,标志成eaten = 1;接下来就是计算RTT以及更新接收的序列号。

if (eaten)
{
    /* Predicted packet is in window by definition.
     * seq == rcv_nxt and rcv_wup <= rcv_nxt.
     * Hence, check seq<=rcv_wup reduces to:
     */
    if (tcp_header_len ==
        (sizeof(struct tcphdr) +
         TCPOLEN_TSTAMP_ALIGNED) &&
        tp->rcv_nxt == tp->rcv_wup)
        tcp_store_ts_recent(tp);

    tcp_rcv_rtt_measure_ts(sk, skb);

    __skb_pull(skb, tcp_header_len);
    tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
    NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPHITSTOUSER);
}

如果没有拷贝成功,那么就把报文放在接收队列中,同时更新RTT,失败的原因比如用户报文数据长度比用户空间缓存的剩余量大等。

if (!eaten)
{
    if (tcp_checksum_complete_user(sk, skb))
        goto csum_error;

    /* Predicted packet is in window by definition.
     * seq == rcv_nxt and rcv_wup <= rcv_nxt.
     * Hence, check seq<=rcv_wup reduces to:
     */
    if (tcp_header_len ==
        (sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
        tp->rcv_nxt == tp->rcv_wup)
        tcp_store_ts_recent(tp);

    tcp_rcv_rtt_measure_ts(sk, skb);

    if ((int)skb->truesize > sk->sk_forward_alloc)
        goto step5;

    NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPHPHITS);

    /* Bulk data transfer: receiver */
    __skb_pull(skb, tcp_header_len);
    __skb_queue_tail(&sk->sk_receive_queue, skb);
    skb_set_owner_r(skb, sk);
    tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
}

最后检查一下是否需要发送ack报文或者是sack报文。

if (!copied_early || tp->rcv_nxt != tp->rcv_wup)
                __tcp_ack_snd_check(sk, 0);

另外一个是如果出现乱序等,快速路径没有满足条件,则走慢速路径,在慢速路径中会有把报文放入乱序队列等操作,具体不表了。

tcp_data_queue(sk, skb);

之后,用户进程通过recv读操作,把报文从接收队列中读取报文,在tcp_recvmsg可以看其过程:

skb_queue_walk(&sk->sk_receive_queue, skb)
{
    /* Now that we have two receive queues this
     * shouldn‘t happen.
     */
    if (WARN(before(*seq, TCP_SKB_CB(skb)->seq),
         KERN_INFO "recvmsg bug: copied %X "
               "seq %X rcvnxt %X fl %X\n", *seq,
               TCP_SKB_CB(skb)->seq, tp->rcv_nxt,
               flags))
        break;

    offset = *seq - TCP_SKB_CB(skb)->seq;
    if (tcp_hdr(skb)->syn)
        offset--;
    if (offset < skb->len)
        goto found_ok_skb;
    if (tcp_hdr(skb)->fin)
        goto found_fin_ok;
    WARN(!(flags & MSG_PEEK), KERN_INFO "recvmsg bug 2: "
            "copied %X seq %X rcvnxt %X fl %X\n",
            *seq, TCP_SKB_CB(skb)->seq,
            tp->rcv_nxt, flags);
}

3. 总结

TCP的滑动窗口的流量控制是通过协调发送方和接收方的速度来实现的,具体来说,就是发送方窗口是由接收方回的ack驱动的,也就是说发送方要能持续发送包需要持续接收ack。另一个方面,接收方在读取报文后,发送ack进行响应,循环进行接收。这个过程通过驱动窗口的可持续滑动,进而实现了流量控制和提高传输效率。

原文地址:https://www.cnblogs.com/yhp-smarthome/p/8338072.html

时间: 2024-11-08 19:09:24

Linux TCP滑动窗口代码简述的相关文章

TCP 滑动窗口和 拥塞窗口

转http://coolshell.cn/articles/11609.html 滑动窗口 -- 表征发送端和接收端的接收能力 拥塞窗口-- 表征中间设备的传输能力 TCP滑动窗口 需要说明一下,如果你不了解TCP的滑动窗口这个事,你等于不了解TCP协议.我们都知道,TCP必需要解决的可靠传输以及包乱序(reordering)的问题,所以,TCP必需要知道网络实际的数据处理带宽或是数据处理速度,这样才不会引起网络拥塞,导致丢包. 所以,TCP引入了一些技术和设计来做网络流控,Sliding Wi

TCP滑动窗口

TCP的滑动窗口解决了端到端的流量控制问题,允许接受方对传输进行限制,直到它拥有足够的缓冲空间来容纳更多的数据.滑动窗口的大小由接收方确定,接收方在发送确认信号给发送方的同时告诉发送方自己的缓冲区大小(在TCP头部字段中),发送方根据此大小确定窗口大小,从而控制数据发送量.同时,滑动窗口协议允许发送方在停止并等待确认前可以连续发送多个分组,由于发送方不必每发一个分组就停下来等待确认,因此该协议可以加速数据的传输. 下面是一个例子: 在建立连接时,双方都告知了对方自己的MSS为1024,所以在传输

TCP 三次握手四次挥手, ack 报文的大小.tcp和udp的不同之处、tcp如何保证可靠的、tcp滑动窗口解释

一.TCP三次握手和四次挥手,ACK报文的大小 首先连接需要三次握手,释放连接需要四次挥手 然后看一下连接的具体请求: [注意]中断连接端可以是Client端,也可以是Server端. [注意] 在TIME_WAIT状态中,如果TCP client端最后一次发送的ACK丢失了,它将重新发送.TIME_WAIT状态中所需要的时间是依赖于实现方法的.典型的值为30秒.1分钟和2分钟.等待之后连接正式关闭,并且所有的资源(包括端口号)都被释放. [问题1]为什么连接的时候是三次握手,关闭的时候却是四次

TCP 滑动窗口的简介(写得太好,转载过来的)

TCP 滑动窗口的简介 POSTED BY ADMIN ON AUG 1, 2012 IN FLOWS34ARTICLES | 0 COMMENTS TCP的滑动窗口主要有两个作用,一是提供TCP的可靠性,二是提供TCP的流控特性.同时滑动窗口机制还体现了TCP面向字节流的设计思路.TCP 段中窗口的相关字段. TCP的Window是一个16bit位字段,它代表的是窗口的字节容量,也就是TCP的标准窗口最大为2^16-1=65535个字节. 另外在TCP的选项字段中还包含了一个TCP窗口扩大因子

tcp 滑动窗口网络传输协议动态演示

我是一个菜鸟,只为了有好东西和大家分享! 最近项目中遇到一个让我很纠结的问题,为什么浏览器下载速度我测试可以达到1兆,而当我用sperf测试服务器到我本机单纯的传输速度的时候,速度最大超不过200kb/s.首先应该明白的是浏览器是单线程,而传输速度却惊奇的达到1兆,我在vs中测试一个单纯的下载,速度也是1兆.那么到这里,我想到了可能是tcp  滑动窗口协议,协议的动态图,我付了链接. 不喜勿喷,我是菜鸟.    http://histrory.visualland.net/tcp_swnd_tu

TCP滑动窗口与回退N针协议

[转]TCP 滑动窗口协议/1比特滑动窗口协议/后退n协议/选择重传协议 2014-1-5阅读884 评论0 本文转自 http://www.cnblogs.com/ulihj/archive/2011/01/06/1927613.html 滑动窗口协议 一图胜千言,看下面的图,简单解释下: 发送和接受方都会维护一个数据帧的序列,这个序列被称作窗口.发送方的窗口大小由接受方确定,目的在于控制发送速度,以免接受方的缓存不够大,而导致溢出,同时控制流量也可以避免网络拥塞.下面图中的4,5,6号数据帧

TCP/IP(十一)TCP滑动窗口和用赛控制

目前建立在TCP协议上的网络协议特别多,有telnet,ssh,有ftp,有http等等.这些协议又可以根据数据吞吐量来大致分成两大类:(1)交互数据类型,例如telnet,ssh,这种类型的协议在大多数情况下只是做小流量的数据交换,比如说按一下键盘,回显一些文字等等.(2)数据成块类型,例如ftp,这种类型的协议要求TCP能尽量的运载数据,把数据的吞吐量做到最大,并尽可能的提高效率.针对这两种情况,TCP给出了两种不同的策略来进行数据传输. 1.TCP的交互数据流 对于交互性要求比较高的应用,

TCP滑动窗口控制流量的原理

TCP滑动窗口控制流量的原理 TCP的滑动窗口机制       TCP这个协议是网络中使用的比较广泛,他是一个面向连接的可靠的传输协议.既然是一个可靠的传输协议就需要对数据进行确认.TCP协议里窗口机制有2种:一种是固定的窗口大小:一种是滑动的窗口.这个窗口大小就是我们一次传输几个数据.对所有数据帧按顺序赋予编号,发送方在发送过程中始终保持着一个发送窗口,只有落在发送窗口内的帧才允许被发送:同时接收方也维持着一个接收窗口,只有落在接收窗口内的帧才允许接收.这样通过调整发送方窗口和接收方窗口的大小

tcp滑动窗口与拥塞控制

TCP协议作为一个可靠的面向流的传输协议,其可靠性和流量控制由滑动窗口协议保证,而拥塞控制则由控制窗口结合一系列的控制算法实现.一.滑动窗口协议     所谓滑动窗口协议,自己理解有两点:1. "窗口"对应的是一段可以被发送者发送的字节序列,其连续的范围称之为"窗口":2. "滑动"则是指这段"允许发送的范围"是可以随着发送的过程而变化的,方式就是按顺序"滑动".在引入一个例子来说这个协议之前,我觉得很有必