TCP三次握手、四次挥手总结

前言:
无论面试Java、C++、Python还是前端开发,计算机网络中TCP的知识可以说是必问的。
本篇文章是对tcp三次握手和四次挥手问题的总结。

  1. TCP基本认识
    在这里插入图片描述
    2 TCP连接建立过程
    在这里插入图片描述
    3 TCP连接的断开问题
    在这里插入图片描述4 socket编程
    在这里插入图片描述

本篇文章只关注于TCP连接的建立和断开过程,对于流量控制、拥塞控制以及可靠性传输不做过多讲解。

01 TCP的基本认识

先看一下tcp的头部格式
在这里插入图片描述
序列号:在建立连接时又计算机生成的随机数作为其初始值,通过SYN包传输给接收端主机,每发送一次数据,就会对序列号的大小进行累加,序列号主要用来解决网络包的乱序问题。
确认应答号: 指的是下一次期望收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个 序号之前的数据都是正常接收的,累计确认用来解决包丢失的问题。
控制位:
ACK:该位为1时,确认应答字段变为有效,TCP规定除了最初建立连接时的SYN包之外该位必须设置为1.
RST:该位为1时,表示TCP连接中出现异常必须强制断开连接。
SYN:该位为1时,表示希望建立连接,并且在序列号字段进行序列号初始值的设定
FIN:该位为1时,表示今后不会再有数据发送,希望断开连接,当通信结束希望断开连接时,通信双方的主机之间可以相互交换FIN位置为1的TCP段。

为什么需要TCP协议,TCP协议工作在哪一层?

IP层提供的服务时不可靠的,它不保证网络数据包的交付,不保证按序交付,也不保证数据的完整性。
在这里插入图片描述
如果需要保障网络数据包的可靠性,那么就需要由上层的传输层的TCP协议来负责。
因为TCP是一个工作在传输层的可靠的数据传输服务,他能确保接收端接收的数据是无差错、无间隔、非冗余和按序的。

什么是TCP?

TCP是面向连接的、可靠的、基于字节流的传输层通信协议。

面向连接:一定是“一对一”才能连接,不能像UDP协议可以一个主机同时向多个主机发送信息,也就是一对多是无法做到的。
可靠的:无论网络链路中出现了怎样的链路变化,TCP都可以保证报文的可靠传输。
字节流:消息是没有边界的,所以无论我们消息有多大都可以进行传输,并且消息是有序的,当前一个消息还没有收到的时候,即使它先收到了后面的字节的数据,那么也不能扔给应用层去处理,同时对于重复的报文自动丢弃。

建立一个tcp连接需要客户端与服务器端达成以下三个信息的共识:

  1. Socket:由ip地址和端口号组成
  2. 序列号:用来解决乱序问题
  3. 窗口大小:用来做流量控制

如何唯一确定一个TCP连接?

TCP四元组可以唯一确定一个连接,四元组包括如下:
原地址,源端口号,目标地址,目标端口号
在这里插入图片描述
源地址和目的地址字段32位是在IP的头部中,作用是通过ip协议发送报文给对方主机。
源端口和目的端口的字段16位是在TCP的头部中,作用是告知TCP协议应该把报文发送给哪个进程。

一个IP的服务器监听了一个端口,它的TCP最大连接数是多少?

服务器通常固定在某个本地端口上进行监听,等待客户端的连接请求。
因此客户端ip和端口是可变的,理论值计算公式如下:

最大TCP连接数 = 客户端的IP数 乘以 客户端的端口数
对 IPv4,客户端的 IP 数最多为 2 的 32 次方,客户端的端口数最多为 2 的 16次方,也就是服务端单机最大 TCP 连接数,约为 2 的 48 次方。
当然,服务端最大并发TCP连接数远不能达到理论上线值:
首先是文件描述符的限制,Socket都是文件,所以首先通过ulimit配置文件描述符的数量;
另一个是内存限制,每个TCP占用一定的内存,操作系统内存是有限的。

TCP和UDP有什么区别?分别应用在什么场景?

UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务。
UDP协议非常简单,头部只有8个字节,UDP头部格式如下:
在这里插入图片描述

