03-网络 TCP UDP
TCP(Transmission Control Protocol, 传输控制协议):面向连接
- 必须先启动服务端,再启动客户端 –> 连接服务端。
- 安全、可靠、面向连接(不会丢包)。
TCP 如何保证传输的可靠性?
- 三次握手和四次挥手来确保连接可靠。
- 对失序数据包重新排序以及去重:为了防止服务器端接收重复的数据和对数据进行排序,TCP 引入了序列号。TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。
- 校验和:校验和确保数据正确和合法性。TCP 将保持它首部和数据的检验和。在发送和接收时都要计算校验和;同时可以使用 md5 对数据进行加密。目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。
- 确认和重传机制:在数据包丢失或延迟的情况下,重新发送数据包,直到收到对方的确认应答(ACK)。TCP 重传机制主要有:基于计时器的重传(也就是超时重传)、快速重传(基于接收端的反馈信息来引发重传)、SACK(在快速重传的基础上,返回最近收到的报文段的序列号范围,这样客户端就知道,哪些数据包已经到达服务器了)、D-SACK(重复 SACK,在 SACK 的基础上,额外携带信息,告知发送方有哪些数据包自己重复接收了)。关于重传机制的详细介绍,可以查看详解 TCP 超时与重传机制这篇文章。
- 流量控制。TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议(TCP 利用滑动窗口实现流量控制)。
- 拥塞控制。当网络拥塞时,减少数据的发送。TCP 在发送数据的时候,需要考虑两个因素:一是接收方的接收能力,二是网络的拥塞程度。接收方的接收能力由滑动窗口表示,表示接收方还有多少缓冲区可以用来接收数据。网络的拥塞程度由拥塞窗口表示,它是发送方根据网络状况自己维护的一个值,表示发送方认为可以在网络中传输的数据量。发送方发送数据的大小是滑动窗口和拥塞窗口的最小值,这样可以保证发送方既不会超过接收方的接收能力,也不会造成网络的过度拥塞。
流量控制:滑动窗口
为什么需要流量控制? 这是因为双方在通信的时候,发送方的速率与接收方的速率是不一定相等,如果发送方的发送速率太快,会导致接收方处理不过来。如果接收方处理不过来的话,就只能把处理不过来的数据存在 接收缓冲区(Receiving Buffers) 里(失序的数据包也会被存放在缓存区里)。如果缓存区满了发送方还在狂发数据的话,接收方只能把收到的数据包丢掉。出现丢包问题的同时又疯狂浪费着珍贵的网络资源。因此,我们需要控制发送方的发送速率,让接收方与发送方处于一种动态平衡才好。
TCP 是双工的协议,会话的双方都可以同时接收、发送数据。TCP 会话的双方都各自维护一个 发送窗口
和一个 接收窗口
。
TCP 发送窗口可以划分成四个部分:
- 已经发送并且确认的 TCP 段(已经发送并确认);
- 已经发送但是没有确认的 TCP 段(已经发送未确认);
- 未发送但是接收方准备接收的 TCP 段(可以发送);
- 未发送并且接收方也并未准备接受的 TCP 段(不可发送)。
TCP 发送窗口结构图示:
- SND.WND:发送窗口。
- SND.UNA:Send Unacknowledged 指针,指向发送窗口的第一个字节。
- SND.NXT:Send Next 指针,指向可用窗口的第一个字节。
可用窗口大小 = SND.UNA + SND.WND - SND.NXT
。
TCP 接收窗口可以划分成三个部分:
- 已经接收并且已经确认的 TCP 段(已经接收并确认);
- 等待接收且允许发送方发送 TCP 段(可以接收未确认);
- 不可接收且不允许发送方发送 TCP 段(不可接收)。
TCP 接收窗口结构图示:
接收窗口的大小是根据接收端处理数据的速度动态调整的。 如果接收端读取数据快,接收窗口可能会扩大。否则,它可能会缩小。
另外,这里的滑动窗口大小只是为了演示使用,实际窗口大小通常会远远大于这个值。
- 各自的
接收窗口
大小取决于应用、系统、硬件的限制(TCP 传输速率不能大于应用的数据处理速率)。 - 各自的
发送窗口
则取决于对端通告的接收窗口
,两者相同。 - 发送窗口只有收到对端对于本端发送窗口内字节的 ACK 确认,才会移动发送窗口的左边界。
- 接收窗口只有在前面所有的段都确认的情况下才会移动左边界。当在前面还有字节未接收但收到后面字节的情况下,窗口不会移动,并不对后续字节确认。以此确保对端会对这些数据重传。
- TCP并不是每一个报文段都会回复 ACK 的,可能会对两个报文段发送一个 ACK,也可能会对多个报文段发送 1 个 ACK【累计ACK】,比如说发送方有 1/2/3 3个报文段,先发送了 2/3 两个报文段,但是接收方期望收到 1 报文段,这个时候 2/3 报文段就只能放在缓存中等待 1 报文段 的空洞被填上,如果 1 报文段一直不来,2/3 报文段也将被丢弃,如果 1 报文段来了,那么会发送一个 ACK 对这 3 个报文进行一次确认。
TCP 的拥塞控制是怎么实现的
在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫拥塞。
拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。相反,流量控制往往是点对点通信量的控制,是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率,以便使接收端来得及接收。
为了进行拥塞控制,TCP 发送方要维持一个 拥塞窗口(cwnd) 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。
在 TCP 中,cwnd 的单位是字节。具体来说,如果 TCP 每次传输都是按照 MSS(Maximum Segment Size)大小来发送数据,那么 cwnd 可以按照数据包个数来理解,但一般会以字节为单位来表示。如果没有特别说明是字节,那么当 cwnd 增加 1 时,就相当于字节数增加了 1 个 MSS 大小。
TCP 的拥塞控制采用了四种算法,即 慢开始、 拥塞避免、快重传 和 快恢复。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。
慢启动:慢开始算法的思路是当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么可能会引起网络阻塞,因为现在还不知道网络的符合情况。经验表明,较好的方法是先探测一下,在发送和数据被对方确认的过程中去计算对方的接收速度,来逐步增加每次发送的数据量,最后到达一个稳定的值,进入高速传输阶段。
慢启动的算法记住一个规则:当发送方每收到一个 ACK,拥塞窗口(cwnd)的大小就会增长,增加的大小就是已确认段的数目。也就是说,每经过一轮 RTT,cwnd 大小翻倍。这种情况一直保持到要么没有收到一些段,要么窗口大小到达预先定义的阈值。如果发生丢失事件,TCP 就认为这是网络阻塞,就会采取措施减轻网络拥挤。一旦发生丢失事件或者到达阈值,TCP 就会进入线性增长阶段。这时,每经过一个 RTT 窗口增长一个 MSS。。
慢启动中拥塞窗口的 cwnd 值,开始是1,接下开是指数型增涨的:1、2、4、8、16 …..拥塞避免:TCP 使用了一个叫慢启动门限(
ssthresh
)的变量,一旦cwnd>=ssthresh
(大多数 TCP 的实现,通常大小都是65536),慢启动过程结束,拥塞避免阶段开始。
cwnd 的值不再指数级往上升,开始加法增加。此时当窗口中所有的报文段都被确认时,cwnd 的大小加 1,cwnd 的值就随着 RTT 开始线性增加,这样就可以避免增长过快导致网络拥塞,慢慢的增加调整到网络的最佳值。这样显然慢多了。当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种:
超时重传:当发生了超时重传,则就会使用拥塞发生算法。这个时候,ssthresh 和 cwnd 的值会发生变化:
ssthresh = cwnd/2
ssthresh 降低为拥塞窗口(cwnd)值的一半。cwnd = 1
cwnd 重新设置为1。- 重新进入慢启动过程。
快速重传机制: TCP 引入了一种叫 Fast Retransmit 的算法,不以时间驱动,而以数据驱动重传。当接收方发现丢了一个中间包的时候,发送三次前一个包的 ACK,于是发送端就会快速地重传,不必等待超时再重传。
TCP 认为这种情况不严重,因为大部分没丢,只丢了一小部分,则 ssthresh 和 cwnd 变化如下:ssthresh = cwnd/2
,也就是设置为原来的一半。cwnd = ssthresh/2
。- 进入快速恢复算法。
快速恢复:快速重传和快速恢复算法一般同时使用,快速恢复算法认为,你还能收到 3 个重复 ACK 说明网络也不那么糟糕,所以没有必要像 RTO 超时那么强烈。
正如前面所说,进入快速恢复之前,cwnd 和 ssthresh 已被更新了:ssthresh = cwnd/2
,也就是设置为原来的一半。cwnd = ssthresh/2
。
然后,进入快速恢复算法如下:- 当收到 3 个重复的报文段时,重新计算拥塞窗口
cwnd = ssthresh + 3
(3 的意思是确认有 3 个数据包被收到了),然后立即重传丢失的数据包。 - 如果收到重复的 ACK,那么 cwnd 增加 1。
- 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从重复 ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态。
seq = Sequence number(顺序号码), ack = Acknowledge number(确认号码)
ARQ 协议了解吗?
自动重传请求(Automatic Repeat-reQuest,ARQ)是 OSI 模型中数据链路层和传输层的错误纠正协议之一。它通过使用确认和超时这两个机制,在不可靠服务的基础上实现可靠的信息传输。如果发送方在发送后一段时间之内没有收到确认信息(Acknowledgements,就是我们常说的 ACK),它通常会重新发送,直到收到确认或者重试超过一定的次数。
ARQ 包括停止等待 ARQ 协议和连续 ARQ 协议。
停止等待 ARQ 协议
停止等待协议是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认(回复 ACK)。如果过了一段时间(超时时间后),还是没有收到 ACK 确认,说明没有发送成功,需要重新发送,直到收到确认后再发下一个分组;
在停止等待协议中,若接收方收到重复分组,就丢弃该分组,但同时还要发送确认。
1) 无差错情况:
发送方发送分组,接收方在规定时间内收到,并且回复确认。发送方发送新消息。
2) 出现差错情况(超时重传):
停止等待协议中超时重传是指:只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重传时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为 自动重传请求 ARQ 。另外在停止等待协议中若收到重复分组,就丢弃该分组,但同时还要发送确认。
3) 确认丢失和确认迟到
- 确认丢失:确认消息在传输过程丢失。
- 当 A 发送 M1 消息,B 收到后,B 向 A 发送了一个 M1 确认消息,但却在传输过程中丢失。
- 而 A 并不知道,在超时计时过后,A 重传 M1 消息,B 再次收到该消息后采取以下两点措施:
- 丢弃这个重复的 M1 消息,不向上层交付。
- 向 A 发送确认消息。(不会认为已经发送过了,就不再发送。A 能重传,就证明 B 的确认消息丢失)。
- 确认迟到:确认消息在传输过程中迟到。
- A 发送 M1 消息,B 收到并发送确认。在超时时间内没有收到确认消息,A 重传 M1 消息,B 仍然收到并继续发送确认消息(B 收到了 2 份 M1)。
- 此时 A 收到了 B 第二次发送的确认消息。接着发送其他数据。过了一会,A 收到了 B 第一次发送的对 M1 的确认消息(A 也收到了 2 份确认消息)。处理如下:
- A 收到重复的确认后,直接丢弃。
- B 收到重复的 M1 后,也直接丢弃重复的 M1。
连续 ARQ 协议
连续 ARQ 协议可提高信道利用率。发送方维持一个发送窗口,凡位于发送窗口内的分组可以连续发送出去,而不需要等待对方确认。接收方一般采用累计确认,对按序到达的最后一个分组发送确认,表明到这个分组为止的所有分组都已经正确收到了。
- 优点: 信道利用率高,容易实现,即使确认丢失,也不必重传。
- 缺点: 不能向发送方反映出接收方已经正确收到的所有分组的信息。 比如:发送方发送了 5 条 消息,中间第三条丢失(3 号),这时接收方只能对前两个发送确认。发送方无法知道后三个分组的下落,而只好把后三个全部重传一次。这也叫 Go-Back-N(回退 N),表示需要退回来重传已经发送过的 N 个消息。
超时重传如何实现?超时重传时间怎么确定?
当发送方发送数据之后,它启动一个定时器,等待目的端确认收到这个报文段。接收端实体对已成功收到的包发回一个相应的确认信息(ACK)。如果发送端实体在合理的往返时延(RTT)内未收到确认消息,那么对应的数据包就被假设为已丢失并进行重传。
- RTT(Round Trip Time):往返时间,也就是数据包从发出去到收到对应 ACK 的时间。
- RTO(Retransmission Time Out):重传超时时间,即从数据发送时刻算起,超过这个时间便执行重传。
RTO 的确定是一个关键问题,因为它直接影响到 TCP 的性能和效率。如果 RTO 设置得太小,会导致不必要的重传,增加网络负担;如果 RTO 设置得太大,会导致数据传输的延迟,降低吞吐量。因此,RTO 应该根据网络的实际状况,动态地进行调整。
RTT 的值会随着网络的波动而变化,所以 TCP 不能直接使用 RTT 作为 RTO。为了动态地调整 RTO,TCP 协议采用了一些算法,如加权移动平均(EWMA)算法,Karn 算法,Jacobson 算法等,这些算法都是根据往返时延(RTT)的测量和变化来估计 RTO 的值。
建立连接-TCP 三次握手
第一次握手:
- Client 向 Server 发起一次建立连接的请求
SYN=1, seq=随机值x
。进入SYN_SENT
状态。
- Client 向 Server 发起一次建立连接的请求
第二次握手:
- Server 向 Client 确认标识,并发送自己的标识
SYN=1, ACK=1, seq=随机值y, ack=随机值x+1
。进入SYN_RCVD
状态。
- Server 向 Client 确认标识,并发送自己的标识
第三次握手:
- Client 确认标识,建立连接,开始传输数据
ACK=1, seq=随机值x+1, ack=随机值y+1
。进入ESTABLISHED
状态。 - Server 收到
ACK=1, ack=y+1
之后,也进入ESTABLISHED
状态。完成三次握手,连接建立。
- Client 确认标识,建立连接,开始传输数据
ack=x+1
是因为默认包大小为 1。
断开连接-TCP 四次挥手
第一次挥手:
- Client 发送数据包
FIN=1, seq=x
到 Server,进入FIN_WAIT_1
状态,这表示没有数据要发送给 Server 了。
- Client 发送数据包
第二次挥手:
- Server 收到
FIN=1, seq=x
之后,发送ACK=1, ack=x+1
到 Client,进入CLOSE_WAIT
状态。此时 Client 已经没有要发送的数据了,但仍可以接受 Server 发来的数据。 - Client 收到
ACK=1, ack=x+1
之后进入FIN_WAIT_2
状态。
- Server 收到
第三次挥手:
- Server 发送数据包
FIN=1, seq=y
到 Client,进入LAST_ACK
状态;
- Server 发送数据包
第四次挥手:
- Client 收到服务器的
FIN=1, seq=y
后,进入TIME_WAIT
状态,接着发送ACK=1,ack=x+1
到 Server; - Server 收到后,确认
ack
后,变为CLOSED
状态,不再向客户端发送数据。 - Client 等待
2*MSL
(报文段最长寿命)时间后,也进入CLOSED
状态。完成四次挥手。
- Client 收到服务器的
CLOSING
: 这种状态比较特殊,实际情况中应该是很少见,属于一种比较罕见的例外状态。正常情况下,当你发送 FIN 报文后,按理来说是应该先收到(或同时收到)对方的 ACK 报文,再收到对方的 FIN 报文。但是 CLOSING 状态表示你发送 FIN 报文后,并没有收到对方的 ACK 报文,反而却也收到了对方的 FIN 报文。
什么情况下会出现此种情况呢?其实细想一下,也不难得出结论:那就是如果双方几乎在同时 close 一个 SOCKET 的话,那么就出现了双方同时发送 FIN 报文的情况,也即会出现 CLOSING 状态,表示双方都正在关闭 SOCKET 连接。
数据传输
某主机发送的 seq 和 ack 是根据上一个接收包的 seq、ack 和 len 得到,具体为:seq=ack,ack=seq+len
- 如果某一主机连续发了 4 个包,后三个包的 seq 和 ack 和第一个包的一样。
- seq 会单调增大。
- 如果握手完第一个数据包是客户端发送,第一个数据包的 seq 和 ack 和第三次握手的一样。
注:
- 如果 A 发到 B 连续几个包,seq 号不变,ack 号一直在变大,说明 A 一直在收 B 的数据,一直在给 B 应答。
- 如果 A 发到 B 连续几个包,seq 号一直变大,ack 号一直没变,说明 A 一直在向 B 发数据,不用给 B 应答,而是在等 B 的应答。
- tcp 包分为包头的内容,tcp 的包头长度是 32 字节,整个数据包的包头是 66 字节(不一定的),如果整个数据包是 66 字节的话,那内容长度就是 0。
- 每个 tcp 包都带有 win、ack,这些是告诉对方,我还可以接收数据的滑动窗口是多少,如果 A 发到 B 的包的 win 为 0,就是 A 告诉 B 说我现在滑动窗口为 0 了,你不要再发给我了,就说明 A 端环境有压力(如带宽满了等)。
- ack 可以理解为应答。A 发给 B 的 ack 是告诉 B,我已收到你发的数据包,收到 ack 这里了,你下次要发 seq 为 ack 号的给我。
第 2 次握手传回了 ACK,为什么还要传回 SYN
服务端传回发送端所发送的 ACK 是为了告诉客户端:“我接收到的信息确实就是你所发送的信号了”,这表明从客户端到服务端的通信是正常的。
回传 SYN 则是为了建立并确认从服务端到客户端的通信。
SYN 同步序列编号(Synchronize Sequence Numbers) 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务端之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务端使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement)消息响应。这样在客户机和服务端之间才能建立起可靠的 TCP 连接,数据才可以在客户机和服务端之间传递。
三次握手过程中可以携带数据吗
在 TCP 三次握手过程中,第三次握手是可以携带数据的(客户端发送完 ACK 确认包之后就进入 ESTABLISHED 状态了),这一点在 RFC 793 文档中有提到。也就是说,一旦完成了前两次握手,TCP 协议允许数据在第三次握手时开始传输。
如果第三次握手的 ACK 确认包丢失,但是客户端已经开始发送携带数据的包,那么服务端在收到这个携带数据的包时,如果该包中包含了 ACK 标记,服务端会将其视为有效的第三次握手确认。这样,连接就被认为是建立的,服务端会处理该数据包,并继续正常的数据传输流程。
初始序列号 seq 是什么
TCP 连接的一方 A,随机选择一个 32 位的序列号(Sequence Number)作为发送数据的初始序列号(Initial Sequence Number,ISN),比如为 1000,以该序列号为原点,对要传送的数据进行编号:1001、1002…
三次握手时,把这个初始序列号传送给另一方 B,以便在传输数据时,B 可以确认什么样的数据编号是合法的;同时在进行数据传输时,A 还可以确认 B 收到的每一个字节,如果 A 收到了 B 的确认编号(acknowledge number)是 2001,就说明编号为 1001-2000 的数据已经被 B 成功接受。
为什么建立连接协议是三次握手,而关闭连接却是四次握手 or 为什么不能把服务器发送的 ACK 和 FIN 合并起来,变成三次挥手(CLOSE_WAIT 状态意义是什么)
这是因为服务端在 LISTEN
状态下的 SOCKET 收到 SYN 报文的连接请求后,它可以把 ACK 和 SYN(ACK 起应答作用,而 SYN 起同步作用)放在一个报文里来发送。
但关闭连接时,当收到对方的 FIN 报文通知时,它仅仅表示对方没有数据发送给你了,但未必你所有的数据都全部发送给对方了,所以你未必会马上关闭 SOCKET,你可能还需要发送一些数据给对方之后,再发送 FIN 报文给对方来表示你同意现在可以关闭连接了,所以这里的 ACK 报文和 FIN 报文多数情况下都是分开发送的。
TCP 三次握手为什么不能是两次
主要是防止两次握手情况下已经失效的连接请求突然又传送到服务器端而产生错误。
例如:客户端 A 向服务器端 B 发送 TCP 连接请求,第一个连接请求报文在网络的某个节点长时间滞留,A 超时后认为报文丢失,于是再重传一次连接请求,B 收到后建立连接。数据传输完毕后双方断开连接,而这时之前滞留的连接请求到达了服务器端 B,而 B 认为 A 又发来连接请求。如果两次握手建立连接,A 无连接请求,造成 B 的资源浪费。
可以采用四次握手吗
肯定可以。三次握手都可以保证连接成功了,何况是四次,但是会降低传输的效率。
三次握手的异常情况
第一次握手丢包:
- 主要是 客户端:默认情况下,
connect()
是阻塞式的,如果请求无法发送到 Server,那么connect()
会进行一段很长时间的等待和重试(重传次数和时间间隔我们暂且不去深究),此时我们可以使用通过设置SO_SNDTIMEO
来为connect()
设置超时以减少connect()
的等待时间。
- 主要是 客户端:默认情况下,
第二次握手丢包:
- 对于 客户端 来说,依然是 connect 超时,所以处理方式和第一次握手丢包是一样的。
- 对于 服务器 来说,会导致服务器端收不到第三次握手请求,所以会进行等待重传,直到多次重传失败后,关闭半连接。
这里需要提一下的是,服务器会维护一个半连接队列,用于等待客户的第三次握手请求。当收到第三次握手请求或者多次重传失败后,服务器会将该半连接从队列中删除。(这里暂且不去深究半连接队列的等待重新策略和配置)
我们经常听说的 DDos攻击,就可以这个环节实现,syn flood 就是一种常见的 DDos 攻击方式。简单来说,syn flood 就是只发送第一次握手请求后,就关闭连接,将服务器的半连接队列占满,从而让正常用户无法得到服务。
第三次握手丢包:
- 由于客户在发送第三次握手包后,不再等待确认,就直接进入了
ESTABLISHED
状态,所以一旦第三次握手失败,客户和服务器的状态就不同步了。 - 此时服务器会进行多次重发,一旦客户再次收到
SYN+ACK(第二次握手请求)
,会再次确认。
不过,如果第三次握手一直失败,则会出现客户端已经建立连接,而服务器关闭连接的情况。随后,一旦客户向服务器发送数据,则会收到一条 RST 回应,告诉用户连接已经重置,需要重新进行三次握手。
- 由于客户在发送第三次握手包后,不再等待确认,就直接进入了
如果已经建立了连接,但是客户端突然出现故障了怎么办
TCP 设有一个保活计时器。服务器收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置 2 小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔 75 分钟发送一次。若一连发送 10 个探测报文仍没反应,服务器认为客户端出了故障,接着就关闭连接。
四次挥手的异常情况
第一次挥手丢包:
- 客户端:处于
FIN_WAIT_1
,丢失会导致客户端重传,如果多次重传失败,客户端将停止重传,直接进入CLOSE
状态。
- 客户端:处于
第二次挥手丢包:
- 客户端:没有收到 ACK 确认,会重新发送 FIN 请求,如果多次重传失败,客户端将停止重传,直接进入
CLOSE
状态。 - 由于服务器第二次握手不会重发(ACK报文段是不会重传的),所以即使丢包也不管,直接向对方发送第三次握手的 FIN 包,此时客户端执行”同时关闭“的流程,等待
TIME_WAIT
时间后关闭。在客户端进入TIME_WAIT
之后,服务端由于 FIN 没有响应,会重发,如果被客户TIME_WAIT
收到并发送LAST-ACK
确认,则流程正常结束,如果反复重发没有响应,那么超时关闭。
- 客户端:没有收到 ACK 确认,会重新发送 FIN 请求,如果多次重传失败,客户端将停止重传,直接进入
第三次挥手丢包:
- 服务器会持续等待在
LAST_ACK
状态,客户端迟迟收不到这个 ACK,服务端就会重发 FIN 报文。 - 客户端会持续等待在
FIN_WAIT_2
状态 - 最后双方超时关闭。
- 服务器会持续等待在
第四次挥手丢包:
- 客户端进入
TIME_WAIT
状态,等待 2MSL 后关闭。 - 服务器由于收不到
LAST-ACK
响应则进行重发,如果多次重发失败,则超时关闭。
- 客户端进入
为什么 TIME_WAIT 状态还需要等 2MSL(Maximum Segment Life,报文段最大生存时间) 后才能返回到 CLOSED 状态
正常情况下双方都同意关闭连接了,而且握手的 4 个报文也都协调和发送完毕,按理可以直接回到 CLOSED
状态(就好比从 SYN_SENT
状态到 ESTABLISH
状态那样)。
但是我们必须假想网络是不可靠的,你无法保证你最后发送的 ACK 报文会一定被对方收到,因此对方处于 LAST_ACK
状态下的 SOCKET 可能会因为超时未收到 ACK 报文而重发 FIN 报文,所以这个 TIME_WAIT
状态的作用就是用来重发可能丢失的 ACK 报文。
考虑最坏的一种情况:第四次挥手的 ACK 包的最大生存时长(MSL) + 服务端重传的 FIN 包的最大生存时长(MSL) = 2MSL
MSL(Maximum Segment Lifetime) : 一个片段在网络中最大的存活时间,2MSL 就是一个发送和一个回复所需的最大时间。如果直到 2MSL,Client 都没有再次收到 FIN,那么 Client 推断 ACK 已经被成功接收,则结束 TCP 连接。
什么是半连接队列和全连接队列?
在 TCP 三次握手过程中,Linux 内核会维护两个队列来管理连接请求:
- 半连接队列(也称 SYN Queue):当服务端收到客户端的 SYN 请求时,此时双方还没有完全建立连接,它会把半连接状态的连接放在半连接队列。
- 全连接队列(也称 Accept Queue):当服务端收到客户端对 ACK 响应时,意味着三次握手成功完成,服务端会将该连接从半连接队列移动到全连接队列。如果未收到客户端的 ACK 响应,会进行重传,重传的等待时间通常是指数增长的。如果重传次数超过系统规定的最大重传次数,系统将从半连接队列中删除该连接信息。
这两个队列的存在是为了处理并发连接请求,确保服务端能够有效地管理新的连接请求。另外,新的连接请求被拒绝或忽略除了和每个队列的大小限制有关系之外,还和很多其他因素有关系,这里就不详细介绍了,整体逻辑比较复杂。
UDP(User Datagram Protocol, 用户数据协议):无连接
UDP 是一个面向报文(报文可以理解为一段段的数据)的协议。意思就是 UDP 只是报文的搬运工,不会对报文进行任何拆分和拼接操作。具体来说
- 在发送端,应用层将数据传递给传输层的
UDP
协议,UDP
只会给数据增加一个UDP
头标识下是UDP
协议,然后就传递给网络层了 - 在接收端,网络层将数据传递给传输层,
UDP
只去除IP
报文头就传递给应用层,不会任何拼接操作
UDP
是无连接的,也就是说通信不需要建立和断开连接。也是不可靠的。协议收到什么数据就传递什么数据,并且也不会备份数据,对方能不能收到是不关心的。UDP
没有拥塞控制,一直会以恒定的速度发送数据。即使网络条件不好,也不会对发送速率进行调整。这样实现的弊端就是在网络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些实时性要求高的场景(比如电话会议)就需要使用UDP
而不是TCP
。UDP
没有TCP
那么复杂,需要保证数据不丢失且有序到达。所以UDP
的头部开销小,只有八字节,相比TCP
的至少二十字节要少得多,在传输数据报文时是很高效的。头部包含了以下几个数据:
- 两个十六位的端口号,分别为源端口(可选字段)和目标端口。
- 整个数据报文的长度。
- 整个数据报文的检验和(
IPv4
可选字段),该字段用于发现头部信息和数据中的错误。
UDP
不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,也就是说UDP
提供了单播,多播,广播的功能。
TCP 与 UDP 的区别(重要)
- 是否面向连接:UDP 在传送数据之前不需要先建立连接。而 TCP 提供面向连接的服务,在传送数据之前必须先建立连接,数据传送结束后要释放连接。
- 是否是可靠传输:远地主机在收到 UDP 报文后,不需要给出任何确认,并且不保证数据不丢失,不保证是否顺序到达。TCP 提供可靠的传输服务,TCP 在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制。通过 TCP 连接传输的数据,无差错、不丢失、不重复、并且按序到达。
- 是否有状态:这个和上面的“是否可靠传输”相对应。TCP 传输是有状态的,这个有状态说的是 TCP 会去记录自己发送消息的状态比如消息是否发送了、是否被接收了等等。为此 ,TCP 需要维持复杂的连接状态表。而 UDP 是无状态服务,简单来说就是不管发出去之后的事情了(这很渣男!)。
- 传输效率:由于使用 TCP 进行传输的时候多了连接、确认、重传等机制,所以 TCP 的传输效率要比 UDP 低很多。
- 传输形式:TCP 是面向字节流的,UDP 是面向报文的。
- 首部开销:TCP 首部开销(20 ~ 60 字节)比 UDP 首部开销(8 字节)要大。
- 是否提供广播或多播服务:TCP 只支持点对点通信,UDP 支持一对一、一对多、多对一、多对多;
- TCP 通信类似于于要打个电话,接通了,确认身份后,才开始进行通行;
- UDP 通信类似于学校广播,靠着广播播报直接进行通信。
TCP 和 UDP 的应用场景
对某些实时性要求比较高的情况使用 UDP,一般用于即时通,比如游戏,媒体通信,实时直播,即使出现传输错误也可以容忍。
其它大部分情况下都是用 TCP,因为要求传输的内容可靠,不出现丢失的情况。
HTTP 基于 TCP 还是 UDP?
HTTP/3.0 之前是基于 TCP 协议的,所以发送 HTTP 请求之前首先要建立 TCP 连接也就是要经历 3 次握手。而 HTTP/3.0 将弃用 TCP,改用 基于 UDP 的 QUIC 协议 。
此变化解决了 HTTP/2 中存在的队头阻塞问题。队头阻塞是指在 HTTP/2.0 中,多个 HTTP 请求和响应共享一个 TCP 连接,如果其中一个请求或响应因为网络拥塞或丢包而被阻塞,那么后续的请求或响应也无法发送,导致整个连接的效率降低。这是由于 HTTP/2.0 在单个 TCP 连接上使用了多路复用,受到 TCP 拥塞控制的影响,少量的丢包就可能导致整个 TCP 连接上的所有流被阻塞。HTTP/3.0 在一定程度上解决了队头阻塞问题,一个连接建立多个不同的数据流,这些数据流之间独立互不影响,某个数据流发生丢包了,其数据流不受影响(本质上是多路复用+轮询)。
除了解决队头阻塞问题,HTTP/3.0 还可以减少握手过程的延迟。在 HTTP/2.0 中,如果要建立一个安全的 HTTPS 连接,需要经过 TCP 三次握手和 TLS 握手:
- TCP 三次握手:客户端和服务器交换 SYN 和 ACK 包,建立一个 TCP 连接。这个过程需要 1.5 个 RTT(round-trip time),即一个数据包从发送到接收的时间。
- TLS 握手:客户端和服务器交换密钥和证书,建立一个 TLS 加密层。这个过程需要至少 1 个 RTT(TLS 1.3)或者 2 个 RTT(TLS 1.2)。
所以,HTTP/2.0 的连接建立就至少需要 2.5 个 RTT(TLS 1.3)或者 3.5 个 RTT(TLS 1.2)。而在 HTTP/3.0 中,使用的 QUIC 协议(TLS 1.3,TLS 1.3 除了支持 1 个 RTT 的握手,还支持 0 个 RTT 的握手)连接建立仅需 0-RTT 或者 1-RTT。这意味着 QUIC 在最佳情况下不需要任何的额外往返时间就可以建立新连接。
相关证明可以参考下面这两个链接:
使用 TCP 的协议有哪些?使用 UDP 的协议有哪些?
运行于 TCP 协议之上的协议:
- HTTP 协议(HTTP/3.0 之前):超文本传输协议(HTTP,HyperText Transfer Protocol)是一种用于传输超文本和多媒体内容的协议,主要是为 Web 浏览器与 Web 服务器之间的通信而设计的。当我们使用浏览器浏览网页的时候,我们网页就是通过 HTTP 请求进行加载的。
- HTTPS 协议:更安全的超文本传输协议(HTTPS,Hypertext Transfer Protocol Secure),身披 SSL 外衣的 HTTP 协议
- FTP 协议:文件传输协议 FTP(File Transfer Protocol)是一种用于在计算机之间传输文件的协议,可以屏蔽操作系统和文件存储方式。
注意:FTP 是一种不安全的协议,因为它在传输过程中不会对数据进行加密。建议在传输敏感数据时使用更安全的协议,如 SFTP。 - SMTP 协议:简单邮件传输协议(SMTP,Simple Mail Transfer Protocol)的缩写,是一种用于发送电子邮件的协议。
注意:SMTP 协议只负责邮件的发送,而不是接收。要从邮件服务器接收邮件,需要使用 POP3 或 IMAP 协议。 - POP3/IMAP 协议:两者都是负责邮件接收的协议。IMAP 协议是比 POP3 更新的协议,它在功能和性能上都更加强大。IMAP 支持邮件搜索、标记、分类、归档等高级功能,而且可以在多个设备之间同步邮件状态。几乎所有现代电子邮件客户端和服务器都支持 IMAP。
- Telnet 协议:用于通过一个终端登陆到其他服务器。Telnet 协议的最大缺点之一是所有数据(包括用户名和密码)均以明文形式发送,这有潜在的安全风险。这就是为什么如今很少使用 Telnet,而是使用一种称为 SSH 的非常安全的网络传输协议的主要原因。
- SSH 协议: SSH( Secure Shell)是目前较可靠,专为远程登录会话和其他网络服务提供安全性的协议。利用 SSH 协议可以有效防止远程管理过程中的信息泄露问题。SSH 建立在可靠的传输协议 TCP 之上。
运行于 UDP 协议之上的协议:
- HTTP 协议(HTTP/3.0): HTTP/3.0 弃用 TCP,改用基于 UDP 的 QUIC 协议 。
- DHCP 协议:动态主机配置协议,动态配置 IP 地址。
运行于 UDP 和 TCP 协议之上的协议:
- DNS:域名系统(DNS,Domain Name System)将人类可读的域名 (例如,www.baidu.com) 转换为机器可读的 IP 地址 (例如,220.181.38.148)。 我们可以将其理解为专为互联网设计的电话薄。实际上,DNS 同时支持 UDP 和 TCP 协议。
什么是粘包?粘包的原因是什么?哪些情况会发生粘包现象?
只有 TCP 有粘包现象,UDP 永远不会粘包。
粘包:在获取数据时,出现数据的内容不是本应该接收的数据。如:对方第一次发送 hello,第二次发送 world,我方接收时,应该收两次,一次是 hello,一次是 world,但事实上是一次收到 helloworld,一次收到空,这种现象叫粘包。
原因:主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
什么情况会发生:
- 发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据很小,会合到一起,产生粘包)。
- 接收方不及时接收缓冲区的包,造成多个包同时接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)。