tcp/ip协议的那些事

Posted by zhangshun on September 3, 2020

TCP介绍


  • tcp通过下列方式提供可靠性

    • 应用数据被分割成TCP认为最合适发送的数据块

    • 当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到确认,将重发这个报文段
    • 当TCP收到来自TCP连接另一端的数据,它将发送一个确认。这个确认不是立即发送,通常将推迟几分之一秒
    • TCP将保持它首部和数据的检验和
    • IP数据报到达可能会失序,因此TCP报文段的到达也可能会失序。如果必要,TCP将对收到的数据进行重新排列,将收到的数据以正确的顺序交给应用层
    • TCP还能提供流量控制。TCP连接的每一方都有固定大小的缓冲空间。这将防止较快主机致使较慢主机的缓冲区溢出
  • TCP头部

    tcp头部

    • 每个TCP段都包含源端和目的端的端口号,用于寻找发端和收端应用进程。这两个值加上IP首部中的源端IP地址和目的端IP地址唯一确定一个TCP连接。
    • 一个IP地址和一个端口号也称为一个插口对。
    • 序号用来标识从TCP发端向TCP收端发送的数据字节流,它标识在这个报文段中对的第一个数据字节的序号。如果将字节流看作在两个应用程序间的单相流动,则TCP用序号对每个字节进行计数。序号是32 bit的无符号数。SYN跟FIN要占用一个序号。
    • 确认序号(ACK)应当是上次已成功收到数据字节序号加1,ACK的值是希望下次对方数据包的seq序列号。
    • 发送ACK无需占用任何序号,因为32 bit的确认序号字段和ACK标志一样,总是TCP首部的一部分
    • TCP为应用层提供双全工服务。这意味数据能在两个方向上独立的进行传输。
    • TCP的流量控制由连接的每一端通过声明的窗口大小来提供(windowsize)。窗口大小为字节数,这个值是接收方控制发送方可以连续发送未经确认的报文的数量。窗口大小是一个16 bit字段,因而窗口大小最大为65535字节。
    • 检验和覆盖了整个的TCP报文段:TCP首部和TCP数据。这是一个强制性的字段,一定是由发端计算和存储,并由收端进行验证。
    • 只有当URG标志为1时紧急指针才有效。紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。TCP的紧急方式是发送端向 另一端发送紧急数据的一种方式。
    • 最常见的可选字段是最长报文大小,又称为MSS(Maximum Segment Size)。每个连接方通常都在通信的第一个报文段(SYN)中指明这个选项。它指明本端所能接收的最大长度的报文段。默认值536字节。
    • TCP头部20字节,IP头部20字节,TCP头部+IP头部=40字节。
  • TCP三次握手图

    tcp三次握手

  • TCP四次挥手图

    tcp四次挥手

  • TCP的状态变迁图

    tcp状态变迁图

  • 交互式输入

    • 通常每个交互按键都会产生一个数据分组

    • 这样就会产生4个报文段:

      1、来自客户的交互按键

      2、来自服务器的按键确认

      3、来自服务器的按键回显

      4、来自客户的按键回显确认

    tcp交互输入

  • 经受时延的确认

    通常TCP在接收到数据时并不立即发送ACK;相反,他推迟发送,以便将ACK与需要沿该方向发送的数据一起发送(有时称这种现象为数据捎带ACK)。绝大多数实现采用的时延为200ms,也就是说,TCP将以最大200ms的时延等待是否有数据一起发送。

  • Nagle算法

    该算法要求一个TCP连接上最多只能有一个未被确认的未完成的小分组,在该分组的确认到达之前不能发送其他的小分组。相反,TCP收集这些少量的分组,并在确认到来时以一个分组的方式发出去。该算法的优越之处在于它是自适应的:确认到达得越快,数据也就发送的越快。而在希望减少微小分组数目的低速广域网上,则会发送更少的分组。

    如下图,一次可以放松多个小的分组,这是客户只有收到前一个数据的确认后才发送已经收集的数据。通过使用Nagle算法,减少了TCP连接的次数。

    Nagle算法

  • 利用窗口控制提高速度

    • TCP以1个段为单位,每发送一个段进行一次确认应答的处理。这样的传输方式有一个缺点,就是包的往返时间越长通信性能就越低。

    • 为解决这个问题,TCP引入了窗口这个概念。确认应答不再是以每个分段,而是以更大的单位进行确认,转发时间将会被大幅地缩短。也就是说,发送端主机,再发送了一个段以后不必要一直等待确认应答,而是继续发送。如下图所示:

      窗口控制

    • 窗口的大小就是指无需等待确认应答而可以继续发送数据的最大值。上图中窗口大小为4个段。这个机制实现了使用大量的缓冲区,通过对多个段同时进行确认应答的功能。

  • 滑动窗口控制

    滑动窗口

    • 上图中的装口内的数据即便没有收到确认应答也可以被发送出去。不过,在整个窗口的确认应答没有达到之前,如果其中部分数据出现丢包,那么发送端仍然要负责重传。为此,发送端主机需要设置缓存保留这些待被重传的数据,直到收到它们的确认应答。
    • 在滑动窗口以外的部分包括未发送的数据以及已经确认对端已收到的数据。当数据发出后若如期收到确认应答就可以不用再进行重发,此时数据就可以从缓存区清除。
    • 收到确认应答的情况下,将窗口滑动到确认应答中的序列号的位置。这样可以顺序地将多个段同时发送提高通信能力。这种机制也被称为滑动窗口控制
  • 窗口控制中的重发控制

    在使用窗口控制中,出现丢包一般分为两种情况:

    • 确认应答未能返回的情况。在这种情况下,数据已经到达对端,是不需要再进行重发的,如下图:

      窗口控制丢包-01

    • 某个报文段丢失的情况。接收主机如果收到一个自己应该接收的序列号以外的数据时,会针对当前为止收到数据返回确认应答。如下图所示,当某一个报文段丢失后,发送端会一直收到序列号为1001的确认应答,因此,在窗口比较大,又出现报文段丢失的情况下,同一个序列号的确认应答将会被重复不断地返回。而发送端主机如果连续3次收到同一个确认应答,就会将其对应的数据进行重发。这种机制比超时管理更加高效,因此也被称为高速重发控制。

      窗口控制丢包-02

  • 内核参数(待完善)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    
    # 本地可打开端口号的范围
    net.ipv4.ip_local_port_range = 10000 65535 
    # 系统级别的能够打开的文件句柄的数量
    fs.file-max = 2000000 
    # 单个进程可分配的最大文件数
    fs.nr_open = 2000000 
    # 设置内存分配策略。0:内核将检查是否有足够的可用内存供应用进程使用。1:内核允许分配所有的物理内存。2:内核允许分配超过所有物理内存和交换空间总和的内存
    vm.overcommit_memory = 0 
    # 关闭swap交换分区
    vm.swappiness = 0
      
    # 最大的TCP数据接收窗口(字节)。
    net.core.rmem_max = 16777216
    # 最大的TCP数据发送窗口(字节)。
    net.core.wmem_max = 16777216
    # 为自动调优定义socket使用的内存。
    net.ipv4.tcp_rmem = 4096 87380 16777216
    net.ipv4.tcp_wmem = 4096 65536 16777216
    # 对于本端断开的socket连接,TCP保持在FIN-WAIT-2状态的时间(秒)。
    net.ipv4.tcp_fin_timeout = 30
    # 能够更快地回收TIME-WAIT套接字。
    net.ipv4.tcp_tw_recycle = 0
    # TCP时间戳(会在TCP包头增加12个字节)
    net.ipv4.tcp_timestamps = 1
    # 启用RFC 1323定义的window scaling,要支持超过64KB的TCP窗口,必须启用该值,TCP连接双方都启用时才生效。
    net.ipv4.tcp_window_scaling = 1
    # 启用有选择的应答,通过有选择地应答乱序接收到的报文来提高性能,让发送者只发送丢失的报文段
    net.ipv4.tcp_sack = 1
    # 在每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目。
    net.core.netdev_max_backlog = 30000
    # 表示是否打开TCP同步标签,
    net.ipv4.tcp_syncookies = 1
    # 一个tcp连接关闭后,把这个连接曾经有的参数等信息保存到dst_entry中, 只要dst_entry 没有失效,下次新建立相同连接的时候就可以使用保存的参数来初始化这个连接
    net.ipv4.tcp_no_metrics_save = 1
    # 定义了系统中每一个端口最大的监听队列的长度,这是个全局的参数。
    net.core.somaxconn = 42144
    # 系统所能处理不属于任何进程的socket数量
    net.ipv4.tcp_max_orphans = 262144
    # 对于还未获得对方确认的连接请求,可保存在队列中的最大数目。
    net.ipv4.tcp_max_syn_backlog = 262144
    net.ipv4.tcp_synack_retries = 2
    net.ipv4.tcp_syn_retries = 2
      
    # 屏蔽arp回应
    net.ipv4.conf.all.arp_ignore = 1
    net.ipv4.conf.all.arp_announce = 2
    net.ipv4.conf.default.arp_ignore = 1
    net.ipv4.conf.default.arp_announce = 2
    net.ipv4.conf.lo.arp_ignore = 1
    net.ipv4.conf.lo.arp_announce = 2
    
  • TCP的超时与重传

    TCP提供可靠的运输层。它使用的方法之一就是确认从另一端收到的数据。但数据和确认都有可能会丢失。TCP通过在发送端发送数据时设置一个定时器来解决这种问题。如果当定时器溢出时还没有收到接收方确认,发送端就重传该数据。对任何实现而言,关键之处就在于超时和重传的策略,即怎样决定超时间隔和重传的间隔。

    • 往返时间测量

      RTT:一个TCP连接的往返时间

      计算方法:R=&R+(1-&)M,&是一个推荐值为0.9的平滑因子。每次进行新测量的时候,每个新估计的90%来自前一个估计,而10%则取自新的测量。

      RTO:重传超时时间

      计算方法:RTO=R@,@是一个推荐值为2的时延离散因子。