深入解析TCP协议:常见面试题、头部格式、连接数及粘包问题全攻略
TCP篇TCP常见面试题TCP头部格式有哪些?为什么需要 TCP 协议呢?TCP 是什么呢?什么是 TCP 连接呢?存在一个 IP 服务器,它监听了一个端口,那么这个服务器的 TCP 最大连接数是多少呢?还有 TCP 粘包问题。
粘包关系最大的是基于字节流这一特点。字节流可被理解为在双向通道中流淌的数据,此数据即为我们常说的二进制数据,简单而言就是大量的 01 串,这些 01 串之间不存在任何边界。应用层传到 TCP 协议的数据,并非以消息报文为单位向目的主机发送。而是以字节流的方式发送至下游。这些数据可能会被切割成各种数据包,也可能会被组装成各种数据包。接收端收到这些数据包后,未能正确还原原来的消息。所以出现了粘包现象。发送端每次发送消息时给消息带上识别消息边界的信息,接收端就能依据这些信息识别出消息的边界,进而区分出每个消息。
常见的方法有:加入特殊标志。可以利用特殊的标志来作为头尾,例如当收到了或者回车符时,就认为收到了新消息的开头,在这种情况下继续获取数据,一直到收到下一个头标志或者尾部标记,才将其视为一个完整的消息。
加入消息长度信息:
这个通常要与上面的特殊标志一同使用。当收到头标志时,里面能够带上消息长度,通过这种方式来表明在这之后的特定 byte 数量都属于这个消息。倘若在这之后恰好有符合长度的 byte,就将其取走,然后把它当作一个完整的消息提供给应用层使用。在实际场景里,HTTP 中的某个部分起到了类似的作用。当接收端接收到的消息长度比某个特定值要小的时候,这就意味着还有部分消息没有被收到。此时,接收端会一直等待,直到它拿到了足够的消息或者超时为止。
TCP和UDP的区别?分别的应用场景是?
应用场景
为什么TCP有首部长度字段而UDP没有呢?
TCP 首部中有选项字段,此选项字段的长度是可变的。UDP 的首部长度固定为八个字节。
为什么UDP头部有包长度字段而TCP头部没有呢?
TCP 的包长度能够依据 IP 包长度字节减去 IP 首部字段字节再减去 TCP 首部字节而得出。在 IP 首部字段中,IP 包长度与 IP 首部长度是已知的,而 TCP 首部字段长度在 TCP 首部字段中也是已知的。因此,可以计算得出 TCP 的包长度。UDP 实际上能够通过这个公式进行计算。不过,它可能是为了将首部字段大小补齐为 4 字节的整数倍而特意设计的,这样做的目的是便于网络硬件设备进行处理。
TCP和UDP可以同时绑定相同的端口吗?
在数据链路层可通过 MAC 地址寻找局域网中的设备;在网络层能通过 IP 地址寻找网络中的不同主机或路由器;在传输层则借助端口来识别同一台主机上的不同应用程序。所以在传输层,端口号的作用是区分同一主机不同应用程序的数据包。并且在操作系统的内核里,TCP 协议和 UDP 协议是完全独立的两个软件模块。当前主机收到网络包后,第一步是依据 IP 协议首部字段里的协议号来确认到底是 TCP 协议还是 UDP 协议。接着,再把确认好的网络包交给对应的模块去处理。而送给 TCP/UDP 模块的报文,则是根据“端口号”来确定要送给哪个具体的应用程序去处理。
编程模型有 TCP 编程模型和 UDP 编程模型。为什么 TCP 连接的建立是三次握手,而不是两次或四次呢?序列号和确认号又是如何变化的呢?
发送的TCP报文:
序列号的确认应答号的作用:
初始化序列号ISN是如何随机产生的?
ISN 等于 M 加上 F,其中 M 是一个计时器,它每隔 4 微秒就加 1;F 是一个哈希算法,能够根据一个四元组生成一个随机数值。
为什么每次建立连接时,都要求初始化的序列号要不一样呢?
主要原因是要避免新的相同四元组连接收到历史数据包,以免导致数据混乱。例如,当初始化的序列号相同,在网络拥堵时,客户端重传数据包,而服务器此时断电重启,之前的连接就会消失,服务器收到该数据包后会发送 RST 报文断开连接。然后服务器与客户端建立了具有相同四元组的连接。此时,被网络阻塞的数据包抵达了服务器。由于初始的序列号相同,所以该数据包刚好在服务器的接收窗口内。这就造成了历史的数据包被新的连接接收的问题。如果初始化序列号每次都不相同,那么在很大概率上能够避免这个问题。但需注意并非完全避免,因为存在序列号回绕的情况,这种情况需要借助时间戳机制来校验是否为历史数据包。
为什么会有 IP 分片呢?又为什么需要 TCP 层的 MSS 呢?如果第一次握手报文丢失了,会出现什么样的情况呢?要是第二次握手丢失了,又会是怎样的情形呢?倘若第三次握手丢失了,会有什么后果呢?什么是 SYN 攻击呢?怎样去防止 SYN 攻击呢?
如何处理?
TCP 连接断开的四次挥手为什么需要四次呢?第一次挥手如果丢失会出现什么情况?第二次挥手丢失又会怎样?第三次挥手丢失呢?第四次挥手丢失呢?为什么客户端要等待 2MSL 之后再关闭连接?为什么会有状态呢?
主要有两个原因,其一,要防止历史连接中的数据被后面相同四元组的连接错误接收;其二,要确保被动关闭的一方能够正确关闭。
过多有什么危害?
一是会占用系统资源,其中包括文件描述符、CPU 资源、线程资源以及内存资源等;二是会占用端口号资源,通常可以开启的端口号数量是有限的。并且服务器和客户端数量过多所带来的危害是不一样的。
服务器出现大量状态的原因有哪些?
状态是只有主动关闭才会有的状态。如果服务器频繁出现这个状态,那就意味着服务器在大量主动断开 TCP 连接。其主要原因有以下三个:一是没有使用 HTTP 长连接;二是 HTTP 长连接超时;三是 HTTP 长连接请求数量达到上限。
如果已经建立了连接,但是客户端突然故障了怎么办?
保活机制检测的时间稍长,我们能够在应用层自行实现一个心跳机制。例如,web 服务器通常会具备相应参数,用于设置 HTTP 长连接的超时时间。如果 HTTP 长连接的超时时间被设置为 60 秒,那么 web 服务器会开启一个定时器。当客户端完成一个请求后,如果在 60 秒内没有发起新的请求,一旦定时器到达时间,就会触发回调函数以回收该连接。
如果连接已经建立,但是服务端的进程崩溃了会发生什么?
TCP 连接的信息由内核负责维护。当服务端的进程崩溃时,内核需要回收该进程的所有 TCP 连接资源。因此,内核会发送第一次挥手 FIN 报文。后续的挥手过程也都是在内核中完成的,不需要进程参与。所以,即便服务端的进程退出了,依然能够与客户端完成 TCP 四次挥手的过程。
TCP重传、滑动窗口、流量控制、拥塞控制重传机制超时重传
它的使用场景有两种情况,一是数据包丢失,二是确认应答丢失。在发送数据的时候,会设定一个定时器。当超过了指定的时间后,如果没有收到对方的 ACK 确认应答报文,就会重新发送该数据。超时重传所需要的时间被称为 RTO,而包往返所需要的时间被称为 RTT。通常情况下,RTO 应该被设置为比 RTT 略大一些的值。如果 RTO 设置过小,可能包还未丢失就发生重传,进而导致网络拥堵,并且重传的数据越多,网络会越拥堵。若 RTO 太大,会导致丢包很久后才被发现,效率较低。而实际的网络处于动态变化中,RTT 的时间也不固定。因此,RTO 是一个动态的值。在 linux 操作系统里,它会计算 RTO 的值。其一,TCP 需要通过对 RTT 时间进行采样,接着进行加权平均,从而算出一个平滑 RTT 的值,并且这个值是不断变化的,原因是网络状况在持续变化。其二,除了对 RTT 进行采样,还要对 RTT 的波动范围进行采样,这样就能避免如果 RTT 出现一个大的波动,就很难被发现的情况。
快速重传
https://img2.baidu.com/it/u=3037231749,2141562665&fm=253&fmt=JPEG&app=138&f=JPEG?w=795&h=500
当发送方收到三个确认应答号相同的数据包时,就会触发快速重传。发送方发出了 1、2、3、4、5 份数据:首先,第一份 Seq1 送达了,接着发送方 Ack 回 2,这表示希望收到确认应答号为 2 的数据包。结果 Seq2 由于某些缘由未被收到,而 Seq3 抵达了,所以依旧 Ack 回 2;后续的 Seq4 和 Seq5 都已到达,但依然 Ack 回 2,原因是 Seq2 仍未被收到;发送端接收到三个 Ack = 2 的确认,知晓了 Seq2 还未收到,便会在定时器过期之前,重新传输丢失的 Seq2。最后收到了 Seq2。此时,Seq3、Seq4、Seq5 都已收到,所以 Ack 回了 6。因此,快速重传的工作方式为:当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。
快速重传机制仅解决了一个问题,此问题为超时时间的问题。然而,它仍面临着另一个问题,即重传时是重传一个还是重传所有的问题。假设发送方发送了 6 个数据,其编号顺序为 Seq1 到 Seq6 。然而,Seq2 和 Seq3 丢失了。当接收方收到 Seq4、Seq5 和 Seq6 时,它会回复 ACK2 给发送方。但发送方不知道这些连续的 ACK2 是接收方收到哪个报文后回复的。那么,是选择重传 Seq2 这一个报文呢,还是重传 Seq2 之后已经发送的所有报文(Seq2、Seq3、Seq4、Seq5、Seq6)呢?如果只选择重传 Seq2 这一个报文,那么重传的效率是比较低的。因为对于丢失的 Seq3 报文,需要在后续接收到三个重复的 ACK3 之后,才能够触发重传。如果选择重传 Seq2 之后已发送的所有报文,那么可以同时重传已丢失的 Seq2 和 Seq3 报文。然而,Seq4、Seq5、Seq6 的报文已经被接收过了,对于重传 Seq4 至 Seq6 这部分数据来说,相当于做了一次无用功,会浪费资源。可以看出,无论是重传一个报文,还是重传已发送的报文,都存在问题。
SACK
为了解决快速重传的问题,就出现了SACK。
重发时,只选择这个 TCP 段进行重复。
D-SACK
它是解决数据包没有丢失而确认应答丢失的情况。「接收方」发给「发送方」的两个 ACK 确认应答丢失了。发送方超时后,重传了第一个数据包(3000 ~ 3499)。「接收方」发现数据是重复收到的。于是「接收方」回了一个 SACK = 3000~3500,告知「发送方」3000~3500 的数据早已被接收。因为 ACK 已到 4000,这意味着 4000 之前的所有数据都已收到。所以这个 SACK 代表着 D-SACK。“发送方”得知了数据未丢失,而是“接收方”的 ACK 确认报文丢失了。
滑动窗口
我们都知晓,TCP 在每发送一个数据时,都需要进行一次确认应答。只有当上一个数据包收到了应答,才会发送下一个数据包。然而,这种方式存在缺点,其效率相对较低。为了解决这个问题,TCP 引入了窗口的概念。接着,需要指定窗口的大小,这个窗口大小指的是无需等待确认应答就可以继续发送数据的最大值。窗口的实现是操作系统开辟的一个缓存空间。发送方主机在等待确认应答返回之前,需要在缓冲区中保留已发送的数据。如果按期收到确认应答,那么数据就可以从缓存区被清除。
TCP 头中有一个字段,名为窗口大小。此字段用于接收端告知发送端自身还有多少缓冲区可用于接收数据。这样,发送端就能依据接收端的处理能力来发送数据,从而避免接收端处理不过来的情况。因此,通常情况下,窗口的大小是由接收方的窗口大小所决定的。发送方所发送的数据大小,不能比接收方的窗口大小更大。不然的话,接收方就不能正常地接收到数据了。
我设置的滑动窗口大小为 30 个字节。目前,10 到 20 个字节的数据已发送出去,但尚未收到确认应答。在此情况下,我们此刻仍可发送 10 窗口的数据。若前面 5 个字节的数据确认应答均已收到,滑动窗口便可继续发送数据。由此可见,滑动窗口实现了一个累计应答,能够提高整体的响应效率。
接收窗口和发送窗口的大小并非完全相等,因为滑动窗口不是固定不变的。例如,若接收方的应用进程读取数据的速度很快,那么接收窗口就能很快空缺出来。新的接收窗口大小是通过 TCP 报文中的字段告知发送方的。由于这个传输过程存在时延,所以接收窗口和发送窗口是约等于的关系。
流量控制
发送方给接收方发数据时,需考虑接收方的处理能力。若发送方发送数据过快,而接收方处理不了,就会触发重发机制,进而导致网络流量无端浪费。为避免这种现象发生,TCP 提供了一种机制,能让“发送方”依据“接收方”的实际接收能力来控制发送的数据量,这就是所谓的流量控制。它也是通过滑动窗口来实现的。
丢包问题
发送窗口中存放的字节数在操作系统内存缓冲区中,接收窗口中存放的字节数也在操作系统内存缓冲区中。操作系统的缓冲区会被操作系统调整。例如,当服务器非常繁忙时,可能会收缩内存缓冲区。如果窗口收缩的变更未及时通知客户端,而客户端仍按照上一次应答的窗口进行发送数据,就有可能超过窗口大小从而被服务器拒绝接收。为防止这种情况出现,TCP 规定不可以在减少缓存的同时收缩窗口。它采用先收缩窗口,过一段时间后再减少缓存的方式,这样就能避免丢包情况。
窗口收缩至 0 时,窗口会关闭且不再接收数据。同时会启动一个计时器,待接收方处理完数据后,会向发送方通告一个窗口非 0 的 ACK 报文。若超时未接收到该报文,发送方就会发送窗口探测(probe)报文。对方在确认这个探测报文时,会给出自己现在的接收窗口大小。
窗口探测的次数通常是 3 次。每次的时间大约在 30 到 60 秒之间,不过不同的实现情况可能会有所不同。如果经过 3 次探测后,接收窗口依然为 0,那么有的 TCP 实现会发送 RST 报文,以中断连接。
糊涂窗口综合症
开销为 40 个字节的首部用于传输几个字节的数据。解决方案有两个,其一为接收方不告知小窗口,其二为发送方不发送小数据包。
当“窗口大小”比 min( MSS,缓存空间/2 )小的时候,也就是比 MSS 与 1/2 缓存大小中的最小值还小的时候,会向发送方通告窗口为 0,这样就阻止了发送方再发送数据过来。等到接收方处理了一些数据之后,如果窗口大小大于等于 MSS,或者接收方缓存空间有一半可以使用,就能够把窗口打开,让发送方发送数据过来。
发送方运用 Nagle 算法,此算法的思路在于进行延时处理。只有当满足以下两个条件中的某一个条件时,才能够发送数据:其一,需等到窗口大小大于等于 MSS 并且数据大小也大于等于 MSS;其二,收到之前所发送数据的 ack 回包。
拥塞控制
前面的流量控制是为了避免“发送方”的数据将“接收方”的缓存填满,然而它并不能知晓网络中发生的情况。通常来讲,计算机网络处于一个共享的环境之中。所以也有这种可能,即由于其他主机之间的通信而导致网络出现拥堵。网络出现拥堵时,若持续发送大量数据包,可能引发数据包时延、丢失等状况。此时,TCP 会重传数据,然而一重传便会使网络负担加重,进而导致更大的延迟以及更多的丢包,这种情况会进入恶性循环并不断被放大……所以,当网络发送拥塞时,TCP 应降低发送的数据量。有了拥塞控制,其目的是避免“发送方”的数据将整个网络填满。为了让“发送方”能调节要发送数据的量,定义了一个名为“拥塞窗口”的概念。
拥塞窗口 cwnd 是发送方所维护的一个状态变量,它会依据网络的拥塞程度而发生动态变化。之前我们提到过发送窗口 swnd 和接收窗口 rwnd 是近似相等的关系。由于加入了拥塞窗口的概念,此时发送窗口的值为 swnd = min(cwnd, rwnd),即拥塞窗口与接收窗口中的最小值。
拥塞窗口cwnd变化的规则:
只要“发送方”在规定时间内未接收到 ACK 应答报文,即发生超时重传,就会认为网络出现拥塞。
慢启动
TCP 在连接刚建立完成之后,首先存在一个慢启动的过程。此慢启动意味着逐渐地增加发送数据包的数量。慢启动的算法如下:发送方每收到一个 ACK 时,拥塞窗口 cwnd 的大小就会增加 1。
连接建立完毕后,首先进行初始化,此时 cwnd = 1,这意味着可以传输一个 MSS 大小的数据。
收到一个 ACK 确认应答后,cwnd 会增加 1。这样一来,一次就能够发送 2 个。
收到 2 个 ACK 确认应答后,cwnd 会增加 2。这样一来,就能够比之前多发 2 个。所以这一次能够发送 4 个。
当这 4 个 ACK 确认到达时,每个确认会使 cwnd 增加 1。因为有 4 个确认,所以 cwnd 总共增加 4。这样一来,就能够比之前多发 4 个。因此,这一次可以发送 8 个。
可以看到它呈指数级增长。当拥塞窗口达到慢启动门限(slow start)时,就会采用拥塞避免算法。通常慢启动门限为 65535 个字节。
拥塞避免
达到慢启动门限后进入拥塞避免算法,其规则为:每当收到一个 ACK 时,cwnd 就增加 1/cwnd。
https://img0.baidu.com/it/u=386842988,1655582744&fm=253&fmt=JPEG&app=138&f=JPEG?w=636&h=500
接上前面的慢启动的例子,现在假定为 8。当有 8 个 ACK 应答确认到达时,因为每个确认会使 cwnd 增加 1/8,所以 8 个 ACK 确认会使 cwnd 一共增加 1。这样一来,这一次就能够发送 9 个 MSS 大小的数据,从而进入线性增长阶段。
拥塞控制
网络一直增长后,会慢慢进入拥塞状况,进而出现丢包现象,此时需要对丢失的数据包进行重传。触发重传机制后,就进入了“拥塞发生算法”。
重传中有两种算法,分别是超时重传和快速重传。当不同的重传算法被触发时,与之对应的拥塞控制算法也会有所不同。
发生超时重传时,慢启动门限被设置为发送拥塞时拥塞窗口大小的一半,即 cwnd/2。同时,拥塞窗口会重置它的初始值,这里假定 cwnd 的初始化值为 1。之后进入拥塞避免状态。
当发生快速重传时,其拥塞控制算法如下:慢启动被设置为拥塞时窗口大小的一半;拥塞窗口被设置为原来的一半再加上 3,这意味着收到了三个重复的 ACK;如果还收到重复的 ACK,就会再 + 1;之后进入拥塞避免状态。
在快速恢复的过程中,首先是 cwnd 的一半,接着 cwnd 增加 3,这表明网络可能出现了阻塞,所以需要减小 cwnd 来避免这种情况,加 3 意味着在快速重传时已经确认接收到了 3 个重复的数据包。
随后开始重传丢失的数据包。如果再次收到重复的 ACK,cwnd 就会增加 1。每个收到的重复的 ACK 包加 1,意味着这些包已经离开了网络。这个过程的目的是能够迅速地把丢失的数据包发送给目标。
收到新数据的 ACK 之后,将 cwnd 设置为第一步中的值,此时恢复过程结束。
IP篇IP首部字段
IP的基本认识IP地址基础知识无分类IP地址-CIDR
它将 32 比特的地址进行划分,分为两个部分,一部分是网络号,另一部分是主机号。其表示形式为 a.b.c.d/x,这里的/x 意味着前 x 位属于网络号,x 的取值范围是 0 到 32,这样就使 IP 地址具备了更高的灵活性。
IP地址与路由控制
IP 地址包含网络地址部分,此部分用于网络控制。路由控制包中记录着网络地址以及下一步应发送至的路由器的地址。主机和路由器都拥有各自的路由表。在发送 IP 包时,需先确定 IP 包首部中的目标地址,然后从路由控制表中找到与该地址具有相同网络地址的记录,依据该记录把 IP 包转发给相应的下一个路由器。如果路由控制表中存在多条记录,这些记录的网络地址相同。那么就选择其中相同位数最多的网络地址,也就是最长匹配的那个。
IPv6
- 在内部网络内进行单播通信时,可以使用本地单播地址,它相当于 IPv4 中的私有地址。在互联网中进行通信时可以运用全局单播地址,这种全局单播地址就如同 IPv4 里的共有地址。
IPv6相对于IPv4的改进IP协议相关技术DNS
域名进行解析。首先是本地 DNS 服务器,接着会到达本地缓存,然后依次经过根 DNS 服务器、顶级 DNS 服务以及权威域名服务器。
ARP
在传输一个 IP 数据包时,确定了源 IP 地址与目标 IP 地址后,依据主机的路由表能确定路由的下一跳。接着,网络层的下一层是数据链路层,我们也需要知晓下一跳的 MAC 地址。通过 ARP 协议,就能够求得下一跳的 MAC 地址。
最后将 ARP 响应包返回给主机。操作系统一般会将首次通过 ARP 获取到的 MAC 地址进行缓存。这样做是为了在下次需要时,能够直接从缓存中找到与相应 IP 地址对应的 MAC 地址。
DHCP
我们的电脑一般是借助 DHCP 来动态获得 IP 地址。首先要明确,DHCP 客户端进程会对 68 端口号进行监听,而 DHCP 服务端进程则会对 67 端口号进行监听。
客户端收到 DHCP ACK 后,交互就完成了。此时,客户端可以在租用期内使用 DHCP 服务器分配的 IP 地址。
如果租约的 DHCP IP 地址快到期了,那么客户端就会向服务器发送 DHCP 请求报文。
可以发现,在 DHCP 交互过程中,始终是采用 UDP 广播的方式进行通信。
但是如果 DHCP 服务器与客户端不在同一个局域网内,并且路由器不会转发广播包,那么此时就需要 DHCP 中继代理来承担转发 DHCP 请求包给 DHCP 服务器的任务。
NAT
解决方法:一是改用IPv6地址。
ICMP
互联网控制报文协议,主要功能有:确认 IP 包能否成功送达目标地址;报告发送过程中 IP 包被废弃的缘由;改善网络设置等。在 IP 通信里,若某个 IP 数据包因某种缘故未到达目标 IP 主机,此原因将由 ICMP 负责告知。ICMP可分为两类。一类是用于诊断的查询信息,这类信息也被称作查询报文类型。另一类是用于通知出错原因的错误信息,此信息也被称为差错报文类型。
IGMP
IGMP 是一种因特网组管理协议。它的工作范围在主机(组播成员)与最后一跳路由之间。
面试题
页:
[1]