tcp下载窗口太小的问题_tcp所有定时器

tcp协议中所有定时器。超时一词在软件领域用途非常广泛,是解决的很多问题利器。TCP设计精髓在于他自我管理的状态机,而要想状态机正常运行,超时必不可少,如connect flood攻击。

建立连接定时器

开始

TCP属于可靠连接,需要经历三次握手。

30effbe7ec8dcc1d8ab1e01339f16e0c.png

如上图所示,主要分为以下过程:

  • 第一步:服务器准备好接受外来的连接。通常通过调用socket、bind和listen三个函数来完成,称之为被动打开(passive open)
  • 第二步:客户端通过connect发起主动连接(active open)。导致客户端TCP发送一个SYN同步分节。一般SYN分节不携带数据,其所在的IP数据报只包含一个IP首部、一个TCP首部以及TCP选项(最为常见的是MSS分节,在这里并没有画出)
  • 第三步:服务器确认客户的SYN,同时自己也发送一个SYN分节。服务器在单个分节中发送SYN和对客户端SYN的ACK确认。此时客户端已经处于ESTABLISHED状态
  • 第四步:客户端必须确认服务端的SYN分节。确认成功,此时服务端也进入ESTABLISHED状态。

思考:在握手过程中如果有一方因为网络抖动或者中间路由丢弃,导致不能及时给对方回复,此时怎么办呢?

下图为正常的tcp三次握手抓包图

67c8e5455dde9875238b0d6a6843cf23.png

其中No对应列表的序号.

环境查看

e9f7d0fa90091ff41be44b3453c3d124.png

结果分析

在这里有可能会出现两种情况超时

  1. 第一种:当TCP客户端发出请求没有收到SYN分节响应时,则会返回ETIMEOUT。即,调用connect函数时,内核会发送一个SYN分节,若无响应则等待6s再发送一个,若仍无响应则等待24s再发送一个。若总共等待75s(4.4BSD规定75s)后仍未收到响应则返回本错误。注意,不同系统对时间值的设置不相同。
  2. 第二种:当客户发出的SYN在中间的某个路由器上引发“destination unreachable”(目的地不可达)的ICMP错误时,则认为是一种软错误,不会终止。客户主机内核会保存该消息,并按照上述第一种情况来间接性继续发送SYN。在某个规定的时间(4.4BSD规定75s)后仍未收到响应,则把保存的消息(即ICMP错误)作为EHOSTUNREACH或ENETUNREACH错误返回给进程。

实验验证

  1. 系统:centos6.5
  2. 命令
1
2
3
  1. 实验结果

e151d8cc116d429e69648f64bac8747b.png
总时间为63s,这个时间还是难以忍受的,想想如果一个食堂因为你一个人等了1分多钟,估计你会被打shi
  • 抓包: tcpdump -i lo ‘tcp[tcpflags] = tcp-syn’

8d9177f16c3af7741e61dbf18f5af718.png
在这里我们也看到了总共6次,重试了5次。而且也可以看到:1s-à2s-à4s-à8s-à16s方式来进行重试的。

解决方法

  1. 修改内核参数(强烈不推荐)
    vim /etc/sysctl.conf
    将net.ipv4.tcp_syn_retries = 5修改为 1,则可以将 connect 超时时间改为 3 秒,例如:# sysctl net.ipv4.tcp_syn_retries=1,之后使用命令sysctl -p /etc/sysctl.conf保存
    . 应用层进行设置
  • 使用alarm,指定超时时间,超时满后产生SIGALRM信号
  • Select中阻塞等待I/O,此时套接字必须为非阻塞
  • 使用较新的SO_RECVTIMEO和SO_SNDTIMEO

重传定时器(RTO)

开始

首先介绍一下滑动窗口。来自百度百科:滑动窗口协议的基本原理就是在任意时刻,发送方都维持了一个连续的允许发送的帧的序号,称为发送窗口;同时,接收方也维持了一个连续的允许接收的帧的序号,称为接收窗口。发送窗口和接收窗口的序号的上下界不一定要一样,甚至大小也可以不同。不同的滑动窗口协议窗口大小一般不同。发送方窗口内的序列号代表了那些已经被发送,但是还没有被确认的帧,或者是那些可以被发送的帧。下面举一个例子(假设发送窗口尺寸为2,接收窗口尺寸为1)。

591a83d537f1f916e558e38b57b70eed.png

在这里我们重点分析发送窗口,其原理是一个发送窗口,其中包括两部分内容:已发送但未确认和可以发送但还未发送的。

