zoukankan      html  css  js  c++  java
  • Tcp问题汇总

    一 TCP三次握手



    PS:TCP协议中,主动发起请求的一端称为『客户端』,被动连接的一端称为『服务端』。不管是客户端还是服务端,TCP连接建立完后都能发送和接收数据。

    起初,服务器和客户端都为CLOSED状态。在通信开始前,双方都得创建各自的传输控制块(TCB)。 
    服务器创建完TCB后遍进入LISTEN状态,此时准备接收客户端发来的连接请求。

    第一次握手 
    客户端向服务端发送连接请求报文段。该报文段的头部中SYN=1,ACK=0,seq=x。请求发送后,客户端便进入SYN-SENT状态。

    • PS1:SYN=1,ACK=0表示该报文段为连接请求报文。
    • PS2:x为本次TCP通信的字节流的初始序号。 
      TCP规定:SYN=1的报文段不能有数据部分,但要消耗掉一个序号。

    第二次握手 
    服务端收到连接请求报文段后,如果同意连接,则会发送一个应答:SYN=1,ACK=1,seq=y,ack=x+1。 
    该应答发送完成后便进入SYN-RCVD状态。

    • PS1:SYN=1,ACK=1表示该报文段为连接同意的应答报文。
    • PS2:seq=y表示服务端作为发送者时,发送字节流的初始序号。
    • PS3:ack=x+1表示服务端希望下一个数据报发送序号从x+1开始的字节。

    第三次握手 
    当客户端收到连接同意的应答后,还要向服务端发送一个确认报文段,表示:服务端发来的连接同意应答已经成功收到。 
    该报文段的头部为:ACK=1,seq=x+1,ack=y+1。 
    客户端发完这个报文段后便进入ESTABLISHED状态,服务端收到这个应答后也进入ESTABLISHED状态,此时连接的建立完成!

    为什么连接建立需要三次握手,而不是两次握手? 
    防止失效的连接请求报文段被服务端接收,从而产生错误。

    PS:失效的连接请求:若客户端向服务端发送的连接请求丢失,客户端等待应答超时后就会再次发送连接请求,此时,上一个连接请求就是『失效的』。

    若建立连接只需两次握手,客户端并没有太大的变化,仍然需要获得服务端的应答后才进入ESTABLISHED状态,而服务端在收到连接请求后就进入ESTABLISHED状态。此时如果网络拥塞,客户端发送的连接请求迟迟到不了服务端,客户端便超时重发请求,如果服务端正确接收并确认应答,双方便开始通信,通信结束后释放连接。此时,如果那个失效的连接请求抵达了服务端,由于只有两次握手,服务端收到请求就会进入ESTABLISHED状态,等待发送数据或主动发送数据。但此时的客户端早已进入CLOSED状态,服务端将会一直等待下去,这样浪费服务端连接资源。

    二 TCP四次挥手

    TCP连接的释放一共需要四步,因此称为『四次挥手』。 
    我们知道,TCP连接是双向的,因此在四次挥手中,前两次挥手用于断开一个方向的连接,后两次挥手用于断开另一方向的连接。

    第一次挥手 
    若A认为数据发送完成,则它需要向B发送连接释放请求。该请求只有报文头,头中携带的主要参数为: 
    FIN=1,seq=u。此时,A将进入FIN-WAIT-1状态。

    • PS1:FIN=1表示该报文段是一个连接释放请求。
    • PS2:seq=u,u-1是A向B发送的最后一个字节的序号。

    第二次挥手 
    B收到连接释放请求后,会通知相应的应用程序,告诉它A向B这个方向的连接已经释放。此时B进入CLOSE-WAIT状态,并向A发送连接释放的应答,其报文头包含: 
    ACK=1,seq=v,ack=u+1。

    • PS1:ACK=1:除TCP连接请求报文段以外,TCP通信过程中所有数据报的ACK都为1,表示应答。
    • PS2:seq=v,v-1是B向A发送的最后一个字节的序号。
    • PS3:ack=u+1表示希望收到从第u+1个字节开始的报文段,并且已经成功接收了前u个字节。

    A收到该应答,进入FIN-WAIT-2状态,等待B发送连接释放请求。

    第二次挥手完成后,A到B方向的连接已经释放,B不会再接收数据,A也不会再发送数据。但B到A方向的连接仍然存在,B可以继续向A发送数据。

    第三次挥手 
    当B向A发完所有数据后,向A发送连接释放请求,请求头:FIN=1,ACK=1,seq=w,ack=u+1。B便进入LAST-ACK状态。

    第四次挥手 
    A收到释放请求后,向B发送确认应答,此时A进入TIME-WAIT状态。该状态会持续2MSL时间,若该时间段内没有B的重发请求的话,就进入CLOSED状态,撤销TCB。当B收到确认应答后,也便进入CLOSED状态,撤销TCB。

    为什么A要先进入TIME-WAIT状态,等待2MSL时间后才进入CLOSED状态? 
    为了保证B能收到A的确认应答。 
    若A发完确认应答后直接进入CLOSED状态,那么如果该应答丢失,B等待超时后就会重新发送连接释放请求,但此时A已经关闭了,不会作出任何响应,因此B永远无法正常关闭。

    三 流量控制

    1 滑动窗口

    数据的传送过程中很可能出现接收方来不及接收的情况,这时就需要对发送方进行控制以免数据丢失。利用滑动窗口机制可以很方便地在TCP连接上对发送方的流量控制。TCP的窗口单位是字节,不是报文段,发送方的发送窗口不能超过接受方给出的接收窗口的数值

    说明: 使发送方暂停发送的状态将持续到主机B重新发出一个新的窗口值为止,B向A发送的三个报文段都设置了ACK=1

    考虑一种特殊的情况,接收方若没有缓存足够使用,就会发送零窗口大小的报文,此时发送方将发送窗口设置为0,停止发送数据; 之后接收方有足够缓存,发送了非零窗口大小的报文,但是这个报文中途丢失,那么发送方的发送窗口就一直为0导致死锁。为此,TCP为每一个连接设有一个持续计时器(Persistence Timer).当TCP连接的一方收到对方的零窗口通知时就启动持续计数器。若持续计时器时间到期,就发送一个零窗口探测报文段(携有1字节的数据),那么收到这个报文段的一方就重新设置持续计数器,给出现在的窗口值。

    TCP规定,即使设置为零窗口,也必须接收以下几种报文段: 零窗口探测报文段, 确认报文段和携带紧急数据的报文段

    2 发送时机

      (1) TCP维持一个变量MSS,等于最大报文段长度。只要缓冲区存放的数据达到MSS字节时,就组装成了一个TCP报文段发送出去。 
      (2) 由发送方的应用进程指明要发送的报文段,即:TCP支持推送操作。 
      (3) 发送方的一个计时器期限到了,这时就把当前已有的缓存数据装入报文段(但长度不能超过MSS大小)发送出去。

    3 Nagle算法

    发送方把第一个数据字节发送出去,把后面到达的数据字节缓存起来。当发送方接收对第一个数据字符的确认后,再把发送缓存中的所有数据组装成一个报文段再发送出去,同时继续对随后到达的数据进行缓存。只有在收到前一个报文段的确认后,才继续发送下一个报文段。TCP规定一个连接最多只能有一个未被确认的未完成的小分组,在该分组的确认到达之前不能发送其他的小分组。当数据到达较快而网络速率较慢时,用这样的方法可明显地减少所用的网络带宽。

    Nagle算法还规定: 当到达的数据已达到发送窗口大小的一半或已经达到报文段的最大长度,就可立即发送一个报文段

    4 延迟ACK:

    如果tcp对每个数据包都发送一个ack确认,那么只是一个单独的数据包为了发送一个ack代价比较高,所以tcp会延迟一段时间,如果这段时间内有数据发送到对端,则捎带发送ack,如果在延迟ack定时器触发时候,发现ack尚未发送,则立即单独发送;

    延迟ACK好处:

    (1) 避免糊涂窗口综合症;

    (2) 发送数据的时候将ack捎带发送,不必单独发送ack;

    (3) 如果延迟时间内有多个数据段到达,那么允许协议栈发送一个ack确认多个报文段;

    5  当Nagle遇上延迟ACK:

    试想如下典型操作,写-写-读,即通过多个写小片数据向对端发送单个逻辑的操作,两次写数据长度小于MSS,当第一次写数据到达对端后,对端延迟ack,不发送ack,而本端因为要发送的数据长度小于MSS,所以nagle算法起作用,数据并不会立即发送,而是等待对端发送的第一次数据确认ack;这样的情况下,需要等待对端超时发送ack,然后本段才能发送第二次写的数据,从而造成延迟

    6 糊涂窗口综合症 
    TCP接收方的缓存已满,而交互式的应用进程一次只从接收缓存中读取1字节(这样就使接收缓存空间仅腾出1字节),然后向发送方发送确认,并把窗口设置为1个字节(但发送的数据报为40字节的的话)。然后,发送方又发来1个字节的数据(发送方的IP数据报是41字节),接收方发回确认,仍然将窗口设置为1个字节。这样,网络的效率很低。要解决这个问题,可让接收方等待一段时间,使得或者接收缓存已有足够空间容纳一个最长的报文段或者等到接收方缓存已有一半的空闲空间。只要出现这两种情况,接收方就发回确认报文,并向发送方通知当前的窗口大小。此外,发送方也不要发送太小的报文段,而是把数据报积累成足够大的报文段,或达到接收方缓存的空间的一半大小。

    四 拥塞控制

    网络拥塞现象是指到达通信子网中的某一部分数量过多,使得该部分网络来不及处理,以致引起这部分乃至整个网络性能下降的现象,严重时导致网络通信业务陷入停顿出现死锁现象。拥塞控制时通过拥塞窗口处理网络拥塞现象的一种机制

    发送报文段速率确定: 
       [1]. 全局考虑防止拥塞 <- - 拥塞窗口 (Congestion Window) - -> 发送端流量控制,发送端根据自己估计的网络拥塞程度而设置的窗口值; 
       [2]. 接收端的接收能力 <- - 接收窗口 (Reciver Window) - -> 接收端流量控制,接收端根据目前的接收缓存大小所许诺的最新窗口值;
    
        发送方窗口的上限值 = Min [ rwind, cwind ] 
      rwind < cwind 时,接收方的接收能力限制发送方窗口的最大值。 
      当cwind < rwind 时,网络的拥塞限制发送方窗口的最大值。

    针对拥塞控制共有4种算法: 慢启动 拥塞避免 快重传 快恢复。我们假定:(1) 数据单方向传送,而另外一个方向只传送确认。  (2)接收方总是有足够大的缓存空间,因为发送窗口的大小由网络的拥塞程度来决定

     1 慢启动

    发送方维护一个拥塞窗口cwind的状态变量,拥塞窗口的大小取决于网络的拥塞程度,动态变化。通过逐渐增加cwind的大小来探测可用的网络容量,防止连接开始时采用不合适的发送量导致网络拥塞。

    当主机开始发送数据时,如果通过较大的发送窗口立即将全部数据字节都注入到网络中,由于不清楚网络状况,有可能引起网络拥塞。较好的方法是试探,从小到大逐渐增大发送端拥塞窗口的cwind数值。

    例子: 开始发送方先设置cwnd=1,发送第一个报文段M1,接收方接收到M1后,ACK返回给发送端,发送端将cwnd增加到2,接着发送方发送M2,M3,再次接受到ACK后将cwnd增加到4...慢启动算法每经过一个传输轮次,拥塞窗口cwind就加倍。

    当rwind足够大时,为防止拥塞窗口cwind的增长引起网络拥塞,还需要另外一个变量,慢开始门限ssthresh

    当cwind < ssthresh时,使用上述慢启动算法;

    当cwind > ssthresh时,停止使用慢启动算法,改为拥塞避免算法

    2 拥塞避免

    让拥塞窗口cwind缓慢地增大,没经过一个往返时间RTT就把发送方的拥塞窗口cwind+1, 而不是加倍。这样拥塞窗口cwind线性缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢地多。

    无论慢启动开始阶段还是在拥挤避免阶段,只要发送方判断网络出现拥塞(没收到ACK),就把慢启动门限ssthresh设置为出现拥塞时的cwind的一半。然后把拥塞窗口cwind重新设置为1,执行慢启动算法。目的是迅速减少主机发送到网络中的分组树,使得发生阻塞的路由器有足够的时间把队列中积压的分组处理完毕

    控制过程(见上图):

    (1) TCP连接初始化,将拥塞窗口cwind设置为1个报文段,即cwind = 1

    (2) 执行慢开始算法,cwind按指数规律增长,直到cwind == ssthresh时,开始拥塞避免算法,cwind按线性规律增长

    (3) 当网络发生阻塞,把ssthresh值更新为拥塞前cwind的一半(12 = 24/2),cwind重新设置为1,再按照(2)执行

    注意⚠️:

    拥塞避免是由指数增长拉低到线性增长,降低出现拥塞的可能,并不是能完全避免网络拥塞

    慢开始算法只是在TCP连接建立和网络出现超时时才使用

    3 快重传和快恢复

    一条TCP连接有时会因等待重传计时器的超时而空闲较长的时间,慢开始和拥塞避免无法很好地解决这类问题,因此提出了快重传和快恢复的拥塞控制方法

    为使发送方及早知道有报文没有达到对方,快重传算法首先要求接受方每收到一个报文段后就立即发出重复确认。快重传算法并非取消了重传机制,只是在某些情况下更早地重传丢失的报文段。即,当TCP源端收到3个相同的ACK确认时,即认为有数据包丢失,则源端重传丢失的数据包,而不必等待RTO(Retransmission Timeout)超时。由于发送方尽早重传未被确认的报文段。因此,采用快重传后可以使整个网络吞吐量提高20%

    快重传算法要求首先接收方收到一个失序的报文段后就立刻发出重复确认,而不要等待自己发送数据时才进行捎带确认。接收方成功的接受了发送方发送来的M1、M2并且分别给发送了ACK,现在接收方没有收到M3,而接收到了M4,显然接收方不能确认M4,因为M4是失序的报文段。如果根据可靠性传输原理接收方什么都不做,但是按照快速重传算法,在收到M4、M5等报文段的时候,不断重复的向发送方发送M2的ACK,如果接收方一连收到三个重复的ACK,那么发送方不必等待重传计时器到期,由于发送方尽早重传未被确认的报文段。

    控制过程(见上图):

    (1) 当发送方连续收到3个重复确认时,执行"乘法减小"算法,慢启动门限减半,为了预防网络发生拥塞

    (2) 由于发送方现在认为网络很可能没有发生拥塞,因此不执行慢启动。而是把cwind值设为新的门限值,然后执行拥塞避免算法,cwind值线性增大,避免了当网络拥塞不够严重时采用"慢启动"算法而造成过大地减小发送窗口尺寸的现象,这就是快恢复。

    五 其他问题

    1 三次握手/四次挥手几个字段含义

    在TCP/IP协议中,有个FLAGS字段,这个字段有以下几个标识: SYN, FIN, ACK, PSH, RST, URG, seq, ack等

    SYN(synchronous)         表示建立连接

    FIN(finish)                     表示关闭连接

    ACK(acknowledgement)  表示响应

    PSH(push)                     表示有数据传输

    RST(reset)                     表示连接重置

    URG(urgent)                  表示紧急

    seq(sequence number)    表示确认号码

    ack(acknowledge number) 表示确认号码

    2 长连接 VS 短连接

    短连接:
    • 建立连接——数据传输——关闭连接
    • 建立连接——数据传输——关闭连接
    长连接:
    • 建立连接——数据传输...(保持连接)...数据传输——关闭连接 

    长连接可以省去较多TCP建立和关闭的操作,减少浪费,节约资源。对于频繁请求资源的客户来说,适合使用长连接。不过这里存在一个问题,存活功能的探测周期太长,还有就是它知识探测TCP连接的存活,遇到恶意的连接时,保活功能就不够使了。在长连接的应用场景下,client端一般不会主动关闭它们之间的连接,client与server之前的连接如果一直不关闭的话,会存在一个问题,随着客户端连接越来越多,server会扛不住,这时候server端需要采取一些策略:

    (1) 关闭一些长时间没有读写事件发生的连接,这样可以避免一些恶意连接导致server端服务受损

    (2) 如果条件再允许可以以"客户端机器"粒度,限制每个客户端的最长连接数,这样可以完全避免某个蛋疼的客户端连累后端服务

    相比之下,短链接对于服务端来说管理比较简单,存在的连接都是有用的连接,不需要额外的控制手段。但如果客户请求频繁,将在TCP的建立和关闭操作上浪费时间和带宽。

    3 长/短连接的应用场景

    长连接多用于操作频繁,点对点的通讯,而且连接数不能太多的情况。每个TCP连接都需要三次握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,再次处理时直接发送数据包就OK了,不用建立TCP连接。例如: 数据库的连接用长连接,如果用短连接频繁的通信会造成socket错误,而且频繁的socket创建也是对资源的浪费。

    而像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。所以并发量大,但每个用户无需频繁操作情况下需用短连好。

  • 相关阅读:
    LeetCode 222.完全二叉树的节点个数(C++)
    LeetCode 704.二分查找(C++)
    LeetCode 441.排列硬币(C++)
    LeetCode 981.基于时间的键值存储(C++)
    LeetCode 167.两数之和(C++)
    LeetCode 367.有效的完全平方数(C++)
    LeetCode 881.救生艇(C++)
    LeetCode 860.柠檬水找零(C++)
    LeetCode 870.优势洗牌(C++)
    bootstrap
  • 原文地址:https://www.cnblogs.com/balfish/p/8671027.html
Copyright © 2011-2022 走看看