计算机网络_TCP

241 阅读10分钟

TCP包首部

  • TCP状态位

    • URG 紧急指针
    • ACK 确认序号有效
    • PSH 接收方应该尽快将这个报文段交给应用层
    • RST 连接重置(Reset)
    • SYN 同步序号用来发起一个连接.
    • FIN 发段完成发送任务
  • 初始序列号: TCP作为一个可靠的传输协议,无论在什么样的网络下,都要确保可靠的传输层.在传输过程中可能会遇到的问题包括:

    • 重复发送数据包
    • 数据包丢失
    • 数据包乱序
  • 为了解决上述问题:TCP中加入了序列号.有了序列号,我们就可以:

    • 通过对比序列号进行去重复
    • 通过序列号没被对方接受到的时候重复发送(seq=J之后没收到ack=J+1)
    • 通过序列号来重新排序数据包.
  • 序列号是在第一次客户端请求建立连接时候初始化的.seq就是序列号.对方在收到SYN消息之后通过ack=seq+1,来应答.

三次握手

  • 第一次握手:客户端将标志位SYN(Synchronize Sequence Numbers)置为1,随机产生一个值seq=J(该序列编号为TCP连接初始端/客户端的初始序列编号),并将该数据包发送给服务器端,客户端进入SYN_SENT状态,等待服务器端确认.

  • 第二次握手:服务器端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务器端将标志位ACK(Acknowledgement Number)置为1, ack=J+1,确认连接请求.并发送SYN=1和随机产生一个值seq=K,发送给客户端请求建立连接.

  • 第三次握手:客户端收到确认后,检查ack是否为J+1,ACK是否为1.如果正确则将标志位ACK置为1,ack=K+1.并将该数据包发送给服务器端,服务器端检查ack是否为K+1,ACK是否为1.如果正确则建立连接成功,客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了.

  • 这里提问:为什么TCP连接需要3次握手?

    • The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.
    • 主要原因是为了防止历史重复连接初始化造成的混乱问题,防止使用 TCP 协议通信的双方建立了错误的连接.这是RFC793中给出了答案
  • 两次握手建立连接的话:客户端发seq=100, 网络环境差,服务端很久之后收到了.返回ack=101.建立连接.但是这个客户端发的seq=100的请求建立连接有可能是800年前的了.已经丢弃了的,但是服务端不知道,会一直等对方发东西过来.这就导致了资源的浪费.

  • 四次握手建立连接的话:客户端首先发seq=100, 客户端收到服务端的ack=101, 客户端收到服务端的seq=200(表示服务端也请求建立连接),客户端发ack=101作为应答.确实可以建立连接. 但是中间的两步可以省略为一步.也就是3次握手中的第二步(SYN=1, ACK=1, ack=J+1, seq=K)

四次挥手

  • 中断连接端可以是客户端,也可以是服务器端.

  • 第一次挥手:客户端发送一个FIN=M,用来关闭客户端到服务器端的数据传送,客户端进入FIN_WAIT_1状态.意思是说"我客户端没有数据要发给你了",但是如果你服务器端还有数据没有发送完成,则build着急关闭连接,可以继续发送数据.

  • 第二次挥手:服务器端收到FIN后,先发送ack=M+1,告诉客户端,你的请求我收到了.但是我还没准备好,请继续等我的消息.这个时候客户端就进入FIN_WAIT_2状态,继续等待服务器端的FIN报文.

  • 第三次挥手:当服务器端确定数据已发送完成,则向客户端发送FIN=N报文,告诉客户端,好了,我这边数据发完了,准备好关闭连接了.服务器端进入LAST_ACK状态.

  • 第四次挥手:客户端收到FIN=N报文后,就知道可以关闭连接了,但是他还是不相信网络,怕服务器端不知道要关闭,所以发送ack=N+1后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传.服务器端收到ACK后,就知道可以断开连接了.客户端等待了2MSL后依然没有收到回复,则证明服务器端已正常关闭,那好,我客户端也可以关闭连接了.最终完成了4次挥手.

  • 这里提问:为什么要4次挥手?

    • TCP是全双工模式,当客户端向服务端发送FIN的时候,表示客户端已经没有要发给服务端的消息了.但是这时候客户端还可以接受来自服务端的数据.服务端发ACK表示知道客户端的数据已经全部发完了.等服务端发完所有消息之后,也会向客户端发一个FIN,表示服务端也发完了.客户端返回ACK表示知道了.至此TCP连接断开.
    • 这里还有一点.当客户端发送完ACK之后要再等待2MSL,因为如果网络环境差,导致最后客户端发的ACK服务端没收到的话,服务端会继续发FIN,如果不等待直接close的话就不能够正确的断开连接了. 而且如果

