文丨Robbie_22


 

周一小拍为大家介绍了《TCP/IP漫游(上)》,本篇文章就具体的使用场景继续做详细的介绍,欢迎大家阅览~

 

 

流量控制

 

流量控制实际上是发送方发送和接收方处理速度匹配的过程。

TCP连接发送的报文段,都会放入上文所说的缓冲中等待应用程序取出。如果一端持续发送报文段,另一端一直没有及时处理完并接着取出报文段,就会造成缓冲溢出。这时,就需要进行双方的速度匹配,进行流量控制。接收方会将自己的缓冲剩余空间rwnd告诉发送方,发送方为了控制速度,只能再发送所得到的剩余空间rwnd容量的报文段。由于上文所说TCP采取的确认方式,发送方得到的这个rwnd容量不会限制已发送而未得到确认的报文段,这些报文段很可能已经在接收方的缓冲中了,只限制将要发送的报文段。

将传送数据看做数据流后,上面的这个过程就像在以序号作为基准在数据流上移动窗口一样,所以得名流量窗口。而剩余空间rwnd,也就是TCP header中的window位。

这里还有个问题,如果一方接收到了零剩余空间信息,这方就再也不发送报文了吗?不是,TCP为应对这个情况会有个计时器(persist timer),出现这种情况就会让计时器记时,当计时器触发,这方会发送个剩余空间探测报文段(window probe),以检测是否可以重新发送报文段。如果一直没有剩余空间,计时器永远不会终止,仍会做重新记时、超时的循环。在WireShark中通过

tcp.analysis.zero_window_probe

tcp.analysis.window_full

找出剩余空间探测报文段和通知发送方接收方空间已满的报文段。

 

拥塞控制(Congestion-Control)

 

拥塞控制很好理解,TCP如果不照顾网络总体状况,一股脑的传送数据的话,在极差的网络环境下只会恶性循环,可以直接搞瘫网络。而传送数据太小心的话又不能充分利用带宽资源。所以,拥塞控制就是一个动态平衡的策略。拥塞控制实际上就是一个由三个状态组成的有限状态机(FSM),这三个状态是慢启动(slow start)、拥塞避免(congestion avoid)、快速恢复(fast recovery)。

首先,聊这三个状态之前先聊一下TCP对拥塞的认识。

  •  TCP如何根据自己的有限信息判定网络拥塞?只要出现超时重传和3次冗余ACK引起的快速重传就认为网络拥塞了。
  • 网络拥塞也有程度的区分,TCP如何判定拥塞的程度?超时重传被认为拥塞程度强,快速重传被认为拥塞程度弱。
  • 相对于拥塞,TCP如何判断网络不拥塞?只要收到了非冗余的ACK,TCP就认为一切顺利,没有拥塞。
  • TCP如何判断快要拥塞了?TCP会在每次发生拥塞后,记录下导致发生拥塞的报文段的数量的一半,最小不能小于2单位(mss)报文段,这个值被用来衡量下次是否快要拥塞。

----------------------

正常收到ACK                              → 不拥塞

到了上次拥塞一半的量               → 快要拥塞

快速重传                                     → 拥塞程度弱

超时重传                                     → 拥塞程度强

----------------------

TCP有了这四个认识,就可以愉快地照顾网络总体情况了。TCP以cwnd标识能够发送的报文段的量,不用多说,拥塞控制整个过程也像是在数据流上移动窗口,所以也叫拥塞窗口。

首先,慢启动。

在慢启动阶段,TCP以cwnd为1作为初始量,然后每确认一个报文段,都会为cwnd加1。这样,如果TCP一直保持最大限度的发送报文段,每过一个RTT,TCP发送的报文段量就会翻倍。所以,在慢启动阶段,TCP是指数级增长。慢启动的语义是,现在对网络状态不是很清楚,先假设状态不好,一上来少发送点,然后多发点试探网络状况。

其次,拥塞避免。

