[极客时间] TCP协议 读后感
[极客时间] TCP协议 读后感
前言
网络协议中最重要的一块设计, 传输层协议, 其中对应长连接的就是 TCP
传输控制协议 - 维基百科 了.
刘超老师在讲解这篇的时候,给了一个贴心的口诀:
顺序问题 ,稳重不乱;
丢包问题,承诺靠谱;
连接维护,有始有终;
流量控制,把握分寸;
拥塞控制,知进知退。
短短几行, 就讲 TCP
的精髓囊括其中!
这里只是对刘超老师文章的个人总结, 不会长篇大论去讲解相关的概念.
一 数据包
问题集锦
- 数据包含有
IP
吗? - 数据包中的端口是用来做什么的?
- 数据包的序列号作用, 关于序列号,在连接时需要注意什么?
TCP
都有哪些 标志符TCP Flag
?TCP
滑动窗口 作用? 拥塞和流量控制问题, 哪个是对内要求,哪个是对外要求? (经常会混的点)
知识点记录
- 如上图中所示,
TCP
层是没有IP
的概念的,IP
是在TCP
的 下一层,TCP
包在经过IP
层的时候会再增加IP
的包头 . TCP
是通过Port
端口 进行应用的识别的, (应用进程会进行端口的绑定, 端口的转发)TCP
序列号是为了解决信息的乱序问题, 这样构建信息的时候,才可以得到完整正确的信息.- 这里要注意的事, 连接时有初始化序列号的步骤, 且初始序列号两端并不是从1开始,也不一定相同. 这样做的原因一个是为了防止
tcp
包的冲突 (Client
端如果认定传输包丢失的话, 会重新发送序列包, 假设Client
端挂了, 又一次重连, 而这个时候,之前重发的序列包,到达了Server
, 会造成Serve
误认为该包是有效的最新包. 然后因为真正的序列号不匹配,导致连接出问题). 同时也可以解决 TCP序号预测攻击 Linux
采用基于 时钟 的方案,随机偏移(并有使用hash
计算)生成初始序号
, 同一个Client
端口 与Server
端口 建立的连接, 其序列号重复的话,至少需要经过 4 个小时可能.- 如果含有同步化旗标(
SYN
),则此为最初的序列号;第一个数据比特的序列码为本序列号加一。 - 如果没有同步化旗标(
SYN
),则此为第一个数据比特的序列码。
- 这里要注意的事, 连接时有初始化序列号的步骤, 且初始序列号两端并不是从1开始,也不一定相同. 这样做的原因一个是为了防止
- 确认号(
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
头部。
- 20 字节的
二 连接与断开连接
问题集锦
TCP
如何建立连接的,描述以下过程 ?- 为什么握手必须是 三次 ?
- 为什么挥手必须是 四次 ?
- 连接时要初始化哪些数据?
TCP
如果在每个握手的时候丢包,对应的客户端或服务器会如何处理?TCP
长连接设计中, 心跳指的是什么, 如何优化这一块?
知识点记录
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
其实早就不在了.
- (1) 最简单的情况是,
- 那为什么不是 四次握手 呢, 这是因为 三次握手 最后
Client
发送的 响应之响应 被Server
收到后,Server
再发送一个响应之响应之响应包
也会有丢失的可能, 假设为了确认本次接受包没丢, 再发送一个包的策略是多余的.- 而且一般建立连接后,
Client
会立即发送消息, 因此可以保证Server
端知道这个连接有效, 或者Server
挂了,Client
会受到报错信息, 也就明白连接无效的问题. 文章中说, 即便不立即发送消息, 也可以开启keep-alive
来保持心跳, 保证有效连接. 注意 这里的keep-alive
和Http
的并不一样.
- 而且一般建立连接后,
- 连接初始化 最重要的就是 序列号 的问题, 在上面 数据包的小结里讲过了.
- 每次握手的丢包,基本都会重传, 丢包在理解为什么是三次握手或者四次挥手的过程中很关键.
- 关于第三次握手的重试问题: 在
Linux
下,默认重试次数为 5次,重试的间隔时间从1s开始每次都翻倍,5次的重试时间间隔为1s, 2s, 4s, 8s, 16s,总共31s,第5次发出后还要等32s才知道第5次也超时了,所以,总共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 63s,TCP
才会断开这个连接。使用三个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
传输
- 如何高效传输数据, 而不是串行发包?
- 什么是累计确认或者累计应答
cumulative acknowledgment
, 你还能想到类似的东西吗?
知识点记录
- 群聊比单聊,为什么复杂这么多? 文中讲解了 应用层
ACK
的应用, 同TCP
的ACK
累计应答结合起来理解,非常棒
参考
以下主要资料来自 刘超老师的 TCP
文章和 霜神的 TCP
系列