超时重传

  1. 超时重传原理
    超时重传是TCP保持可靠性中不可缺少的一项。其原理是发送一个数据后就开启一个计时器,在一定时间内如果没有得到ACK分节(如上图Window Alreadly Sent 14bytes),则发送端重新发送数据报文,直到数据全部发送成功为止,此时会重置RTO。
    当然需要注意的是,此份数据因为保存在内核,而且保存完成路由的信息,所以在重传时不会涉及到数据拷贝(应用进程到内核的双向复制),也不需要上文切换,所以只需要将数据从内核搬到网卡,重新发送即可。
  2. Linux下有两个重要的内核参数和TCP超时有关:

739fffdba3de7f25505709f18655b9c8.png

550b6f4827073ecc37a9c994b4337e77.png

tcp_retries1:表示指定在底层IP接管之前(也就是不需要网络层参与)TCP最少执行的重传次数,默认值为3;
tcp_retries1:表示连接放弃前TCP最多执行的重传次数,默认值为15(一般对应13-30分钟)。

坚持定时器(persist timer)

开始

TCP提供流量控制机制,其原理:TCP总是告知对端在任何时刻他一次能够从对端接受多少字节的数据,这也被为通告窗口(Advertised Window)。在任何时刻,该窗口指出接受缓冲区当前可用的空间量(可以使用netstat -lpn查看),从而确保发送端发送的数据不会使接收端缓冲区溢出。

  • 当前接受和发送缓冲区大小

bd42d9a38ca62746b100583ec011415b.png
  • 系统默认和最大接受缓冲区大小

dec87e3ac60e2cde6012e06edaee1d48.png

实验与分析

  • 该窗口时刻动态变化的,当接受到来自发送端的数据时,窗口大小就减小;而接受端读取数据后,窗口会变大。通过窗口大小可能是0,此时会通知发送端不要在发送数据,那么问题来了,客户端什么时候再继续发送数据呢?一直傻等着吗?答案显然是:No。
  • 当然,TCP规范中约定,即使对端为0窗口模式,但依旧接受以下几种报文:零窗口探测报文、确认报文和携带紧急数据的报文段。因此,为解决这种死锁问题,此时需要一个坚持定时器。TCP为每个连接设置一个坚持定时器,只要TCP连接一方收到对方的0窗口通知,就启用该定时器。若定时器到期,就发送一个零窗口探测报文(仅携带一个字节的数据),对方在ACK这个探测报文时设置当前的窗口大小。
  • 实验
    • 服务端代码(centos6.7)

e98cba134a248a7bc003d5e8ae129656.png
    • 客户端代码

247206acacfba8a4229faf4b79214666.png
    • 结果

b2310479353b329fc6a23c4d317ee16b.png

解决方法

可以通过SO_RCVBUF套接字来修改该TCP选项。(不建议)

延迟定时器(Delayed ACK)

开始

延迟应答定时器和ACK延滞(Delayed ACK Algorithm)算法息息相关。
ACK延滞算法思想是:TCP在接收到数据后并不立即发送ACK,而是等待一小段时间(典型值为50-200ms,即延迟定时器),然后才发送ACK。为提高网络传输效率,避免某一个时刻网络充斥着大量的ACK小报文,TCP期待在这一小段时间内自身有数据发送回对端,捎带ACK,从而节省一个TCP分节。延迟应答定时器就是为防止某一时刻没有数据,导致ACK一直等待不发送,造成死锁。

保活定时器(Keep Alive)

开始

  • 首先需要明确一点的是TCP中的Keep Alive选项目的旨在检测对端主机是否崩溃或不可达(譬如电源故障等),不同于HTTP的Keep Alive,HTTP1.1引入的目的是复用连接,也就是所谓的长连接,降低频繁创建连接(即短连接)带来的性能问题。

参数分析

  • 在Linux系统中,SO_KEEPALIVE选项涉及到三个相关参数,可以通过man tcp命令来查看。默认是7200s,即2小时

d8631825d87ab6861d30cf7e32380c08.png

TCP_KEEPDILE:设置连接上如果没有数据发送的话,多久后发送keepalive探测分组,单位是秒
TCP_KEEPINTVL :前后两次探测之间的时间间隔,单位是秒
TCP_KEEPCNT :关闭一个非活跃连接之前的最大重试次数

5cbdb68828b6d83049e1cb5e8b329556.png

实验与分析

客户端代码

6eb18311910a631239faf9ab972840ab.png

服务端代码见上

  • ==存在缺陷:==
    • ==2小时时间太长==
    • ==可能网络抖动,导致之前的分节丢失,实际上该连接依旧有效,导致对端误以为终止,进而结束一个有效连接。==
  • ==针对这个问题,目前解决方案两种:==
    • ==第一种:修改内核参数,但是必须要注意大多数内核是基于整个内核维护的这些时间参数的,而不是基于每个套接字,因此如果把无活动周期从2小时修改为自定义的值,将会影响到该主机上所有开启本选项的套接字(默认不开启该选项)。==
    • 第二种:避免使用该选项,应用层维护:
      • 心跳包(hearbeat)
      • 乒乓包(pingpong):和心跳包类似,除了携带标志位外,还可以携带少量数据==
      • 使用TCP的紧急数据位(URG)携带心跳包