当cwnd增长到快要拥塞的时候会状态迁移到拥塞避免。上文说到为标志快要拥塞会维护一个值ssthresh(slow start threshold),当cwnd大于等于ssthresh,慢启动迁移到拥塞避免状态。进入拥塞状态后,每确认一个报文段,都会为cwnd加1/cwnd。这样,如果TCP一直保持最大限度的发送报文段,每过一个RTT,TCP发送的报文段量会加1。所以,在拥塞避免状态,TCP是线性增长。拥塞避免的语义是,快要拥塞,小心一点。

然后,快速恢复。

如果出现了快速重传怎么办?不管是慢启动还是拥塞避免,都迁移到快速恢复。既然拥塞程度弱,那就适当的降低cwnd,将cwnd除2,并且维护ssthresh记录拥塞的量,将cwnd的值赋给ssthresh。发生快速恢复就说明出现了3次冗余ACK,TCP基于选择确认,认为引起3次冗余ACK的报文段顺利到达,将cwnd加上3个单位(mss)的量。如果再收到这个报文段的冗余ACK,为cwnd加1。如果收到了非这个报文段的冗余ACK,表明这个报文段正确到达了,将ssthresh赋给cwnd,并结束快速恢复,迁移到拥塞避免状态。所以,在快速恢复状态,TCP增长的量级在拥塞避免和慢启动之间。快速恢复的语义是,出了点小岔子,没问题稳一稳,回到正轨上后,既然出了点小岔子,那以后就小心一点。

最后,状态变迁。

现在已经有了对快速重传的处理。那超时重传怎么办?如果出现超时重传,无论在哪个状态都迁移到慢启动,将cwnd重置为1。这样,这三个状态都可以两两互相迁移到。TCP的拥塞控制就在迁移状态中度过。

最后,上图。

 

android 触发漫游RSSI阈值 触发漫游阀值_TCP

 

 

更多的问题

 

实际上到这里,TCP的核心已经聊完了。但是,是的,你没有猜错,TCP还有更多的问题。

 

 

糊涂窗口综合症(Silly-Window-Syndrome)与Nagle算法

 

流量控制很好的照顾了接收方,但是也引来了问题,如果接收方一直告诉发送方的剩余空间rwnd很小。那么发送方将一直发送内容很小的报文段。相对于TCP header20字节,如果每次内容只有个位数字节,那这样网络基本上就都在传输控制信息,网络使用率就太低了。这就出现了糊涂窗口综合症。而出现这种情况发送方和接收方自然也都有自己的应对办法。

对于接收方一般会使用David D Clark’s的策略,就是“欺骗”发送方,如果是剩余空间很小的情况,干脆就通告发送方剩余空间是零,这样发送方就不会再发送小内容了。等到剩余空间超过1单位或者剩余空间超过缓冲的一半的时候,再不“欺骗”发送方。

对于发送方会使用Nagle算法。就是对于小内容的停等协议。如果是小内容的话,要查看是否所有已发送的小内容都已被确认,都被确认才能发送,这就形成了对小内容的停止发送等待确认的协议。Nagle认为小内容就是小于1单位(mss)的量。

实际上Nagle算法不光是设计来解决发送方的糊涂窗口综合征,它还减轻了拥塞。它可以将多个等待的小内容合并成一个数据报发送。这样直接的减少数据报的数量,从而减轻了拥塞。

 

 

TCP保活(Keep-Alive)

 

TCP的保活与HTTP的长连接不是一个意思,HTTP的长连接是复用TCP连接,减少连接时延。而TCP的保活是检查连接的对方是否还响应。一般是服务器端对客户端经行保活,如果客户端不响应,服务器端就不浪费资源,断掉连接。TCP自然是使用计时器来实现保活,超时时间默认为两小时。如果对方无反应,每隔75秒需重试9次。有趣的是,如果对方重启了或者说崩溃后又恢复了,对方接到保护探测报文段后会设置RST flag(复位)返回给发送方,然后发送方会断掉连接。

 

 

总结

 

TCP协议是个可靠协议,通过序号、校验和、超时重传、快速重传、确认来做到这点。并且还要照顾接收方和网络总体状况,主要体现在流量控制和拥塞控制。它还是个会建立连接的协议,需要在双方记录一些状态去跟踪传输过程,并提供端到端的服务。

 

