不落辰

知不可乎骤得,托遗响于悲风

0%

计算机网络-传输层-TCP2

TCP 八股补充

TCP 三次握手问题

  • server方究竟什么时候为TCP连接分配资源?如缓冲区、变量?
    • 是established之后 还是SYN_RCVD之后

《TCP/IP详解 卷1:协议(原书第2版)》的第 13.6.1 节有讲到这个问题。

通常情况下,当一个连接请求到达本地却没有相关进程在目的端口侦听时就会产生一个重置报文段。

UDP 协议规定,当一个数据报到达一个不能使用的目的端口时就会产生一个 ICMP 目的地不可达(端口不可达)的消息。TCP 协议则使用重置报文段来代替完成相关工作。

listen , connect , accept 以及 三次握手流程

  • 问题:accept时是否已经完成三次握手?
    • 当然已经完成。
  • 在server调用listen之后,client就可以调用connect来和server完成三次握手建立连接。
  • 也即,在server调用accept之前,client就已经和server完成了三次握手,建立好了TCP连接。
  • 也即,accept所做的事情仅仅就是将已经建立好的TCP连接从Accept中取出,交给应用层。(即将connection socket交给应用层)
  • API以及三次握手流程如下

close与四次挥手

  • 调用close,向对方发送FIN报文,代表自己这一端没有数据要发送了。
  • client与server都可通过调用close关闭自己这一方的连接。
  • server接收到了 FIN 报文,TCP 协议栈会插入EOF到recv buffer中,user 通过read 感知FIN报文(就是通过读取EOF来感知EOF报文的吧)。EOF 会被放在已排队等候的其他已接收的数据之后,因为 EOF 表示在该连接上再无额外数据到达。此时,server进入 CLOSE_WAIT 状态。
  • 接着,当server处理完数据后,自然就会读到 EOF,于是也close socket,发出FIN 包,server进入LAST_ACK 状态;

listen 的 backlog

  • 省流:backlog 即 Accept全连接队列大小
    • Linux内核中会维护两个队列:
    • 半连接队列(SYN 队列):接收到一个 SYN 建立连接请求,处于 SYN_RCVD 状态;
    • 全连接队列(Accpet 队列):已完成 TCP 三次握手过程,处于 ESTABLISHED 状态;
  • int listen (int socketfd, int backlog)
  • socketfd : listeningfd
    ?- backlog,这参数在历史版本有一定的变化
  • 在早期 Linux 内核 backlog 是 SYN 队列大小,也就是未完成的队列大小。
  • 在 Linux 内核 2.2 之后,backlog 变成 accept 队列,也就是已完成连接建立的队列长度,所以现在通常认为 backlog 是 accept 队列。
  • 但是accpt队列上限值是内核参数 somaxconn 的大小
  • 也就说 **accpet 队列长度 = min(backlog, somaxconn)**。
  • SYN队列和Accept队列满了怎么办

三次而非两次

  • 为什么三次握手而非两次握手?
    • 为什么三次握手才可以初始化Socket、序列号和窗口大小并建立 TCP 连接 ?
  • 避免server维护大量半连接,浪费资源。也避免将老数据当作新数据接收。
  • 同步双方的初始序列号。
  • 阻止重复历史连接的初始化。

防止server维持多个半连接,浪费资源

  • 情景:client的SYN报文段超时重传。
  • 两次握手
    • client根本不会对server返回的ack做出任何响应报文。
    • server无需等待client返回ack就直接分配资源建立连接
    • client端却建立连接
  • 因此,两次握手,server端可能会维护大量的无用的半连接,浪费资源
可能将老数据当作新数据接收
  • 更进一步,server不但维护了半连接,还将旧数据当做新数据接收。

同步双方的初始序列号 。(三次而非两次/四次)

  • TCP 协议的通信双方, 都必须维护一个「序列号」, 序列号是可靠传输的一个关键因素,作用:

    • 保证双方正确的按序的接收(其实是按序的向上层交付)到对方的一个个报文段。
    • A -data-> B。data报文段要携带A自己的seq number,和根据B的seq推断出的ack number。
  • 建立连接时同步序列号(就是告诉对方自己的序列号)步骤

    • client 发送 SYN 报文 给server,server须回一个 ACK报文,
    • server 发送 SYN 报文 给client,clinet须回一个 ACK报文,
  • 四次。但是server给client的ack可以被syn报文段捎带。故三次

  • 这也解释了为什么不是2次握手,

    • 如果只有2次的话,那么只能是client -SYN-> server , client <-SYNACK- server。只能保证server接收到了client的SYN,无法保证client接收到了server的SYN
  • 如图

