TCP三次握手与四次挥手

三次握手

B的TCP服务器进程先创建传输控制块TCB,准备接受客户进程的连接请求。然后服务器进程就处于LISTEN状态,等待客户的连接请求。

  1. A首先创建传输控制块TCB,然后向B发出连接请求报文段,SYN=1,初始序号seq=x(随机,SYN=1的报文段不能携带数据,但要消耗掉一个序号),此时TCP客户进程进入SYN-SENT状态

  2. B收到连接请求报文段后,如同意建立连接,则向A发送确认,SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y(随机,SYN=1的报文段不能携带数据,但要消耗掉一个序号),TCP服务器进程进入SYN-RCVD状态

  3. A收到B的确认后,要向B给出确认报文段,ACK=1,确认号ack=y+1,seq=x+1(初始为seq=x,第二个报文段所以要+1),ACK报文段可以携带数据,不携带数据则不消耗序号。TCP连接已经建立,A进入ESTABLISHED,B收到后也进入ESTABLISHED状态

———–数据传输阶段———-

四次挥手

由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这���方向的连接,但是另一方扔仍然能够发送数据,直到另一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。

  1. A发出连接释放报文段,FIN=1,序号seq=u(之前最后一次传送的序号加1),并停止再发送数据,主动关闭TCP连接,进入FIN-WAIT-1状态,等待B的确认。

  2. B收到连接释放报文段后即发出确认报文段,ACK=1,确认号ack=u+1,序号seq=v(之前最后一次传送的序号加1),B进入CLOSE-WAIT状态,此时的TCP处于半关闭状态,A到B的这个方向的连接已释放。A收到确认报文段后进入FIN-WAIT2状态

    ———–可能这时Server还有数据要发送给Client—————–

  3. B没有要向A发出的数据,B发出连接释放报文段,FIN=1,ACK=1,序号seq=w(之前最后一次传送的序号加1),确认号ack=u+1,B进入LAST-ACK(最后确认)状态,等待A的确认。

  4. A收到B的连接释放报文段后,对此发出确认报文段,ACK=1,seq=u+1,ack=w+1,A进入TIME-WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2*MSL(最长报文段寿命)后,A才进入CLOSED状态。B在收到确认报文段后就进入了CLOSED状态。

三次握手交换了什么数据

除了序列号,第一次还包括client通告server的接收窗口大小以及本方的MSS,第二次还包括server通告client的接收窗口大小以及本方的MSS

三次握手为什么两次不可以,为什么四次没必要

三次握手的首要原因是为了防止旧的重复连接初始化造成混乱。如果是历史连接(序列号过期或超时),则第三次握手发送的报文是 RST 报文,以此中止历史连接;如果不是历史连接,则第三次发送的报文是 ACK 报文,通信双方就会成功建立连接;

还有一个原因是因为有可能Client的第一个SYN报文段长时间滞留在网络中,Client一直等不到回应就释放了连接,但Server收到这个滞后的SYN报文段会向Client发送确认,并且同意建立连接,Client此时已经释放连接了,对Server的连接确认不予理睬,于是Server空空等待Client发来数据,浪费资源!

第二次与第三次可以合并在一起,所以四次没必要

三次握手的第三次丢失怎么办

server没有收到ACK,超时重传,一定次数还没收到则关闭连接

第二次握手,client就已经进入established状态,若第三次丢失,则发送数据给server时,server还未建立连接,会返回RST报文,这时client就能感知到了

有可能四次握手吗

有可能。当两端同时发起 SYN 来建立连接的时候,就出现了四次握手

有可能三次挥手吗

如果 Server 在收到 Client 的 FIN 包后,在也没数据需要发送给 Client 了,那么对 Client 的 ACK 包和 Server 自己的 FIN 包就可以合并成为一个包发送过去,这样四次挥手就可以变成三次了(似乎 linux 协议栈就是这样实现的)

初始序列号ISN为什么是随机的,怎么随机

  1. 如果不是随机产生初始序列号,黑客将会以很容易的方式获取到你与其他主机之间通信的初始化序列号,并且伪造序列号进行攻击
  2. 在网络不好的场景中,TCP连接可能不停地断开,若用固定ISN,很可能新连接建立后,之前在网络中延迟的数据报才到达,这就乱套
  3. 不同OS的ISN生成算法不一样,就是随机数生成算法,比如RFC文档推荐:ISN = M + F(localhost, localport, remotehost, remoteport),M是个计时器,每隔 4 毫秒加 1,F是个哈希算法,一般用MD5比较安全
  4. ISN是32位的

socket编程

  • 客户端 connect 成功返回是在第二次握手成功
  • 服务端 accept 成功返回是在三次握手成功

为了方便调试服务器程序,一般会在服务端设置 SO_REUSEADDR 选项,这样服务器程序在重启后,可以立刻使用。这里设置SO_REUSEADDR 是不是就等价于对这个 socket 设置了内核中 的 net.ipv4.tcp_tw_reuse=1 这个选项?

  • tcp_tw_reuse 是内核选项,主要用在连接的发起方(客户端)。TIME_WAIT 状态的连接创建时间超过 1 秒后,新的连接才可以被复用,注意,这里是「连接的发起方」;
  • SO_REUSEADDR 是用户态的选项,用于「连接的服务方」,用来告诉操作系统内核,如果端口已被占用,但是 TCP 连接状态位于 TIME_WAIT ,可以重用端口。如果端口忙,而 TCP 处于其他状态,重用会有 “Address already in use” 的错误信息。
  • tcp_tw_reuse 是为了缩短 time_wait 的时间,避免出现大量的 time_wait 连接而占用系统资源,解决的是 accept 后的问题。
  • SO_REUSEADDR 是为了解决 time_wait 状态带来的端口占用问题,以及支持同一个 port 对应多个ip,解决的是 bind 时的问题。