IP

 

IP协议最大的任务就是寻路,找到发往目的地的路径然后发送过去,也就是说IP协议提供“点到点”的服务。IP协议不是可靠传输协议,只能尽力将数据报(digram)发送到目的地。这也代表着,数据报和数据包之间是独立的,没有状态。相对与TCP协议像是可变数据,IP协议就像是不可变数据。实际上,IP协议无状态流就像是响应式编程。

 

 

IP寻路协议

 

先聊一下IP协议如何寻路,IP协议不可能一次性将数据报发送到目的地,必须经过多个中转站。如果要求一次性发送到目的地,要求双方有个独有的连接,然而为网络上所有人都建立这样一个连接是不可能的。并且这个中转站不可能强大到知道整个网络的拓扑结构,它只知道周围的节点的拓扑结构。

 

这就呈现出了IP寻路模型。路由器充当中转站的角色,主机和路由器都有一个路由表,路由表指示周围路由器的拓扑结构,就像一个地图一样,数据报通过查询路由表的结果寻路到下一个路由器。下一个路由器以同样方式负责寻路到再下一个路由器。这样,每一个路由器只负责到下一跳路由器(next-hop router)。最后IP协议通过多个路由器就到达了目的地。路由表不仅可以通过精确的目的地主机号寻路,还可以以子网的网络号寻路。当然还有保底的默认路径。

子网实际上作为比主机更大粒度的划分网络,以子网寻路可以极大的减少路由表的体积。相当于通过加大划分的粒度,减少了维护整个网络系统的成本。

IP协议寻路还有更多的问题。比如,主机也可以将数据报以发送给自己,当发现IP地址是自己时,数据报会交给以太网环回程序,环回程序将数据报加入本地的IP队列与其他数据报一视同仁。

主机可以被设置成路由器转发数据报,如果主机接收到了不是自己IP地址的数据报,只要被设置可以转发出去。但是如果没有寻路到下一跳怎么办?主机要返回一个ICMP(网络控制消息协议),代表差错。

ICMP还可以用来重定向,比如说主机想发送一个数据报到目的地,可以发送给A和B,寻路的结果下一跳为A,主机发送给了A。A寻路的下一跳为B,发送给了B,A可以侦测出这个情况,然后发送给主机一个重定向ICMP,让主机的路由表修改为寻路到B。

 

 

数据分片(IP-Fragmentation)

 

当数据报量超过了MTU怎么办?对比于TCP的分段,IP要分片。然而,这两个步骤互不干扰,是完全隔离开的。IP分片后,接收方接收到数据报后,将分片要重新组合起来。IP分片对于UDP协议比较有用,对TCP没有太大用处,TCP更希望自己来分段,而不靠IP去分片。IP不是个可靠协议,如果分片其中的一片出了问题,TCP也无法重传单个分片,自然TCP就更希望自己来分段,做到重传单个分段。

 

 

IP Header

 

IP协议分为IPv4和IPv6版本,两种版本header不相同,版本在Version区域区分。

首先是IPv4。

android 触发漫游RSSI阈值 触发漫游阀值_TCP_02

其次是IPv6。

android 触发漫游RSSI阈值 触发漫游阀值_TCP_03

这里,就捡几个体现出IP服务的说一下,相比于TCP。IP协议的header没有那么能体现出IP协议的特点。

IPv4 header一般为20字节,IPv6 header一般为40字节。IPv4中address为32位,而IPv6增大到了128位。这样就从address分配紧张到地球上的每一颗砂砾都能有IP address了!

TTL和Hop Limit都是表示IP协议还能跳的路由器数量,如果为零了,则数据报会被丢弃,并返回一个ICMP通知源主机。Traceroute程序就是这样收集数据报被丢弃后发送的ICMP实现的。

IPv4用Identification唯一识别数据报(分片数据报相同),Fragment offset标识分片的起始位置。而在IPv6中都可以用更加灵活的Next Header表示,Next Header就像链表一样,可以连接多个“Header”,拓展出多个Header。除了分片的起始位置、还可以表示同IPv4 protocol一样能表示的上层协议。