目标和源端口:主要告诉UDP协议应该把报文发送给哪个进程。
包长度:该字段保存了UDP首部的长度和数据的长度之和
校验和:校验和是为了提供可靠的UDP首部和数据而设计

TCP和UDP区别:

1连接:
TCP是面向连接的传输层协议,传输数据之前要先建立连接
UDP是不需要连接的,即可传输数据
2 .服务对象
TCP是一对一的两点服务,即一条连接只有两个端点
UDP支持一对一和多对一交互通信
3 . 可靠性
TCP是可靠交付数据的,数据可以无差错、不丢失、不重复、不乱序
UDP是尽最大可能交付,不保证可靠交付数据
4 拥塞控制和流量控制
TCP具有拥塞控制科流量控制机制,可以保证数据传输的安全性
UDP则没有,即使网络十分拥堵,也不会降低发送速率
5 首部开销
TCP的首部长度较长,会有一定的开销,首部在没有使用选项字段时是20个字节,如果使用选项字段则会变长。
UDP首部只有8个字节,并且是固定不变的开销较小

TCP和UDP的应用场景

由于TCP是面向连接的,能够保证数据的可靠性交付,因此经常用于:
FTP文件传输,HTTP/HTTPS
由于UDP面向无连接,他可以随时发送数据,再加上UDP本身处理简单高效,因此经常用于:

  1. 包总量较小的通信如:DNS、SNMP等
  2. 视频、音频等多媒体通信
  3. 广播通信

为什么UDP头部没有首部长度字段,而TCP头部有首部长度字段?

原因是TCP具有可变长的选项字段,而UDP头部长度则是不会变化的,因此无需一个字段去记录UPD的首部长度

为什么UDP头部有包长度字段,而TCP头部则没有包长度字段?

TCP如何计算负载数据长度:

TCP数据长度 = IP总长度 - IP首部长度 - TCP首部长度
其中IP总长度和IP首部长度是在IP首部格式已知的。TCP首部长度则是在TCP的首部格式已知的,因此就可以求得TCP数据的长度。
大家这时就奇怪了问:“ UDP 也是基于 IP 层的呀,那 UDP 的数据长度也可以通过这个公式计算呀?为何还要有「包长度」呢?”
因为为了网络设备硬件设计和处理方便,首部长度需要是 4字节的整数倍。
如果去掉 UDP 「包长度」字段,那 UDP 首部长度就不是 4 字节的整数倍了,所以觉得这可能是为了补全 UDP 首部长度是 4 字节的整数倍,才补充了「包长度」字段。

TCP连接的建立过程

TCP三次握手状态变迁
在这里插入图片描述

初始时刻,客户端和服务器端都处于CLOSED状态,首先是服务端先启动并且监听某个端口,处于LISTEN状态。
在这里插入图片描述
客户端会随机初始化序列号client_isn,将此序号置于TCP首部序列号字段中,同时把SYN标志位置为1,表示SYN报文,接着把第一个SYN报文发送给服务端,表示向服务端发起连接,该报文是不包含应用层数据的,之后客户端处于SYN-SENT状态。
在这里插入图片描述
服务端收到客户端的SYN报文之后,首先服务器端也会随机初始化自己的序列号server_isn,并将此序号填到TCP首部的序列号字段中,其次把确认应答号的字段填入client_isn + 1,接下来把SYN和ACK的标志位置为1,最后把该报文发送给客户端,该报文也不包含应用层的数据,只有服务处于SYN-RCVD状态。
在这里插入图片描述
客户端收到服务端的报文之后,还要向服务端应答一个报文,首先该应答报文的TCP首部ACK标志位置为1,其次确认应答号字段填入serve_isn + 1,最后把报文段发送给服务器端,此次报文可以携带客户到服务器的数据,之后客户端处于ESTABLISHED状态。
服务器收到客户端的应答报文之后,也进入了ESTABLISED状态。

从以上过程可以发现第三次握手是可以携带数据的,前两次握手不可以携带数据。一旦完成三次握手,双方都处于ESTABLISHED的状态,此次连接到此完成,客户端和服务器端就可以相互发送数据了。

