Protocol

[极客时间] TCP协议 读后感

[极客时间] TCP协议 读后感

前言

网络协议中最重要的一块设计, 传输层协议, 其中对应长连接的就是 TCP 传输控制协议 - 维基百科 了.
刘超老师在讲解这篇的时候,给了一个贴心的口诀:

顺序问题 ,稳重不乱;

丢包问题,承诺靠谱;

连接维护,有始有终;

流量控制,把握分寸;

拥塞控制,知进知退。

短短几行, 就讲 TCP 的精髓囊括其中!

这里只是对刘超老师文章的个人总结, 不会长篇大论去讲解相关的概念.

一 数据包

问题集锦

  • 数据包含有 IP 吗?
  • 数据包中的端口是用来做什么的?
  • 数据包的序列号作用, 关于序列号,在连接时需要注意什么?
  • TCP 都有哪些 标志符 TCP Flag?
  • TCP 滑动窗口 作用? 拥塞和流量控制问题, 哪个是对内要求,哪个是对外要求? (经常会混的点)

知识点记录

tcp-data-struct.png

  • 如上图中所示, TCP 层是没有 IP 的概念的, IP 是在 TCP 的 下一层, TCP 包在经过 IP 层的时候会再增加 IP 的包头 .
  • TCP 是通过 Port 端口 进行应用的识别的, (应用进程会进行端口的绑定, 端口的转发)
  • TCP 序列号是为了解决信息的乱序问题, 这样构建信息的时候,才可以得到完整正确的信息.
    • 这里要注意的事, 连接时有初始化序列号的步骤, 且初始序列号两端并不是从1开始,也不一定相同. 这样做的原因一个是为了防止 tcp 包的冲突 (Client 端如果认定传输包丢失的话, 会重新发送序列包, 假设Client端挂了, 又一次重连, 而这个时候,之前重发的序列包,到达了 Server, 会造成 Serve 误认为该包是有效的最新包. 然后因为真正的序列号不匹配,导致连接出问题). 同时也可以解决 TCP序号预测攻击
    • Linux 采用基于 时钟 的方案,随机偏移(并有使用 hash 计算)生成 初始序号, 同一个 Client 端口 与 Server 端口 建立的连接, 其序列号重复的话,至少需要经过 4 个小时可能.
    • 如果含有同步化旗标(SYN),则此为最初的序列号;第一个数据比特的序列码为本序列号加一。
    • 如果没有同步化旗标(SYN),则此为第一个数据比特的序列码。
  • 确认号ack,32位长)— 期望收到的数据的开始序列号。也即已经收到的数据的字节长度加1。
  • 标志符 TCP Flag 如上图中 保留字后面的多个小格格 ,最常询问的是前三个, 握手与挥手中涉及到的
    • SYN — 为1表示这是连接请求或是连接接受请求,用于创建连接和使顺序号同步
    • ACK — 为1表示确认号字段有效
    • FIN — 为1表示发送方没有数据要传输了,要求释放连接
    • NS — ECN-nonce。
    • CWR — Congestion Window Reduced。
    • ECE — ECN-Echo有两种意思,取决于SYN标志的值。
    • URG — 为1表示高优先级数据包,紧急指针字段有效。 此标志表示TCP包的紧急指针域有效,用来保证TCP连接不被中断,并且督促中间层设备要尽快处理这些数据;
    • PSH — 为1表示是带有PUSH标志的数据,指示接收方应该尽快将这个报文段交给应用层而不用等待缓冲区装满
    • RST — 为1表示出现严重差错。可能需要重现创建TCP连接。还可以用于拒绝非法的报文段和拒绝连接请求。
  • 在没有事先指明的情况下,最大段大小的默认数值为 536 字节。任何主机都应该能够处理至少 576 字节的 IPv4 数据报
    • 20 字节的 TCP 头部加 20 字节的 IP 头部。

二 连接与断开连接

问题集锦

  • TCP 如何建立连接的,描述以下过程 ?
    • 为什么握手必须是 三次 ?
    • 为什么挥手必须是 四次 ?
    • 连接时要初始化哪些数据?
  • TCP 如果在每个握手的时候丢包,对应的客户端或服务器会如何处理?
  • TCP 长连接设计中, 心跳指的是什么, 如何优化这一块?

