Notes On Network Protocol UDP and TCP
UDP 和 TCP from 极客时间 - 趣谈网络协议
UDP
UDP 包头格式
IP
头里面有个8位协议, 里面会存放, 数据里面到底是TCP
还是UDP
。处理完传输层的事情, 内核的事情基本就干完了, 里面的数据应该交给应用程序自己去处理。根据端口号, 将数据交给响应的应用程序。
UDP 的三大特点
- 沟通简单, 没有复杂的数据结构, 处理逻辑, 包头字段. 相信网络通路默认就是很容易送达的,不容易被丢弃的。
- 轻信他人, 它不会建立连接,虽然有端口号,但是监听在这个地方,谁都可以传给他数据,他也可以传给任何人数据,甚至可以同时传给多个人数据。
- 愣头青, 做事不懂权变。它不会根据网络的情况进行发包的拥塞控制,无论网络丢包丢成啥样了,它该怎么发还怎么发。
UDP的三大使用场景
- 需要资源少,在网络情况比较好的内网,或者对于丢包不敏感的应用。
DHCP
就是基于UDP
协议的(广播形式)。一般的获取 IP 地址都是内网请求,而且一次获取不到 IP 又没事,过一会儿还有机会。PXE
操作系统镜像的下载使用的TFTP
,这个也是基于 UDP 协议。
- 不需要一对一沟通,建立连接,而是可以广播的应用。
UDP
不面向连接, 使得可以继承广播或多播协议。D
类地址(组播地址), 使用这个地址, 可以将包组播给一批机器。当一台机器上的某个进程想监听某个组播地址的时候,需要发送 IGMP 包,所在网络的路由器就能收到这个包,知道有个机器上有个进程在监听这个组播地址。当路由器收到这个组播地址的时候,会将包转发给这台机器,这样就实现了跨路由器的组播。
- 需要处理速度快,时延低,可以容忍少数丢包,但是要求即便网络拥塞,也毫不退缩,一往无前的时候。
如果实现的应用需要有自己的连接策略, 可靠保证, 时延要求, 使用
UDP
, 然后在应用层实现这些是再好不过了。
UDP使用的五个例子
网页或者APP的访问
HTTP 协议是基于 TCP 的,建立连接都需要多次交互,对于时延比较大的目前主流的移动互联网来讲,建立一次连接需要的时间会比较长,然而既然是移动中,TCP 可能还会断了重连,也是很耗时的。而且目前的 HTTP 协议,往往采取多个数据通道共享一个连接的情况,这样本来为了加快传输速度,但是 TCP 的严格顺序策略使得哪怕共享通道,前一个不来,后一个和前一个即便没关系,也要等着,时延也会加大。
QUIC
(全称Quick UDP Internet Connections,快速 UDP 互联网连接)是Google
提出的一种基于UDP
改进的通信协议, 目的是降低网络通信的延迟, 提供更好的用户互动体验。流媒体协议
直播协议多使用
RTMP
。RTMP
协议也是基于TCP
的。很多直播应用,都基于
UDP
实现了自己的视频传输协议。网络层不好, 应用选择性丢帧。
实时游戏
在异步
IO
机制引入之前,UDP
尝尝是应对海量客户端链接的策略。游戏对实时要求较为严格的情况下,采用自定义的可靠 UDP 协议,自定义重传策略,能够把丢包产生的延迟降到最低,尽量减少网络问题对游戏性造成的影响。
IoT 物联网
物联网领域终端资源少, 维护
TCP
协议代价太大. 物联网对实时性要求也很高.Google
推出的物联网通信协议Thread
就是基于UDP
协议的移动通信领域
在
4G
王国利, 移动流量上网的数据面对的协议GTP-U
是基于UDP
的。GTP
协议本身就包含复杂的手机上线下线的通信协议。
TCP
TCP
天然认为网络环境是恶劣的,丢包,乱序,重传,拥塞都是常用的事情,一言不合就可能送达不到了,因此要从算法层面来保证可靠性。
TCP 包头格式
序号:解决乱序问题,确认哪个先来哪个后到。
确认序号:确认发出去的包,如果没有收到就应该重新发送,知道送达。解决不丢包的问题。
TCP 状态位
SYN
发起一个链接ACK
是回复RST
重新连接FIN
结束链接
TCP
是面向连接的,因而双方要维护连接的状态,这些带状态位的包的发送,会引起双方的状态变更。就像人与人之间的信任会经过多次交互才能建立。
窗口大小(流量控制):TCP
要做流量控制, 通信双方各声明一个窗口,标识自己当前能够的处理能力,别发送的太快,撑死我,也别发的太慢,饿死我。
拥塞控制:TCP
拥塞控制。控制自己,也即控制发送速度。不能改变世界,就改变自己。
TCP 特点
- 顺序问题,稳重不乱
- 丢包问题,承诺靠谱
- 连接维护,有始有终
- 流量控制,把握分寸
- 拥塞控制,知进知退
TCP 三次握手
请求->应答->应答之应答
- A 发起一个连接请求
- 第一个请求杳无音信
- 包丢了
- 包饶弯路, 超时了
- B没有响应, 不想和我连接
- 再次发送
- 终于到达 B, A 暂时还不知道
- 第一个请求杳无音信
- B 收到了请求包, 知道了 A 的存在, 知道 A 要和它建立链接. 应答
- B 不乐意建立连接, A 会重试一阵后放弃, 连接建立失败
- B 乐意建立链接, 则会发送应答包给 A
- 不能认为连接建立好
- 应答包会丢失
- 应答包会饶弯路
- A 已经挂了
- 不能认为连接建立好
- B 发送的应答包可能会发送多次, 但是只要一次到达 A, A 就认为连接已经建立了, 对于A来说, 他的消息有去有回
- A 给 B 发送应答之应答
- B 也在等待这个消息, 才能确认连接的建立, 只有等到了这个消息, 对于 B 来讲, 才算他的消息有去有回
- 应答之应答也会丢失, 绕路, 甚至 B 挂了. 只要双方的消息都有去有回, 就基本可以了.
需要双方发送的消息都有去有回.
大部分情况下, A 和 B 建立了连接之后, A 会马上发送数据, 一旦 A 发送数据就解决了问题.
- 应答之应答丢失. 当 A 连续发送数据的时候, B 可以认为这个连接已经建立.
- B 挂了. A 发送的数据, 会报错, 说 B 不可达, A 就知道 B 出事情了.
keepalive, 即使没有真实的数据包, 也有探活包.
如果 A 长时间不发包, B 可以主动关闭。
为什么两次握手不行
- A 和 B 原来建立了连接, 做了简单通信, 结束了连接
- 最早 A 第一次发起请求的时候, 重复发了很多次包, 如果这个时候请求到达. B 会认为这也是一个正常的请求的话, 因此建立了连接(如果两次握手就建立链接), 就没有终结了.
TCP 包的序号问题
- A 要告诉 B, 发起包的序号
- B 同样要高速 A, 发起包的序号
序号不能从1开始, 这样往往会冲突.
1 | A 发送 1 2 3. |
- 每个连接都要有不同的序号.
- 这个序号的起始序号是随着时间变化的.
- 32位的计数器, 每
4ms
加一.如果到重复, 需要4个多小时, 绕路的包早都死了. 以为IP
包头里有个TTL
, 也即生存时间.
- 32位的计数器, 每
连接连接过程的状态变化
TCP 四次挥手
- A: B 不玩了
- B: 你不玩了, 我知道了
B 不能在 ACK 的时候, 直接关闭. 有可能A是发完了最后的数据就准备不玩了, 但是 B 还没做完自己的事情, 还是可能在发送数据的, 所以称为半关闭的状态.
A 可以选择不再接收数据, 也可以选择最后再接收一段数据, 等待 B 也主动关闭.
- B: A 好啊, 我也不玩了, 拜拜
- A: 好的, 拜拜
解释
- A : B 不玩了
- B: 你不玩了, 我知道了
- A 没收到回复
- 重新发送”不玩了”
- A 收到回复
- A 跑路了, B 发起的请求得不到A的应答
- B 跑路了, A 不知道 B 是还有事情要处理, 还是过一会会发送结束
断开时序图
A : B 不玩了, FIN-WAIT-1
B: 你不玩了, 我知道了 CLOSE-WAIT
A 收到回复 FIN-WAIT-2
- B 跑路了,
Linux
调整tcp_fin_timeout
这个参数, 设置超时时间
- B 跑路了,
B: A 好啊, 我也不玩了, 拜拜 LAST_ACK
- A: 好的, 拜拜
- 发送 ACK, FIN-WAIT-2结束.
- 如果这个 ACK, B 收不到
- B 重新发送一个
A 好啊, 我也不玩了
, 如果这个时候 A 已经跑路了, B 就再也收不到 ACK 了
- B 重新发送一个
TCP
协议要求 A 最后等待一段时间TIME-WAIT, 这个时间要足够长到如果 B 没收到 ACK, B说A 好啊, 我也不玩了
会重发的- A 会重新发一个 ACK 并且足够时间到达 B
- 如果 A 直接跑路, 端口就直接空出来了, 但是 B 不知道, B 原来发过的很多包可能都还在路上. 如果 A 的端口被一个新的应用占用了, 就会收到 B 发过来的包(虽然需要会重新生成). 双保险, 为了防止混乱, 需要等足够长的时间, 等待原来B发送的所有包都死翘翘, 再空出端口.
等待时间:等待时间设为2MSL
MSL 是 Maximum Segment Liftetime, 报文最大生存时间. 它是任何报文在网络上存在的最长时间, 超过这个时间报文将被丢弃.
IP
头中有一个TTL
, 是IP
数据报可以经过的最大路由数, 每经过一个处理他的路由器此值就减1, 当此值为0则数据报将被丢弃, 同时发送ICMP
报文通知主机.协议规定
MSL
为2分钟, 实际应用中常用的是30秒, 1分钟和2分钟等.
超过了 2MSL
- 按照 TCP 的原理, B 重发
FIN
- A 收到
FIN
, 表示超过时间了. 直接发送RST
TCP 状态机
数字是连接状态变化
虚线是 A 的连接
- 实线是 B 的连接