为什么是三次握手,而不是两次和四次

三次握手可以避免历史重复连接的初始化,这是主要原因
三次握手可以同步双方的序列号
三次握手可以避免资源的浪费

原因1:避免历史连接:

简单来说,三次握手的首要原因是为了防止旧的重复连接初始化造成混乱
网络环境是错综复杂的,往往和我们的预期不同,可能会出现旧的数据包先到达主机,这种情况下,TCP三次握手是如何避免连接混乱呢?
在这里插入图片描述
客户端连续发送了多个SYN连接建立报文,在网络拥塞的情况下,一个旧的报文比新的报文更早的到达了服务端,这个时候服务器端回送一个SYN + ACK报文给客户端,客户端收到后根据自身的上下文,判断这是一个历史连接(序列号超时或过期)那么客户端就会发送RST报文给服务端,表示终止旧的连接,不会造成连接的混乱。
如果是两次握手,句不能判断当前连接是否是历史连接,三次握手可以在客户端发送方准备发送第三次报文时,客户端有足够的上下文来判断当前连接是否是历史连接:
如果是历史连接(序列号超时或过期)则第三次握手发送RST报文,终止连接
如果不是历史连接,则第三次发送的报文时ACK报文,通信双方会成功建立连接。
TCP三次握手可以防止历史连接初始化。

原因二:同步客户端和服务器端双方的序列号

TCP协议通信双方,都必须维护一个序列号,序列号的作用:
接收方可以去除重复数据;
接收方可以根据数据包序列号按序接收
可以标识发送出去的数据包中,哪些已经被收到
可见,序列号在TCP的可靠连接转给你起着非常重要的作用,当客户端发送携带初始序列号的SYN报文时,需要服务端回送一个ACK应答报文,表示服务端接收成功,那当服务端发送「初始序列号」给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。
在这里插入图片描述
四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了「三次握手」。
而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。

原因三:避免资源的浪费:

如果只有两次握手,当客户端的SYN请求在网络中阻塞,客户端没有接收到ACK报文,就会重新发送SYN,由于没有第三次握手,服务器不清楚客户端是否已经收到自己发送的建立连接的ACK确认信号,所以没收到一个SYN只能先主动建立一个连接,这会造成什么情况?
如果客户端SYN阻塞,重复发送多次SYN报文,那么服务器在收到请求后建立读个冗余的无效连接,造成不必要的浪费。
在这里插入图片描述
即两次握手会造成消息滞留情况下,服务器重复接受无用的连接请求 SYN 报文,而造成重复分配资源。

TCP 建立连接时,通过三次握手能防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始化序列号。序列号能够保证数据包不重复、不丢弃和按序传输。
「两次握手」:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号;

「四次握手」:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。

为什么客户端和服务器端的初始化序列号ISN是不同的?

因为网络中的报文会延迟、复制重发、也可能丢失,这样会造成不同连接之间产生相互影响,为了避免影响,客户端和服务端初始化序列号随机且不同。
初始化序列号如何随机产生?
起始 ISN 是基于时钟的,每 4 毫秒 + 1,转一圈要 4.55 个小时

RFC1948 中提出了一个较好的初始化序列号 ISN 随机生成算法。

ISN = M + F (localhost, localport, remotehost, remoteport)

M 是一个计时器,这个计时器每隔 4 毫秒加 1。

F 是一个 Hash 算法,根据源 IP、目的 IP、源端口、目的端口生成一个随机数值。要保证 Hash 算法不能被外部轻易推算得出,用 MD5 算法是一个比较好的选择。

既然IP层会分片,那么TCP层为什么还要MSS呢?

首先来认识一下MTU和MSS
在这里插入图片描述