知识点记录

Tcp_state_diagram.png

tcp-state-change.jpeg

TCP 是面向连接的, 那么为了管理连接, 就有了状态管理. 具体状态如下 摘自 维基百科-传输控制协议:

以S指代服务器,C指代客户端,S&C表示两者,S/C表示两者之一:

  • LISTEN S
    • 服务器等待从任意远程TCP端口的连接请求。侦听状态。
  • SYN-SENT C
    • 客户在发送连接请求后等待匹配的连接请求。通过connect()函数向服务器发出一个同步(SYNC)信号后进入此状态。
  • SYN-RECEIVED S
    • 服务器已经收到并发送同步(SYNC)信号之后等待确认(ACK)请求。
  • ESTABLISHED S&C
    • 服务器与客户的连接已经打开,收到的数据可以发送给用户。数据传输步骤的正常情况。此时连接两端是平等的。这称作全连接。
  • FIN-WAIT-1 S&C
    • (服务器或客户)主动关闭端调用close()函数发出FIN请求包,表示本方的数据发送全部结束,等待TCP连接另一端的ACK确认包或FIN&ACK请求包。
  • FIN-WAIT-2 S&C
    • 主动关闭端在FIN-WAIT-1状态下收到ACK确认包,进入等待远程TCP的连接终止请求的半关闭状态。这时可以接收数据,但不再发送数据。
  • CLOSE-WAIT S&C
    • 被动关闭端接到FIN后,就发出ACK以回应FIN请求,并进入等待本地用户的连接终止请求的半关闭状态。这时可以发送数据,但不再接收数据。
  • CLOSING S&C
    • 在发出FIN后,又收到对方发来的FIN后,进入等待对方对己方的连接终止(FIN)的确认(ACK)的状态。少见。
  • LAST-ACK S&C
    • 被动关闭端全部数据发送完成之后,向主动关闭端发送FIN,进入等待确认包的状态。
  • TIME-WAIT S/C
    • 主动关闭端接收到FIN后,就发送ACK包,等待足够时间以确保被动关闭端收到了终止请求的确认包。【按照RFC 793,一个连接可以在TIME-WAIT保证最大四分钟,即最大分段寿命(maximum segment lifetime)的2倍】
  • CLOSED S&C
    • 完全没有连接。

  • TCP 建立连接的过程被大家称为 三次握手 或者 三路握手 (three-way handshake) .
  • 之所以 三次握手 而不是 两次握手的原因是.
    • (1) 最简单的情况是, Client 在发送了请求包后就挂掉了, Server二次握手也连接也无效.
    • (2) 或者 Client 发送请求包后, Server 收到后,也正常发送了响应包, 没想到响应包半路挂了. Server 也不知道,有不敢问收到没收到
    • (3) 为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误 假设网络环境差, Client 发送请求包后, Server 并没有收到, 所以 Client 可能多次发送 请求包, 其中一个请求包到达了 Server, 并得到了 Server 的应答, 两次握手成功, 如果 Client 高效的发送了一个信息就和 Server 完成了通信, 断开了连接, (很难想象这种高效哈哈), 没想到这时候, 之前 Client 重试连接请求包还没有死, 又来到了 Server, Server 理所应当地建立了 连接, 但是 Client 其实早就不在了.
  • 那为什么不是 四次握手 呢, 这是因为 三次握手 最后 Client 发送的 响应之响应Server 收到后, Server 再发送一个 响应之响应之响应包 也会有丢失的可能, 假设为了确认本次接受包没丢, 再发送一个包的策略是多余的.
    • 而且一般建立连接后, Client 会立即发送消息, 因此可以保证 Server 端知道这个连接有效, 或者 Server 挂了, Client 会受到报错信息, 也就明白连接无效的问题. 文章中说, 即便不立即发送消息, 也可以开启 keep-alive 来保持心跳, 保证有效连接. 注意 这里的 keep-aliveHttp 的并不一样.
  • 连接初始化 最重要的就是 序列号 的问题, 在上面 数据包的小结里讲过了.
  • 每次握手的丢包,基本都会重传, 丢包在理解为什么是三次握手或者四次挥手的过程中很关键.
  • 关于第三次握手的重试问题: 在 Linux 下,默认重试次数为 5次,重试的间隔时间从1s开始每次都翻倍,5次的重试时间间隔为1s, 2s, 4s, 8s, 16s,总共31s,第5次发出后还要等32s才知道第5次也超时了,所以,总共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 63sTCP 才会断开这个连接。使用三个 TCP 参数来调整行为:tcp_synack_retries 减少重试次数;tcp_max_syn_backlog,增大 SYN 连接数;tcp_abort_on_overflow 决定超出能力时的行为。