防止建立错误(老的 / 过期的 / 废弃的)连接

  • 一言以蔽之:防止 旧的连接(即所谓的历史连接) 被错误的建立(再断开?) 而造成混乱/浪费

    • 其实根本原因就是 server(TCP的被动方)无法判断这个连接是否是正确的连接(即 非 历史连接)。只有client(TCP的主动)才能根据server返回的信息(ack)和自身的上下文(seq)来判断是否是正确的连接,然后告知server。
    • 三次握手
      • server端在接收SYN报文段,发送SYNACK报文后,并不立刻建立连接,而是进入一个SYN_RCVD状态。这是因为:
        • 在连接建立之前,server需要有一个中间状态(作为一个缓冲/隔离状态),供给client端发送消息来确认,来告诉server端,server想要确认的连接,是否是一个正确的连接,是否是client端想要建立的连接。
          • 如果是正确的连接,client向server发送一个ACK
          • 如果不是正确的连接,client向server发送一个RST。告诉server建立这个连接
    • 两次握手
      • 在两次握手的情况下,server 没有中间状态给 client 来阻止历史连接,而是直接建立连接。导致server可能建立一个历史连接,造成资源浪费。
  • 例子:

  • 三次握手处理历史(废弃的)SYN报文段

    • server接收SYN报文段,进入SYN_RCVD状态。在server端处于SYN_RCVD状态时,client终止告知server该连接。阻止了server建立该连接
  • 两次握手处理

    • server接收SYN报文段,直接进入ESTABLISHED状态,没有给client端去告诉server这个连接正确与否的机会。(server进入established之后,即可向网络中send data)
    • 然而client根据server发送的ack判断
      • 如果是错误的连接的话,那么发送RST报文段。server接收到RST报文段后,还要断开连接。白白的浪费了资源。send的data也不会被接收,白白浪费
      • 如果是正确的连接,一切好说。
  • 要解决这种现象,最好就是在「被动发起方」发送数据前,也就是建立连接之前,要阻止掉历史连接,这样就不会造成资源浪费,而要实现这个功能,就需要三次握手
  • RFC 793
  • The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.
  • To deal with this, a special control message, reset, has been devised.
    • If the receiving TCP is in a non-synchronized state (i.e., SYN-SENT, SYN-RECEIVED), it returns to LISTEN on receiving an acceptable reset.
    • If the TCP is in one of the synchronized states (ESTABLISHED,FIN-WAIT-1, FIN-WAIT-2, CLOSE-WAIT, CLOSING, LAST-ACK, TIME-WAIT), it aborts the connection and informs its user.
    • We discuss this latter case under “half-open” connections below.

TCP 四次挥手问题

why TIME_WAIT

保证server(被动方)的连接正常关闭 (it’s why 2MSL)

  • 这也解释了为什么是2MSL

  • TIME-WAIT - represents waiting for enough time to pass to be sure the remote TCP received the acknowledgment of its connection termination request.

  • 等待足够的时间以确保最后的 ACK 能让server(被动关闭方)接收,从而帮助其正常关闭

  • 假设场景:client主动向server发起关闭连接

    • client用于确认server FIN的ACK报文丢失,server没有收到。于是server超时重传FIN报文
    • 如果client没有TIME_WAIT状态
      • 那么client在接收FIN,发送ACK之后,直接就进入了CLOSED状态。client收到server的FIN重传之后,回复RST报文,那么server将rst解释为错误(Connection reset by peer),server端的连接异常终止。(client的依旧是正常终止)
    • 如果client有TIME_WAIT状态。(ACK一去,重传的FIN一回,正好2MSL。)
      • client收到FIN报文后 重新计时2MSL。

防止历史连接中的数据,被新连接错误的接收 (it’s also why 2MSL)

  • 前提:

    • 新老连接的四元组相同。(源ip 源 port 目的IP 目的port)
    • 初始序列号:32位的计数器,循环,因此可能重复或者接近。
    • 序列号:32 位,循环。如下是初始序列号。

  • 如果TIME_WAIT过短或者没有TIME_WAIT,

    • 那么假设情景:一个老连接的报文段在网络中被延迟,并且由于没有TIME_WAIT,在新TCP连接建立之后,这个老的报文段仍然存活于网络中,在网络中传播。
    • 那么,存在一种可能。这个老连接的包在死亡之前到达了client,且在此时正好落在新连接的receving window中。且该老连接包(如seq=301)的位置的报文段还没到
      • 那么,就会将这个老连接的报文缓存在receving window中。新连接的正常的301号报文就被丢弃。

  • 为了防止老报文被新连接接收

  • TCP 设计了 2MSL的 TIME_WAIT 状态

    • 2MSL 使得两个方向上的老的报文都在网络中死亡。再出现的报文一定都是新连接的报文

TIME_WAIT why 2MSL ?(不用看 看上面那个就行)

  • 前置

    • MSL : Maximum Segment Lifetime : 报文最大生存时间
      • 单位是时间
      • 一般 MSL = 30s
    • TTL : 最大路由数
      • TCP 协议基于IP 协议,IP header 中有一个 TTL 字段,是 IP 报文段 可以经过的最大路由数,每经过一个处理他的路由器此值就减 1,当此值为 0 则报文段将被丢弃,同时发送 ICMP 报文通知源主机。
      • 单位是跳数
      • 一般 TTL = 64
    • MSL >= TTL
    • 认为报文段经过 64 个路由器的时间不会超过 30 秒,如果超过了,就认为报文已经消失在网络中了。
  • 假设是client向server发起关闭连接的四次挥手请求。

    • client是 主动关闭方,server是被动关闭方。
    • 2MSL 的时间是从client接收到 FIN 后发送 ACK 开始计时的
  • 理由1

  • client为了 接收到 [server] 对 [client之前(进入TIME_WAIT之前/接收server的FIN 发送给server ACK)发送给server的tcp报文段] 的 [响应报文段(server发送给client的)]。

    • 这样的 responding packet是由 server 在进入LAST_ACK之前就发送给client的,但是由于延迟等原因,packet比server发送给client的FIN更晚到达client
  • 我认为下面这个是2MSL主要理由

  • 理由22MSL,至少允许(ACK)报文丢失一次,且 保证server可以正确关闭连接

  • client用于确认server FIN的ACK报文丢失,server没有收到。于是server超时重传FIN报文。这样ACK一去,FIN一回,正好2MSL。

  • client收到FIN报文后 重新计时2MSL。