MTU:一个网络包的最大长度以太网中一般为1500字节
MSS:除去IP和TCP头部之后,一个网络包所能容纳的TCP数据最大长度
如果TCP的整个报文(头部+数据)交给IP层进行分片,有什么影响?
当IP层有一个超过MTU大小的数据要发送,那么IP层就要进行分片,把数据分片,保证每个分片小于MTU,把一份IP数据进行分片以后,又目标主机IP层进行重新组装,交给上一层的TCP传输层。
存在隐患:
当一个IP分片丢失后,整个IP报文的所有分片要重传,因为IP层本身没有超时重传机制,它由传输层的TCP来负责超时和重传。
当接收方发现 TCP 报文(头部 + 数据)的某一片丢失后,则不会响应 ACK 给对方,那么发送方的 TCP 在超时后,就会重发「整个 TCP 报文(头部 + 数据)」。因此,可以得知由 IP 层进行分片传输,是非常没有效率的
所以,为了达到最佳的传输效能 TCP 协议在建立连接的时候通常要协商双方的 MSS 值,当 TCP 层发现数据超过 MSS 时,则就先会进行分片,当然由它形成的 IP 包的长度也就不会大于 MTU ,自然也就不用 IP 分片了。
经过 TCP 层分片后,如果一个 TCP 分片丢失后,进行重发时也是以 MSS 为单位,而不用重传所有的分片,大大增加了重传的效率。

SYN攻击

我们都知道TCP连接是需要三次握手的,假设攻击者短时间伪造不同的IP地址的syn报文,服务端每接收一个SYN报文就进入SYN-RCVD状态,但是服务端发送出去的SYN+ACK报文无法得到未知IP主机的ACK应答,久而久之就会沾满服务端的SYN接收队列,这个队列是未确认连接的队列,使得服务器补鞥呢为正常用户服务。
在这里插入图片描述

避免SYN攻击方式一

其中一种解决方式就是通过修改Linux内核的参数,控制队列的大小和当队列满时应该做什么样的处理。
当网卡接收数据包的速度大于内核处理的速度,会有一个队列来保持这些数据包,。控制该队列的最大值如下:

net.core.netdev_max_backlog

SYN_RCVD状态连接的最大个数

net.ipv4.tcp_max_syn_backlog

超出处理能力时,对新的SYN直接返回RST丢弃连接

net.ipv4.tcp_abort_on_overflow

避免SYN攻击方式二

Linux内核的SYN(未完成连接建立)队列和accept(已完成连接建立队列)是如何工作的
在这里插入图片描述

正常流程:
当服务器接收到客户端的SYN报文,会将其加入到内核的SYN队列
接着发送SYN+ACK给客户端,等待客户端回应ACK报文
服务端接收到ACK报文后,从SYN队列移到ACCEPT队列
应用通过调用accept() socket接口从accept队列去除的连接

在这里插入图片描述

如果应用程序处理过慢,就会导致Accept队列沾满
在这里插入图片描述
收到SYN攻击会使得SYN队列占满

tcp_syncookies的方式可以应对SYN攻击的方法

net.ipv4.tcp_syncookies = 1

在这里插入图片描述

SYN队列满后,后续服务器收到SYN包,不仅如此SYN队列
计算出一个cookie值,再以SYN + ACK中的序列号返回客户端
服务端接收到客户端应答报文时,服务器会检查这个ACK包的合法性,如果合法,直接放到Accept队列中,
最后通过调用accept()Socket接口,从Accept队列取出连接

03TCP连接的断开流程

TCP四次挥手过程以及状态变迁
TCP断开连接采用四次挥手,双发都可以主动断开连接,断开连接后主机中的资源将会被释放。
在这里插入图片描述

客户端打算关闭连接,此时发送一个TCP首部FIN标志位置为1的报文,即FIN报文,之后客户端进入FIN_WAIT_1状态。
服务端收到该报文之后,向客户端发送ACK应答报文,接着服务端进入CLOSED_WAIT状态,
客户端收到服务端的ACK之后,进入FIN_WAIT_2状态
等待服务端处理完数据之后,也向客户端发送FIN报文,之后服务端进入LAST_ACK状态。
客户端收到服务端的FIN报文之后,回答一个ACK报文,之后进入了TIME_WAIT状态
服务端收到ACK应答报文之后,进入CLOSED状态,至此服务端已经关闭连接
客户端在发送ACK之后经过2MSL时间之后,自动进入CLOSED状态,客户端也关闭连接完成。