关于 心跳 这块,知识点太多了 以下都是摘自 霜神的 TCP 进阶 还是看大佬的总结比较好.这里仅做下笔记📒

  • TCP 心跳的必要性, 虽然 TCP 开启 Keep-Alive 可以确定连接有效, 但是无法确认应用层的业务是否能得到有效的处理.

  • TCP 的 KeepAlive 用于检测连接的死活而不能检测通讯双方的存活状态。 所以通常我们处理长连接的时候, 会增加应用层自定义心跳

  • TCP Keep Alive 失效 的几种情况:

    • socks 代理
    • 路由器挂掉
    • 网线拔除
  • 心跳时间影响因素

    • NAT 超时 因为这里 一般心跳包设置时间都在 3 分钟左右
    • DHCP 租期
    • 网络状态变化
  • 心跳间隔

    • 延迟心跳测试法
    • 成功一次认定,失败连续累计认定
    • 临界值避免
    • 动态调整

心跳间隔的优化就能够省一大笔流量钱💰


挥手问题

![four-way handshake.jpg](https://raw.githubusercontent.com/manajay/todayios-images/todayios/2019/07/01/four-way handshake-ffe2e628-7f75-4e0f-a1c5-bac9e3bbdf3b-1562036681347-17337723.jpg)

  • 断开连接的过程被称为 四次挥手 . 注意 Server 或者 Client 都可以主动断开连接
  • 先说如果只是一次挥手, 比如, Client 发送 FIN 包, 告诉 Server, 然后 Client 直接断开连接, 恰巧 这个包丢了, Server 没收到, 还是在那儿傻傻等着. 所以 一次不行.
  • 二次挥手的话, 其实也存在, Server 的响应 丢包的问题, Client 没有收到 Server 的响应, 一直不能断开连接,释放自己的资源, 所以至少要 三次挥手, 保证 Client 自己知道, Server 收到自己的关闭请求了.
  • 而三次挥手不可以的原因,我的理解是 , 三次分手的理由不充分, 保障工作不完善. 因为, 主动发起关闭的一方 (上面一直拿 Client 做栗子), 虽然说想要关闭了, 但是被动方 Server 不一定想要关闭, 可能有最后的数据要传递给 主动方 Client 呢. 所以就像上图一样 , 被动方 Server 在应答 ACK 后, 会处理完最后的数据, 再次发起一个 FIN,ACK 告知 Client 自己的工作也都做完了, Client 收到后, 也发送最后一个 ACK, 知道双方都没有工作要做了, 也就是 和平分手 . 哈哈总的意思就是 分手不是你想分手就分手的
  • 为什么不会有 五次挥手 呢 , 因为这里如上图 Client 在四次握手后,有个 TIME-WAIT 状态, 超时处理, Client 会等待 2MSL, 如果 2MSL 仍然没有收到 Server 的第三次挥手的 FIN,ACK 重试包, 说明 Server 正常收到了 Client 的 第四次挥手包.
    • 这里也就是会有两种丢包, 第三次或者第四次 挥手丢包, 都是使得重新发送 第三次挥手包.

补充一个回答 摘自jawil

why-four-way-handshake.png

传输

  • 如何高效传输数据, 而不是串行发包?
  • 什么是累计确认或者累计应答 cumulative acknowledgment, 你还能想到类似的东西吗?

知识点记录

参考

以下主要资料来自 刘超老师的 TCP 文章和 霜神的 TCP 系列

支付宝扫码打赏 微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章

段连洁's Picture
段连洁

iOSer

Subscribe to JAY 站 | Share Thoughts

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!

Comments