FIN包如何断开连接

  • 客户端调用 close ,表明客户端没有数据需要发送了,则此时会向服务端发送 FIN 报文,进入FIN_WAIT_1 状态;
  • 服务端接收到了 FIN 报文,TCP 协议栈会为 FIN 包插入一个文件结束符 EOF 到接收缓冲区中,应用程序可以通过 read 调用来感知这个 FIN 包。这个 EOF 会被放在已排队等候的其他已接收的数据之后,这就意味着服务端需要处理这种异常情况,因为 EOF 表示在该连接上再无额外数据到达。此时,服务端进入 CLOSE_WAIT 状态;
  • 接着,当处理完数据后,自然就会读到 EOF ,于是也调用 close 关闭它的套接字,这会使得客户端会发出一个 FIN 包,之后处于LAST_ACK 状态;
  • 客户端接收到服务端的 FIN 包,并发送 ACK 确认包给服务端,此时客户端将进入 TIME_WAIT 状态;
  • 服务端收到 ACK 确认包后,就进入了最后的 CLOSE 状态;
  • 客户端经过 2MSL 时间之后,也进入 CLOSE 状态

为什么A在TIME-WAIT状态必须等待2*MSL时间

  1. 第四次挥手的ACK报文可能丢失,B如没有收到ACK报文,则会不断重复发送FIN报文,所以A不能立即关闭,必须确认B收到了ACK,2*MSL正好是一个发送和一个回复的最大时间,如果A直到2*MSL还没收到B的重复FIN报文,则说明第四次挥手的ACK已经成功接收,于是可以放心的关闭TCP连接了。
  2. 第四次挥手的ACK报文之后,再经过2*MSL的时间,就是本次连接持续时间内产生的所有报文都从网络中消失,这样下一个新的TCP连接就不会出现旧的连接请求报文了。
  3. 2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端又接收到了服务端重发的 FIN 报文,那么 2MSL 时间将重新计时。
  4. 在 Linux 系统里 2MSL 默认是 60 秒,那么一个 MSL 也就是 30 秒。Linux 系统停留在TIME_WAIT 的时间为固定的 60 秒��

TCP漫谈之keepalive和time_wait

time_wait状态太多如何处理

  1. 开启tcp_timestamps:在TCP可选选项(option)字段内记录最后一次发送时间和最后一次接收时间(这是time_wait重用和快速回收的保证),net.ipv4.tcp_timestamps = 1
  2. 开启time_wait重用:因为time_wait是主动关闭方的状态,当客户端发送方又有新的TCP连接想要发起时,可以直接重用正在time_wait状态的TCP连接,接收端收到数据报,可以通过timestamp字段判断属于复用前的连接还是复用后的连接net.ipv4.tcp_tw_reuse = 1
  3. 开启time_wait快速回收:不再等待2MSL,而是RTO(远小于2MSL),net.ipv4.tcp_tw_recycle = 1,但time_wait不等待2MSL的话会出现之前的问题,所以Linux4.12版本后就废弃这个参数了,NAT也不好处理time_wait
  4. tcp_max_tw_buckets 控制并发的 TIME_WAIT 的数量,默认值是 180000。如果超过默认值,内核会把多的 TIME_WAIT 连接清掉,然后在日志里打一个警告。官网文档说这个选项只是为了阻止一些简单的 DoS 攻击,平常不要人为的降低它。
  5. 简单来说,就是打开系统的time_wait重用和快速回收。但是这是非常危险的,因为这两个参数违反了TCP协议,而且time_wait状态只有主动发起FIN的那方才会有的,服务器一般不会主动断连,特别是HTTP服务器会设置KeepAlive保持连接

close_wait状态太多如何处理

在服务器与客户端通信过程中,因服务器发生了socket未关导致的closed_wait发生,致使监听port打开的句柄数到了1024个,且均处于close_wait的状态,最终造成配置的port被占满出现“Too many open files”,无法再进行通信。

close_wait状态出现的原因是被动关闭方未关闭socket造成,更多是由于程序编写不当造成的,比如被动关闭方没有检测到关闭socket,或者程序忘记要关闭socket,所以需要修改程序的逻辑

往close的TCP连接发送数据会发生什么?

Linux下显示32: Broken pipe

  • 1)当TCP连接的对端进程已经关闭了Socket的情况下,本端进程再发送数据时,第一包可以发送成功(但会导致对端发送一个RST包过来):之后如果再继续发送数据会失败,错误码为“10053: An established connection was aborted by the software in your host machine”(Windows下)或“32: Broken pipe,同时收到SIGPIPE信号”(Linux下)错误;之后如果接收数据,则Windows下会报10053的错误,而Linux下则收到正常关闭消息;
  • 2)TCP连接的本端接收缓冲区中还有未接收数据的情况下close了Socket,则本端TCP会向对端发送RST包,而不是正常的FIN包,这就会导致对端进程提前(RST包比正常数据包先被收到)收到“10054: An existing connection was forcibly closed by the remote host”(Windows下)或“104: Connection reset by peer”(Linux下)错误。

不为人知的网络编程(四):深入研究分析TCP的异常关闭