上方是一方主动关闭,另一方被动关闭的情况,实际中还会出现同事发起主动关闭的情况:

TCP拥塞控制

  • 发送方维持一个拥塞窗口 cwnd ( congestion window )的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞窗口。

  • 发送方控制拥塞窗口的原则是:只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多的分组发送出去。但只要网络出现拥塞,拥塞窗口就减小一些,以减少注入到网络中的分组数。

  • 慢启动(slow-start)

    • 当主机开始发送数据时,如果立即所大量数据字节注入到网络,那么就有可能引起网络拥塞,因为现在并不清楚网络的负荷情况。因此,较好的方法是 先探测一下,即由小到大逐渐增大发送窗口,也就是说,由小到大逐渐增大拥塞窗口数值。通常在刚刚开始发送报文段时,先把拥塞窗口 cwnd 设置为一个最大报文段MSS的数值。而在每收到一个对新的报文段的确认后,把拥塞窗口增加至多一个MSS的数值。用这样的方法逐步增大发送方的拥塞窗口 cwnd ,可以使分组注入到网络的速率更加合理。

    • 每经过一个传输轮次,拥塞窗口 cwnd 就加倍。一个传输轮次所经历的时间其实就是往返时间RTT。不过“传输轮次”更加强调:把拥塞窗口cwnd所允许发送的报文段都连续发送出去,并收到了对已发送的最后一个字节的确认。 另,慢开始的“慢”并不是指cwnd的增长速率慢,而是指在TCP开始发送报文段时先设置cwnd=1,使得发送方在开始时只发送一个报文段(目的是试探一下网络的拥塞情况),然后再逐渐增大cwnd。

    • 为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量(如何设置ssthresh)。慢开始门限ssthresh的用法如下:

      • 当 cwnd < ssthresh 时,使用上述的慢开始算法。
      • 当 cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。
      • 当 cwnd = ssthresh 时,既可使用慢开始算法,也可使用拥塞控制避免算法。
  • 拥塞避免(congestion avoidance)

    • 让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。这样拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。
    • 无论在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(其根据就是没有收到确认),就要把慢开始门限ssthresh设置为出现拥塞时的发送 方窗口值的一半(但不能小于2)。然后把拥塞窗口cwnd重新设置为1,执行慢开始算法。 这样做的目的就是要迅速减少主机发送到网络中的分组数,使得发生 拥塞的路由器有足够时间把队列中积压的分组处理完毕。

  • 快重传(fast retransmit)

    • 快重传算法首先要求接收方每收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时才进行捎带确认。

    • 接收方收到了M1和M2后都分别发出了确认。现在假定接收方没有收到M3但接着收到了M4。 显然,接收方不能确认M4,因为M4是收到的失序报文段。根据 可靠传输原理,接收方可以什么都不做,也可以在适当时机发送一次对M2的确认。 但按照快重传算法的规定,接收方应及时发送对M2的重复确认,这样做可以让 发送方及早知道报文段M3没有到达接收方。发送方接着发送了M5和M6。接收方收到这两个报文后,也还要再次发出对M2的重复确认。这样,发送方共收到了 接收方的四个对M2的确认,其中后三个都是重复确认。 快重传算法还规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段M3,而不必 继续等待M3设置的重传计时器到期。 由于发送方尽早重传未被确认的报文段,因此采用快重传后可以使整个网络吞吐量提高约20%。
  • 快恢复(fast recovery)

    • 与快重传配合使用的还有快恢复算法,其过程有以下两个要点:

      • 当发送方连续收到三个重复确认,就执行“乘法减小”算法,把慢开始门限ssthresh减半。
      • 与慢开始不同之处是现在不执行慢开始算法(即拥塞窗口cwnd现在不设置为1),而是把cwnd值设置为 慢开始门限ssthresh减半后的数值,然后开始执行拥塞避免算法(“加法增大”),使拥塞窗口缓慢地线性增大。