FIN_WAIT_2定时器

开始

FIN_WAIT_2定时器发生在TCP四次挥手过程,首先来看一下TCP四次挥手。

d4f48dbd43024055637621ea1b3759e6.png

FIN-WAIT-1状态:首先终止一端发送完FIN分节点,等待对端回复ACK时的状态。该状态不应该过多,而且停留的时间很短。FIN-WAIT-2状态:收到对端的ACK确认后,在FIN-WAIT-1状态直接转至FIN-WAIT-2状态,此时等待对端的FIN包。有时ACK和FIN一起,此时会跳过该状态

参数分析

  1. 对于状态的查看可以使用命令:==netstat -nat|awk ‘{print awk $NF}’|sort|uniq -c|sort -n==来进行排序查看

4d325c8370d5c71a712fa7889791590e.png
  1. 如果对端一直不发送FIN分节,FIN-WAIT-2状态将可能会一直存在。但实际上内核参数tcp_fin_timeout会控制超时时间。本机(cento6.5)默认值为==60s==

6382f025439ab5e12214c058247fa04d.png
  1. 而且如果不是为了在半关闭状态下继续接受数据,连接长时间处于FIN-WAIT-2状态并无益处。其中若客户端执行半关闭后,未等服务器关闭连接就强行退出。此时该连接将由内核接管,被称之为孤儿连接。Linux为了防止孤儿连接长时间停留在内核中,将由两个内核参数来控制。

a3e34b497d16ba7390b5acfdb10a1361.png

9d7e101d293c2954673e3f2b0c2c23d1.png
  • tcp_max_orphan:表示最大孤儿连接数
  • tcp_fin_timeout:表示存活时间
  • tcp_orphan_retries:表示重试次数

TIME_WAIT定时器

开始

在tcp有关的网络编程中,最难理解且最容易碰到的就是TIME_WAIT状态了。由于TIME_WAIT时长为2MSL(最长分节生命周期),因此也被称为2MSL超时。
任何TCP实现都必须为MSL选择一个值。RFC1122建议为2m,但源自伯克利实现的系统,传统上改用30s。综合起来,TIME_WAIT状态将持续在1m到4m。MSL是任何IP数据报能够在因特网存活的最长时间,尽管IP报文含有一个8位、称为跳限的字段,最大值为255,但仍旧假设具有最大跳限的分组在网络上存在的时间不可能超过MSL秒。

分析

  1. 可靠的实现全双工连接的终止

d4f48dbd43024055637621ea1b3759e6.png

结合上图,假设客户端(客户端为主动断开一方)丢失了服务端发来的FIN,服务器必然会重新发送该分节,客户端必须要维护状态信息,以允许他重新发送最终的那个ACK。要是客户端不维护状态信息,他将会响应一个RST,该分节被服务器解释成一个错误。然而,此时可能还有数据流在传输。因此如果TCP打算执行所有必要的工作以彻底终止某个连接上两个方向的数据流。那么他必须正确处理连接终止序列4个分节中任何一个分节丢失情况。

  1. 允许老的重复分节在网络中消失

因为每个数据包在网络上最长生命周期为MSL,2MSL可以确任何一个数据包都将消失。这样可以防止来自某个连接的老的重复分组在该连接已终止后再现。因为端口、IP资源都会回收,下次在复用。想想,如果你结束了一次支付宝访问,紧接着我又用了你刚才的IP和端口号去请求同一个网站,原先来不及发送的消息结果被我接受了,我有可能就得到你的密码。

解决方法

  1. 使用SO_REUSEADDR和SO_REUSRPORT
  • SO_REUSRADDR选项而言:
    • 在所有TCP服务器程序中,调用bind之前设置SO_REUSRADDR套接字选项
    • 当编写一个可同一时刻在同一主机运行多次的多播应用程序时,设置SO_REUSRADDR套接字选项,并将锁参加的多播组的地址作为本地IP地址捆绑。(这个可以参照nginx的设计)
  • SO_REUSRPORT选项而言:
    • 主要是针对SO_REUSRADDR绑定通配地址和端口后,不能再绑定更为详细的IP和端口
  1. 使用SO_LINGER选项
    本选项指定close函数对面向连接的协议如何操作。默认操作是close立即返回,但是如果有数据残留在套接字发送缓冲区,系统将试着把这些数据发送给对端,然后在继续进行正常的FIN序列。
    然而SO_LINGER选项可以改变这个默认设置。其数据结构如下:
struct 

710843a947e9d4ed5eda56cbb387cfbb.png

引用

《TCP/IP详解》

《Unix网络编程:卷2》

《计算机网络自顶向下方法》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值