美文网首页计算机基础
tcp/ip 需要知道的一些事

tcp/ip 需要知道的一些事

作者: 砺豪 | 来源:发表于2016-12-19 22:06 被阅读450次

    本文主要是最近看了以下《tcp/ip详解 卷1》的部分小结,个人最感兴趣的是 经典问题 40ms 延时 的问题!
    个人建议着重看19-24章节。每章都有干货。

    单播、多播、广播、组播、泛播概念区分

    参考

    ICMP

    ICMP的作用

    • 差错控制
    • “Ping”的过程实际上是ICMP协议工作的过程。还有其他的网络命令如跟踪路由的Traceroute命令也是

    tcp

    几个概念

    msl 最长报文段存活时间
    mss 最大报文段长度 ,主要是为了防止报文端被分段发送。 如果超过1mss 将会被分段发送。
    定时器

    1. 重传定时器
    2. 坚持(presist)定时器 窗口探查
    3. 保活(keepalive)定时器 就是心跳检测
    4. 2MSL定时器 测量一个连接处于TIME_WAIT状态的时间。
    • 状态流转


    常见的情况,比如server关了,端口依然占用,其实就是处于TIME_WAIT状态 ,2MSL超时,用于超时重传最后的ack。

    • 接收窗口和发送窗口的区别
      接收窗口是接收方发给发送方的,(就是抓包看到的win= ),
      发送窗口是由sender根据receiver的接收窗口以及当前已发送未ack的数据来自己计算的。tcp抓包看不到
    • tcp 滑动窗口和拥塞窗口的区别
      滑动窗口:滑动窗口不是真实存在的窗口,需要sender和recever共同作用。通告窗口(window offered by receiver) ,也就是我们抓包看到的win=xxx,是接收方使用的流量控制。
    Paste_Image.png Paste_Image.png

    我们来分析一下1的情况,当sender 连续发送数据给receiver的时候,如果receiver的应用层来不及把数据从内核recv buffer拷贝到应用buffer,那么recv buffer会爆满,这时候receiver ack win=0。然后sender会把数据一直拷贝到send buffer而send buffer不发给receiver,直到receiver ack win> 0。

    由此可见,通告窗口win就是recv buffer的剩余空间。

    具体例子的分析可见 《tcp/ip 详解1》p213 。我们可以简单根据这个抓包的时序对滑动窗口进行分析。尤其我们看到 报文段8 win=3072.说明在receiver的recv buffer中还有1024个字节未被应用层buffer拷贝过去。因此在图2中可以看到的滑动窗口的通告窗口就是3072。


    图1 图2

    其实我们仔细想想看这里貌似存在一个缺陷,如果receiver win=0 之后 通告的win>0 的ack报文段(比如图1的报文段8)丢失了,那sender岂不是要一直等下去?
    这个问题设计者早就发现了,采用了一个tcp坚持定时器。 sender周期性的向receiver 查询,以便发现窗口是否已经增大。 这些sender发出去的报文段叫做窗口探查。

    **tcp window scale ** :

    由于缓冲区大小在TCP头部只有16位来表示,所以它的最大值是65536,但是对于一些情况来说需要使用更大的滑动窗口,这时候就要使用扩展的滑动窗口,如光纤高速通信网络,或者是卫星长连接网络,需要窗口尽可能的大。这时会使用扩展的32位的滑动窗口大小。

    shift count 在tcp三次握手的时候确认
    最终的calculated window size =2^7 × 227 =128 * 227 都可在上面找到对应的数值。

    拥塞窗口:是发送端维护的一个窗口,是发送方使用的流量控制。主要就是维护cwnd。下面可以看到分析。

    慢启动和拥塞避免

    超时重传采用“指数退避算法”
    慢启动和拥塞避免是两个独立的算法,但通常一起作用。
    需要维持的变量:

    • 拥塞窗口 cwnd(默认初始化为1)
    • 慢启动门限 ssthresh(默认初始化为65535字节)

    拥塞算法:

    Paste_Image.png

    当拥塞发生时(超时或收到重复确认) ssthresh被设置为当前窗口大小的一半( cwnd和接收方通告窗口大小的最小值,但最少为 2个报文段) 。此外,如果是超时引起了拥塞,则c w n d被设置为1个报文段(这就是慢启动) 。

    Paste_Image.png

    前半段是慢启动(由cwnd=1开始可判断),后半段是拥塞避免。此时ssthresh是16个报文段大小。
    拥塞避免的公式:

    Paste_Image.png

    图中都是收到重复ack的 拥塞避免。从图中我们可以清晰的看到每个序号下降的地方就是一次重传,共有3处,而且每处都只有一个下落点,因此可以得出每处只重传了一个报文段。
    对于cwnd的曲线,在每处发现重传后,就


    Paste_Image.png

    详细分析参看《tcp/ip详解》21章

    延迟确认和Nagle算法

    延迟确认:接收端收到数据之后,并不立即发送ACK确认收到数据,而是延迟发送ACK,等待一段时间,以期望和沿该方向传送的数据一起发送。是为了提高网络性能,因为少发了几次,然而,在某些情况下,该技术可以降低应用程序的性能。

    Nagle算法:尽可能发送大块数据,避免使网络中充斥小分组,从而减少发送包的个数来增加网络的利用率。Nagle算法要求在任意时刻,最多有一个未被确认的分组,在收到确认之前,小分组将被缓存在发送端。直到缓存了一定量,等待了一定的时间,或者收到前一个数据的确认,才能发送出去。由TCP_NODELAY选项控制。

    我觉得: 这两个主要是为了解决传输中 连续数据包都很小的情况,为了优化性能,而采用延迟确认和nagle,这样在发的时候可以屯多一点数据一起发,应答的时候,把数据和ack一起带过去。但是对于传输成块数据的情况就不太适用了,这两个默认都是打开的,要注意适时关闭!!,否则会带来数据传输延迟。所以一般我们实际工作中遇到由200ms以内的延时,可以怀疑下和tcp的延时ack,nagle算法有无关系。

    经典问题 40ms 延时

    产生条件

    1. 延迟确认与Nagle算法相互作用
    2. 延迟确认与拥塞控制相互作用

    延迟确认与Nagle算法相互作用

    • 延时ack

    如果收到的数据内容大于一个MSS, 发送ACK;(大部分情况是这个)
    如果收到了接收窗口以外的数据, 发送ACK;
    如果处于quick mode, 发送ACK;
    如果收到乱序的数据, 发送ACK;
    其他, 延迟发送ACK

    延迟ack其实是动态变化的,跟 Pingpong 这个值有关。
    Pingpong是一个状态值, 用来标识当前tcp交互的状态, 以预测是否是W-R-W-R-W-R这种交互式的通讯模式(交互数据流), 如果处于(=1), 可以用延迟ack。
    我们来看以下这个图。


    当检测到是交互数据流时 ,Pingpong=1,启用了延时ack。当出现一个延迟纯ack时取消交互模式 Pingpong=0;
    • Nagle算法

    如果发送内容大于等于1个MSS, 立即发送;
    如果之前没有包未被确认, 立即发送;
    如果之前有包未被确认, 缓存发送内容;
    如果收到ack, 立即发送缓存的内容。
    如果该包含有FIN,则允许发送;
    设置了TCP_NODELAY选项,则允许发送;

    接下来分析以一下下面的情况:

    1 00:44:37.878027 IP 172.25.38.135.44792 > 172.25.81.16.9877: S 3512052379:3512052379(0) win 5840 <mss 1448,wscale 7> 
    2 00:44:37.878045 IP 172.25.81.16.9877 > 172.25.38.135.44792: S 3581620571:3581620571(0) ack 3512052380 win 5792 <mss 1460,wscale 2> 
    3 00:44:37.879080 IP 172.25.38.135.44792 > 172.25.81.16.9877: . ack 1 win 46 
     ...... 
    4 00:44:38.885325 IP 172.25.38.135.44792 > 172.25.81.16.9877: P 1321:1453(132) ack 1321 win 86 
    5 00:44:38.886037 IP 172.25.81.16.9877 > 172.25.38.135.44792: P 1321:1453(132) ack 1453 win 2310 
    6 00:44:38.887174 IP 172.25.38.135.44792 > 172.25.81.16.9877: P 1453:2641(1188) ack 1453 win 102 
    7 00:44:38.887888 IP 172.25.81.16.9877 > 172.25.38.135.44792: P 1453:2476(1023) ack 2641 win 2904 
    8 00:44:38.925270 IP 172.25.38.135.44792 > 172.25.81.16.9877: . ack 2476 win 118
    9 00:44:38.925276 IP 172.25.81.16.9877 > 172.25.38.135.44792: P 2476:2641(165) ack 2641 win 2904 
    10 00:44:38.926328 IP 172.25.38.135.44792 > 172.25.81.16.9877: . ack 2641 win 134
    

    前三个报文段是三次握手,可以看出172.25.81.16.9877 为server,172.25.38.135.44792为client
    4,5,6,7 是client和server互相交互数据流,并且8 出现了40ms延时ack,这个流程符合我们上面的流程分析。我们可看到延时ack是在client端开启了,server端开启了nagle算法。
    在报文段5到达client时,client就知道了这是交互数据流,所以开启延时ack。当7发给client时,由于cleint并不需要发送什么数据给server,所以ack就一直等着定时器超时溢出,我们看到这时候延迟了40ms。
    由于8报文段,即字节序号2476的ack一直没来,并且server端开启着nadle算法(如果之前有包未被确认, 缓存发送内容),所以报文段9 在收到8后才发。
    40ms 这个是时间不同系统可能默认不同,rethat 默认40ms

    解决延时的方案
    对于这个例子而言
    client

    1. client端关闭延迟ack
      但是, 每个tcp请求都返回一个ack包, 导致网络包量的增加,需要斟酌
    2. 设置TCP_QUICKACK属性。 但是需要每次recv后再设置一次。

    server

    1. 关闭nagel算法,即设置TCP_NODELAY。
    staticvoid_set_tcp_nodelay(intfd) {
    intenable =1;
    setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,(void*)&enable,sizeof(enable));
    }
    

    但这样会导致网络中出现很多小于1mss的报文段,因此最好能将这些小报文段先缓存起来,大于1mss的时候再发出去,这样这个mss 会被分段,1个正好是1mss,1个小于1mss,cilent连续收到这两个分段后,判断接受到的数据大于1mss,因此,立即发送ack。无延迟。

    延迟确认与拥塞窗口相互作用

    前提发送方(关闭Nagle),接收方(启用延时ack)
    同样分析,即比如接收方mss大小1460 ,发送方连续发了三个数据大小为100的报文段,此时到达了拥塞窗口cwnd=3,不能在发送了,那么接收方接受到300<1460 ,所以接收方也会出现40ms延时ack。
    缩短延时方案
    参考 http://www.jianshu.com/p/6f795599e4ab * 为什么是40ms?这个时间能不能调整呢?* 这段。

    【参考文章】

    总结TCP的一些算法或者实现方式,在我们平常开发中的应用和可借鉴的地方

    1. 滑动窗口
    2. 超时重传
    3. 延迟确认和Nagle算法 :优化延时

    [附送 好文推荐]

    关注我的公众号,看更多好文!


    相关文章

      网友评论

        本文标题:tcp/ip 需要知道的一些事

        本文链接:https://www.haomeiwen.com/subject/eoptvttx.html