决战2022届秋招:TCP 协议一百问
决战2022届秋招:TCP 协议一百问
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yanglingwell/article/details/99685979
————————————————
号外~ 号外~字节跳动 2022 届校招提前批开始啦~
如何加入我们:
字节跳动校招内推码: UQAYUMY
投递链接: https://jobs.toutiao.com/s/eGx5Pv4或直接发送简历到邮件:yangling.leo@bytedance.com
我们是谁:字节跳动基础架构团队主要负责公司私有云建设,支撑着今日头条、抖音、西瓜视频等多款明星产品。我们积极拥抱开源和创新的软硬件架构,构建一系列基础设施引导研发活动的最佳实践,为整个公司的发展保驾护航。
我们在找谁:2022届获得本科及以上学历,计算机相关专业热爱计算机科学和互联网技术掌握扎实的计算机基础知识,深入理解数据结构、算法和操作系统知识
TCP 协议一百问
一、为什么大家常说 “TCP 协议是面向连接的,面向字节流的,可依赖的协议”?
- 面向连接的(connection-oriented)协议: 应用程序使用 TCP 协议进行数据交换前,需要先与对端建立 TCP 连接。
我认为,所谓 TCP 连接,就是建立连接的双方知道对端的基本信息并且确认对端能够顺利地进行数据交换。
- 面向字节流的(byte stream) 协议:TCP 协议不知道上层发送数据的格式或类型,对它而言,所有待发送的数据都只是一串字节流,没有起点和终点。
- 可依赖的(reliable)协议:TCP 协议通过以下手段来实现数据交换的可依赖:
(1) 应用程序发送的根据网络环境切割成合适大小的 数据段(Segment)。如果接收方发现数据有误,只需要丢弃一个数据段的数据,降低重传成本。
(2)通过 TCP 协议发送数据之后,会等待对端发送确认信息(ACK),以保证对端已正确接收数据。
(3)TCP 协议维护有 重传计时器(retransmission timer),指定时间内没有收到对端的确认信息,TCP 就会认为该数据段已经丢失。然后,它会重新发送该数据。
(4)TCP 数据段的 校验和(checksum) 字段能从一定程度上保证当前数据段的正确性。
(5)在 TCP 数据段 序列号(sequence number) 的帮助下,TCP 协议能够处理乱序到达的数据。
(6)TCP 协议会丢弃接收到 重复数据(duplicate data)。
(7)TCP 协议提供了详细的 流量控制(flow control) 方式。简单总结就是:分段发送、确认和重传机制、数据段的正确性校验、乱序数据和重复数据的处理以及流量控制。
二、为什么 TCP 协议首部没有数据段总长度字段,而 UDP 协议和 IP 协议有数据报和数据包总长度的字段?
- IP 协议 依赖的下层协议,如 以太网(Ethernet) 协议,可能有最小 帧(frame) 的要求,如果 IP 数据包(packet) 的长度小于最小帧长度,就会填充无意义的数据。因此 IP 数据包的长度不能复用 数据链路层(data link layer) 的数据帧长度,需要数据包首部有长度字段。
- 参考文献[1] 说 UDP 协议 的长度字段是多余的,因为它的 数据报(datagram) 长度是可以由 IP 协议的长度字段计算得出。
我觉得由于 UDP 协议 是 面向事务(transaction oriented) 的,因此它需要将自己的长度保存在首部,方便将可能被拆散的数据还原原本的数据报(毕竟谁也不能保证传输层协议一定总是 IP 协议)。The UDP Length field is redundant; the IP header contains the datagram’s total length. — 参考文献[1]
- 由于 TCP 协议是面向数据流的,因此可以不用担心一个数据段由于传输原因被拆分成几个数据段的问题。
后续几个问题都会涉及到关于 TCP 首部(header) 的问题,因此先在此处贴上 TCP 首部的内容(下图出自 参考文献[2]):
三、URG 标识位和 Urgent Pointer 只能标识紧急数据的结束位置,那么接收端是怎么指定紧急数据的起始位置呢?
- 答案是它并不知道,也不需要知道。
There is no way to specify where the urgent data starts in the data stream. — 参考文献[1]
- 由于 TCP 协议是面向字节流的,因此我们想要快速接收紧急数据,必须先将非紧急数据之前的数据都确认接收才行。
所以 TCP 协议并不在乎紧急数据的起点在哪里,它会认为 Urgent Pointer 之前的数据都是紧急的。 - TCP 接收到 URG 数据段之后,就会将当前状态设为 紧急模式(urgent mode), 在 (Sequence Number Urgent Pointer) 之前的数据都会被认为是紧急数据处理(包括 Sequence Number 之前还未到达的数据)。
- 紧急数据被处理完之后,TCP 会恢复 普通模式(normal mode) 处理后续到达的数据。
- 题外话:处于紧急模式下的接收端收到新的 URG 数据段,会将紧急数据的结束位置更新。
If the urgent pointer is updated while the user is in "urgent mode", the update will be invisible to the user. — 参考文献[2]
四、URG 标识位和 PSH 标识位的区别是什么?
- 应用程序通过设置 PSH 标识位来通知 TCP,这些数据需要尽快被发送。对端收到带有 PSH 位的数据段后,会立即将接收到的所有数据发送到应用层。
It(PSH) is a notification from the sender to receiver for the receiver to pass all the data that it has to the receiving process. — 参考文献[1]
- URG 标识位通知对端需要进入紧急模式,优先读取Urgent Pointer 之前的数据(PSH 则没有优先的这层意思,它只是告诉对端:“我发送的数据告一段落,你可以先将前面的数据推给应用层了”)。
五、 为什么 SYN 和 FIN 标识位需要的空数据段需要消耗一个序列号,而 ACK 和 RST 的空数据段则不需要?
因为 SYN 数据段和 FIN 数据段需要被确认(ACK),而 ACK 和 RST 数据段不需要。需要确认的数据段至少需要消耗一个序列号,以便能够确认对端接收到了该数据。
后续几个问题都会涉及到关于 TCP 的状态转换的问题,因此先在此处贴上 TCP 状态转换图(下图出自 参看文献[5]):
六、FIN_WAIT_2 被称为 Half-Close 。如果在该状态下,对端故意一直不发送 FIN 结束连接,是不是本端永远无法结束该状态?
- TCP 协议本身对 Half-Close 没有超时限制。理论上讲对端如果恶意不发送 FIN 关闭连接,该链接将会永远处于 FIN_WAIT_2 状态。
- 应用层非优雅关闭 socket 可以触发 RST 强制结束 FIN_WAIT_2 状态(数据接收缓冲区还有数据,直接发送 FIN。或者 “close l_onoff=1 l_linger = 0”。更多详情参看 参考文献[7] 图7-12)。
- Linux 通过/proc/sys/net/ipv4/tcp_fin_timeout文件来控制的超时时长。
七、TIME_WAIT 状态下接收到对端发送的数据,会做怎样的处理?
- 如果对端发来的是 FIN 数据段,TCP 会向对端发送 ACK 数据段,然后重启 2MSL 的计时。
- 如果对端发来的是非 FIN 数据段,TCP 会直接丢弃掉该数据段,不做任何操作。
八、TIME_WAIT 状态为什么需要等待 2MSL?
- 假设不等待 2MSL 时间,直接将本端设为 CLOSED 状态,而此时对端先接受到了最后一次的 ACK, 然后重新和本端建立一模一样的连接。
这时连接建立之前在网络游走的之前连接的数据传送到了本端,就会被当做是新的连接的数据被错误地解析。因此,需要等待 2MSL 时间来保证之前的连接的数据已经在网络中被丢弃。 - 假设不等待 2MSL 时间,直接将本端设为 CLOSED 状态,而此时最后一次的 ACK 在传输过程中丢失。对端将会重传 FIN,而本端收到 FIN 后发现现在已经没有这个连接了,就会发送 RST,强制重置对端的连接。
If the final ACK from end point 2 is dropped then the end point 1 will resend the final FIN. If the connection had transitioned to CLOSED on end point 2 then the only response possible would be to send an RST as the retransmitted FIN would be unexpected. This would cause end point 1 to receive an error even though all data was transmitted correctly. — 参考文献[6]
九、什么是 Half-Open 连接?如何识别和处理 Half-Open 的连接?(如果对端的电脑崩溃,已建立的连接会一直保持吗?)
- TCP 连接的某端在不通知对端的情况下,单方面的关闭或中断连接,这样的连接被称为 Half-Open 连接。常见的造成 Half-Open 的原因是操作系统崩溃,强制关闭计算机电源等。
下图展示了 Half-Open 出现的状态,出自 参考文献[8]:
- 只要不尝试通过 Half-Open 的连接发送数据,TCP 永远不会知道该连接是 Half-Open 的。这将会造成 TCP 资源的浪费。
As long as there is no attempt to transfer data across a half-open connection, the end that's still up won't detect that the other end has crashed. — 参考文献[1]
- 参考文献[7] 第7.5.5 介绍的 SO_KEEPALIVE 选项可以实现对 Half-Open 状态的探测。
设置了该选项后,指定时间内如果没有进行过数据交换,就会进行自动向对端发送 keep-alive probe 数据段:
(1)如果收到 ACK,则表示连接正常,不做任何处理。
(2)如果收到 RST,则表示对端已经不存在该连接,本端也关闭连接。
(3)如果没响应,则会在重试几次后,关闭本端的连接(认为对端不可达,连接已不存在)。Linux 系统在/proc/sys/net/ipv4路径下提供了三个文件来控制 keep-alive probe(见 参考文献[9]):
- tcp_keepalive_time:指定多久没有进行数据交换后,发送 keep-alive probe。我的系统默认设置的是 7200(s), 即 2 小时内没做数据交换就会进行探测。
- tcp_keepalive_intvl: 指定 keep-alive probe 没响应多久后重试。我的系统默认设置的是 75 (s),即如果没收到探测的 ACK,就会在每 75 s 重新发送探测。
- tcp_keepalive_probes*: 指定 keep-alive probe 发送几次后会认为对端不可达。我的系统设置的是 9,即如果发送了 9 次探测都没有收到 ACK,就认为对端不可达。
- 应用程序可以通过心跳算法来识别和处理 Half-Open 的连接。如 参考文献[10] 。
十、Half-Open 连接和 Half-Close 连接的区别是什么?
- Half-Close 是 TCP 连接的正常状态,确认本端已没有数据需要发送,等待对端主动发送 FIN,结束连接。
- Half-Open 是一种错误的 TCP 连接,通常是某一段异常结束导致的。
- 从理论上来讲,处于 Half-Close 状态的连接也可能遇到 Half-Open 的情况。
十一、我们知道 TCP 的流量控制(包括拥塞控制)的算法很多,请简述各种算法是为了解决什么样的问题。
流量控制的算法当然是为了解决流量控制的问题咯。(@v@)
TODO:每个算法具体的作用
- Nagle 算法 和 延迟确认(Delayed Acknowledgment)算法 主要是为了解决小块数据传输问题,避免网络中传输的数据段都是很小的数据段。
(1)Nagle 算法主要从发送方的角度控制小数据段的发送数量。
(2)延迟确认算法主要从接收方的角度控制空 ACK 数据段的发送数量 - 慢启动(Slow Start)算法,拥塞避免(Congestion Avoidance)算法,快速重传(Fast Retransmit)算法 和 快速恢复(Fast Recovery) 算法, 主要是为了解决大块数传输问题。尽可能的利用网络带宽尽最大努力的传输数据,同时避免网络拥塞。
(1)慢启动算法:在连接刚建立或者数据段发送超时的情况下,用来增加 拥塞窗口(congestion window,cwnd) 的算法。十二、为什么说 Nagle 算法和延迟确认算法一起使用会影响网络性能?
- Nagle 算法在还有发送的数据没被 ACK 之前,尽量不发送小数据段。从而让很多不同时间段发送的小数据段可以合并成大的数据段一次发送,减少网络中小数据段的数量。
- 延迟确认算***等待一定的时间(如 200 ms),从而让更多的数据一次确认或者在 ACK 数据段中携带后续发送的数据,减少网络中的小数据段(空 ACK 数据段)的数量。
- 当 Nagle 算法遇到延迟确认算法就可能会出现互相等待对方的情况:延迟确认算法想等待尽可能多的数据一次确认,Nagle 算法想等待对端的 ACK 数据段来触发小数据段的发送。因此会导致网络性能的降低。参看 参考文献[11]。
十三、慢启动算法是在每一次接收到 ACK 数据段后,cwnd 增加 1,可为什么总是说慢启动的 cwnd 的增长是指数级的?
- 以 ACK 接收的数量作为横坐标来看,的确 cwnd 是线性增长的。
- 理想情况下,刚开始,cwnd = 1, 发送方第一次发送一个数据段,等待接收到它的 ACK 数据段。当发送方接收到 ACK 数据段后,cwnd = 2, 发送两个数据段,然后等待这两个数据段的 ACK。当发送方接收到这两个 ACK 数据段后,cwnd = 4,会发送四个数据段,然后等待接收这四个数据段的 ACK。忽略发送数据前的耗时,每个轮次大约花费一个 RTT。因此以时间为单位(或 RTT),cwnd 是指数增长的。
TO BE CONTINUED...
如果觉得对您有帮助,欢迎评论,点赞和关注我的博客( https://blog.csdn.net/yanglingwell ),么么哒!最后留下一个问题,欢迎大家在评论区回答: 如果在 TIME_WAIT 状态的计算机崩溃并重启,这样就保证不了 2MSL 的等待时间。这会造成什么样的问题?TCP 协议是怎么解决的?
参考文献
- [ 1 ] TCP/IP详解 卷1:协议(英文版)
- [ 2 ] RFC 793 - Transmission Control Protocol
- [ 3 ] Can anyone Explain me difference Between PSH and URG flag in TCP segment - stackexchange.com
- [ 4 ] Why does a SYN or FIN bit in a TCP segment consume a byte in the sequence number space? - stackoverflow.com
- [ 5 ] TCP/IP State Transition Diagram (RFC793)
- [ 6 ] TIME_WAIT and its design implications for protocols and scalable client server systems
- [ 7 ] UNIX网络编程 卷1:套接字联网API(第3版)
- [ 8 ] TCP keepalive overview - TCP Keepalive HOWTO
- [ 9 ] Using TCP keepalive under Linux - TCP Keepalive HOWTO
- [ 10 ] Detecting Dead TCP Connections with Heartbeats and TCP Keepalives - rabbitmq.com
- [ 11 ] questions about nagle vs. delayed ack - serverfault.com