主动关闭链接的才有TIME_WAIT状态。

为什么要四次挥手?

关闭链接时,客户端向服务器端发送FIN仅仅表示客户端不再发送数据了,但是还能接收数据。
服务器收到客户端的FIN报文时,先回一个ACK应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送FIN报文给客户端表示同意现在关闭连接
从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,从而比三次握手导致多了一次。

为什么TIME_WAIT的等待时间是2MSL?

MSL是maximum Segment LifeTime,报文最大生存时间,它是任何报文在网络中的存在的最长时间,超过这个时间报文将会被丢弃,tcp报文是基于ip协议的,在ip头部有一个TTL字段,是ip报文可以经过的最大路由数,每经过一个处理它的路由器这个值就会减一,当这个值变为0时数据包将会被丢弃,同时发送ICMP报文通知源主机。
MSL 与 TTL 的区别:MSL 的单位是时间,而 TTL 是经过路由跳数。所以 MSL 应该要大于等于 TTL 消耗为 0 的时间,以确保报文已被自然消亡。

TIME_WAIT 等待 2 倍的 MSL,比较合理的解释是:网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间

比如,如果被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 Fin 报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方, 一来一去正好 2 个 MSL。

2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端又接收到了服务端重发的 FIN 报文,那么 2MSL 时间将重新计时。

在 Linux 系统里 2MSL 默认是 60 秒,那么一个 MSL 也就是 30 秒。Linux 系统停留在 TIME_WAIT 的时间为固定的 60 秒。

为什么需要TIME_WAIT状态

主动发起关闭连接的一方才有TIME_WAIT状态,需要这各等待时间主要是有两个原因:

  1. 防止具有相同的四元组socket的旧数据包被收到
  2. 保证被动关闭的一放能够正确的关闭,即保证最后的ACK能让被动关闭方接收,从而帮助其正常关闭

防止旧的连接的数据包:

假设TIME_WAIT没有或者等待时间过短,被延迟的数据包抵达后发生什么?
在这里插入图片描述
如图黄色框中服务端在关闭连接之前发送SEQ=301报文,被网络延迟了
这时有相同的端口的TCP连接被复用后,延迟的SEQ=301抵达客户端,那么客户端坑你正常接收这个文件,就会导致数据错乱等问题。

所以TCP设计出这样一个机制,经过2MSL时间,足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再次出现的数据包一定都是先建立连接产生的。

原因二:保证被动方正确关闭

TIME-WAIT 作用是等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭。

假设 TIME-WAIT 没有等待时间或时间过短,断开连接会造成什么问题呢?
在这里插入图片描述
上图红色框中,客户端最后一个ACK报文如果在网络中丢失,此时如果客户端TIME-WAIT过短,直接进入CLOSED状态,服务端则一致处于LAST-ACK状态。
客户端发起建立连接请求的SYN时,服务端会送一个RST报文,连接被终止。

如果 TIME-WAIT 等待足够长的情况就会遇到两种情况:

  1. 服务端正常收到四次挥手的最后一个 ACK 报文,则服务端正常关闭连接。
  2. 服务端没有收到四次挥手的最后一个 ACK 报文时,则会重发 FIN 关闭连接报文并等待新的 ACK 报文。
  3. 所以客户端在 TIME-WAIT 状态等待 2MSL 时间后,就可以保证双方的连接都可以正常的关闭。

TIME_WAIT 过多有什么危害?

如果服务器有处于 TIME-WAIT 状态的 TCP,则说明是由服务器方主动发起的断开请求。
过多的 TIME-WAIT 状态主要的危害有两种:1是内存资源的占用;2是对端口资源的占用,一个TCP连接至少消耗一个本地端口,第二个危害是会造成严重的后果的,要知道,端口资源也是有限的,一般可以开启的端口为 32768~61000,也可以通过如下参数设置指定
net.ipv4.ip_local_port_range
如果服务端 TIME_WAIT 状态过多,占满了所有端口资源,则会导致无法创建新连接。

如果已经建立了连接,但是客户端突然出现故障怎么办?

TCP有一个机制是保活机制,原理是这样的:

定义一个时间段,在这个时间段内如果没有任何连接相关的活动,TCP保活机制会开始起作用,每隔一个时间发送一个探测报文,该探测报文数据非常少,如果连续几个探测报文都没有得到响应,则认为当前TCP连接已经死亡,系统内核将错误信息通知给上层应用程序。

Linux内核中有对应的参数可以设置保活时间、保活探测次数、探测的时间间隔,以下是默认时间

net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75  
net.ipv4.tcp_keepalive_probes=9

tcp_keepalive_time=7200:表示保活时间是 7200 秒(2小时),也就 2 小时内如果没有任何连接相关的活动,则会启动保活机制

tcp_keepalive_intvl=75:表示每次检测间隔 75 秒;

tcp_keepalive_probes=9:表示检测 9 次无响应,认为对方是不可达的,从而中断本次的连接。
也就是说在 Linux 系统中,最少需要经过 2 小时 11 分 15 秒才可以发现一个「死亡」连接。

Socket编程

在这里插入图片描述

  1. 服务端和客户端初始化socket,得到文件描述符
  2. 服务端调用build,将绑定在IP地址和端口
  3. 服务器调用listen 进行监听
  4. 服务端调用accept,等待客户端连接
  5. 客户端调用connect,向服务器端地址和端口发起连接请求
  6. 服务端accept返回用于传输的socket文件描述符
  7. 客户端调用write写入数据,服务端调用read读取数据
  8. 客户端断开连接时,调用close,服务端读取read数据时,读取到EOF,呆数据处理完后,服务端调用close,表示连接关闭

这里需要注意的是,服务端调用 accept 时,连接成功了会返回一个已完成连接的 socket,后续用来传输数据。

所以,监听的 socket 和真正用来传送数据的 socket,是「两个」 socket,一个叫作监听 socket,一个叫作已完成连接 socket。
成功连接建立之后,双方开始通过 read 和 write 函数来读写数据,就像往一个文件流里面写东西一样。

listen是参数backlog的意义?

Linux内核中会维护两个队列:

  • 未完成连接的队列(SYN队列):接收到一个SYN建立连接请求,处于SYN_RCVD状态;
  • 已经完成连接的队列:Accept队列,处于ESTABLISHED状态。
    在这里插入图片描述
int listen (int socketfd, int backlog)

参数一 socketfd 为 socketfd 文件描述符
参数二 backlog,这参数在历史有一定的变化

在早期 Linux 内核 backlog 是 SYN 队列大小,也就是未完成的队列大小。
在 Linux 内核 2.2 之后,backlog 变成 accept 队列,也就是已完成连接建立的队列长度,所以现在通常认为 backlog 是 accept 队列。

accept 发送在三次握手的哪一步?

在这里插入图片描述

  • 客户端的协议栈向服务器端发送了 SYN 包,并告诉服务器端当前发送序列号 client_isn,客户端进入 SYNC_SENT 状态
  • 服务器端的协议栈收到这个包之后,和客户端进行 ACK 应答,应答的值为 client_isn+1,表示对 SYN 包 client_isn 的确认,同时服务器也发送一个 SYN 包,告诉客户端当前我的发送序列号为 server_isn,服务器端进入 SYNC_RCVD 状态;
  • 客户端协议栈收到 ACK 之后,使得应用程序从 connect 调用返回,表示客户端到服务器端的单向连接建立成功,客户端的状态为 ESTABLISHED,同时客户端协议栈也会对服务器端的 SYN 包进行应答,应答数据为 server_isn+1;
  • 应答包到达服务器端后,服务器端协议栈使得 accept 阻塞调用返回,这个时候服务器端到客户端的单向连接也建立成功,服务器端也进入 ESTABLISHED 状态。

从上面的描述过程,我们可以得知客户端 connect 成功返回是在第二次握手,服务端 accept 成功返回是在三次握手成功之后。

客户端调用 close 了,连接是断开的流程是什么?

在这里插入图片描述

  • 客户端调用 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 时间之后,也进入 CLOSED 状态;
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值