1. TCP
TCP 是面向连接、可靠的、基于字节流的传输层通信协议。
- 面向连接:一对一的连接。不像 UDP 可以同时向多个主机发送消息。
- 可靠的:网络链路中出现变化,TCP 可以保证一个报文一定能够到达指定端。
1.1 TCP 头
序列号:建立连接时,计算机生成的随机数作为初始值,通过 SYN 包传给接收端主机,每发送一次数据,就累加 1。解决网络包乱序问题
确认应答号:下一次“期望”收到的数据的序列号,发生端收到这个确认应答后,认为这个序列号以前的数据都被正确接收。解决丢包问题
控制位:
- ACK:确认应答字段有效。除了最初建立连接时的 SYN 包之外该位必须为1
- RST:TCP 连接中出现异常,必须强行断开连接
- SYN:希望建立连接,在其“序列号”字段初始化后设定
- FIN:通信结束,断开连接,不会再发送数据时设定
1.2 TCP 连接
用于保证可靠性和流量控制维护的某些状态信息,包括Socket、序列号和窗口大小称为连接
- Socket:IP + Port
- 序列号:解决乱序等问题
- 窗口大小:流量控制
1.3 唯一确定一个连接
通过 TCP 四元组来确定:
- 源地址
- 源端口
- 目标地址
- 目标端口
源地址和目标地址 (32-bit):在 IP 头部中,通过 IP 协议发送报文给对方主机
源端口和目标端口 (16-bit):在 TCP 头部中,通过 TCP 协议把报文发给哪一个进程。
1.4 三次握手 & 四次挥手
TCP 状态:
- LISTENING: 服务端侦听远端TCP连接请求,等待被连接
- SYN_SENT: 客户端调用connect方法,发送一个SYN请求建立连接
- SYN_RCVD: 服务端收到连接请求并确认后,调用accept方法
- ESTABLISHED: 连接建立
- FIN_WAIT_1: 主动关闭连接,调用close方法后
- CLOSING: FIN_WAIT_1后,等待对端关闭确认 (较少出现)
- CLOSE_WAIT: 收到关闭请求,等待关闭
- FIN_WAIT_2: 收到关闭ACK确认后
- LAST_ACK: 收到关闭请求(CLOSE_WAIT)后,被动关闭连接,调用close方法
- TIME_WAIT: 主动关闭连接,收到被动关闭连接(LAST_ACK)后。等待足够的时间,确保远程TCP连接中断确认,最大程度保证双方正常结束,需等待2*MSL时间才能进行下一次连接
- CLOSED: 被动关闭端收到ACK后,进入CLOSED,连接结束
总结:
TCP 建立连接时,通过三次握手能防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始化序列号。序列号能够保证数据包不重复、不丢弃和按序传输。
不使用「两次握手」和「四次握手」的原因:
- 两次握手:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号;
- 四次握手:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。
1.4.1 TIME_WAIT
主动关闭Socket端会进入TIME_WAIT状态,并持续2MSL时间长度。
MSL (maximum segment lifetime):表示一个IP数据包在互联网上生存的最长时间,超过这个时间将在网络中消失。MSL建议值为2分钟,但传统上为30s
因此,TIME_WAIT状态一般维持在1-4分钟
TIME_WAIT 作用:
可靠地实现TCP全双工连接终止
允许老的重复连接在网络中消逝
TIME_WAIT 危害:
过多会占用内存,一个TIME_WAIT占用4k
网络差的情况下,如果主动方无TIME_WAIT等待,关闭当前连接后,主动方与被动方又重新建立新的TCP连接,此时被动方重传或延时过来的FIN包会直接影响当前新的TCP连接
如何避免:
设置socket选项为SO_REUSEADDR,端口可重用
由于TIME_WAIT状态是主动关闭一方出现的,所以在协议逻辑设计时,尽量由客户端主动关闭,避免服务端出现TIME_WAIT
1.4.2 SYN 攻击
什么是SYN攻击?
- 在三次握手过程中,收到客户端SYN,服务端ACK该请求后进入SYN_RCVD状态,该状态称为半连接(half-open connect),只有等服务端收到ACK再次确认后,才进入ESTABLISHED状态
- SYN 攻击,即客户端在短时间内大量伪造不存在的IP地址,向服务端不断地发送SYN包,服务端回复ACK确认包,并等待客户端确认。但由于源地址不存在,服务端需要不断重发ACK包直至超时,大量SYN包长时间占用未连接队列,导致正常SYN请求被丢弃,网络阻塞服务不可用。
- DoS/DDoS 是一种典型的SYN攻击
如何检测 SYN 攻击?
- 服务器上存在大量半连接状态 (SYN_RCVD)
- 大量随机的源 IP 地址
如何预防 SYN 攻击?
完全阻止SYN攻击是不可能的,可通过一些方法减轻SYN攻击:
- 缩短超时时间(SYN Timeout)
- 增加最大半数连接数
- 过滤网关防护
- SYN cookies 技术
Linux 内核参数:
1 | # 队列最大值 |
1.5 KeepAlive
TCP数据交互完成后,未主动释放连接,在无法知道对端的情况下保持了这个连接,长时间累积导致非常多的半打开连接,造成系统资源浪费。
KeepAlive: 隔一段时间给对端发送一个探测包,如果对方回应ACK,则认为连接还是存活的。在超过一定重试次数之后还是未收到对方的回应,则丢弃该连接。
1.6 如何实现长连接
HeartBeat心跳包
客户端每隔一小段时间向服务器发送一个数据包,通知服务器自己仍然在线。30s 00 00 03
TCP协议的KeepAlive机制
默认不打开,要用setsockopt将SOL_SOCKET.SO_KEEPALIVE设置为1,并且设置参数
tcp_keepalive_time/tcp_keepalive_probes/tcp_keepalive_intvl
keep-alive机制,可以减少tcp连接建立的次数,也意味着减少TIME_WAIT连接状态,以此来提高服务器性能
但keep-alive也可能导致系统资源被无效占用,合适设置keep-alive timeout时间非常重要
1.7 滑动窗口
滑动窗口(Sliding window)是一种流量控制技术,它被用来改善网络吞吐量,即容许发送方在接收任何应答之前传送附加的包,接收方告诉发送方在某一个时刻能送多少包(成为窗口尺寸)
让发送的每一个包都有一个id,接收端必须对每一个包进行确认,这样设备A一次多发送几个片段,而不必等候ACK,同时接收端也要告知它能够收多少,这样发送端发起来也有个限制,当然还需要保证顺序性,不要乱序,对于乱序的状况,我们可以允许等待一定情况下的乱序,比如说先缓存提前到的数据,然后去等待需要的数据,如果一定时间没来就DROP掉,来保证顺序性!
接收端可以根据自己的状况通告窗口大小,从而控制发送端的发送,进行流量控制。
滑动窗口原理:
TCP并不是每一个报文段都会回复ACK确认,可能会对多个报文段发送1个ACK (累积ACK确认)。
比如发送方有1/2/3个报文段,接收方收到2/3报文段后,一直未收到报文段“1”,将会丢弃报文段2/3.
实现滑动窗口:
1 | var ( |
使用HTTP压测工具hey:
1 | hey -c 6 -n 300 -q 6 -t 80 http://localhost:3000 |
1.8 MTU & MSS
MTU
: 一个网络包的最大长度,以太网一般未 1500 字节
MSS
:除去 IP 和 TCP 头部后,一个网络包能容纳的 TCP 数据的最大长度
当 IP 层有一个超过 MTU 大小的数据(TCP 头部 + TCP 数据)要发送,那么 IP 层就要进行分片,把数据分片成若干片,保证每一个分片都小于 MTU。把一份 IP 数据报进行分片以后,由目标主机的 IP 层来进行重新组装后,再交给上一层 TCP 传输层。
但是,如果一个 IP 分片丢失,整个 IP 报文的所有分片都得重传。
因为 IP 层本身没有超时重传机制,它由传输层的 TCP 来负责超时和重传。
当接收方发现 TCP 报文(头部 + 数据)的某一片丢失后,则不会响应 ACK 给对方,那么发送方的 TCP 在超时后,就会重发「整个 TCP 报文(头部 + 数据)」。
因此,可以得知由 IP 层进行分片传输,是非常没有效率的。
所以,为了达到最佳的传输效能 TCP 协议在建立连接的时候通常要协商双方的 MSS 值,当 TCP 层发现数据超过 MSS 时,则就先会进行分片,当然由它形成的 IP 包的长度也就不会大于 MTU ,自然也就不用 IP 分片了。
经过 TCP 层分片后,如果一个 TCP 分片丢失后,进行重发时也是以 MSS 为单位,而不用重传所有的分片,大大增加了重传的效率。
2. UDP
- 目标和源端口:告诉 UDP 协议应该把报文发给哪个进程。
- 包长度: UDP 首部的长度跟数据的长度之和。
- 校验和:提供可靠的 UDP 首部和数据而设计。
UDP是一个简单的传输层协议,与TCP相比,有如下特征:
- UDP 缺乏可靠性。不提供确认、序列号、超时重传等机制。
- UDP 数据报可能在网络中被复制,被重新排序。即UDP不保证数据一定到达目的地,也不保证数据报的先后顺序,也不保证每个数据报只到达一次
- UDP 数据报有长度的。如果一个数据报正确地到达目的地,该数据报的长度也随着随着数据一起传给了接收方。
- UDP 面向无连接的。UDP客户端与服务器不存在长期关系,不需要经过三次握手和四次挥手操作
- UDP支持多播和广播
3. TCP vs UDP
连接 | 协议 | 可靠性 | 使用场景 | |
---|---|---|---|---|
TCP | 面向连接 | 流协议,无大小限制 | 可靠 | 可靠的通信。使用校验和、确认和重传机制来确保可靠传输 |
UDP | 无连接 | 数据包协议,有限制 | 不可靠 | 1. 包总量较小的通信(DNS, SNMP) 2.视频、音频等流媒体(即时通信)3.广播通信 |
tcp 传输的是数据流,udp是数据包;tcp要进行三次握手、udp不需要
TCP 和 UDP 区别:
1. 连接
- TCP 面向连接,传输数据前先要建立连接。
- UDP 不需要连接,即刻传输数据。
2. 服务对象
- TCP 是一对一的两点服务。
- UDP 支持一对一、一对多、多对多的交互通信
3. 可靠性
- TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按需到达。
- UDP 是尽最大努力交付,不保证可靠交付数据。
4. 拥塞控制、流量控制
- TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。
- UDP 没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。
5. 首部开销
- TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是
20
个字节,如果使用了「选项」字段则会变长的。 - UDP 首部只有 8 个字节,并且是固定不变的,开销较小。
6. 传输方式
- TCP 流式传输,没有边界,但保证顺序和可靠。
- UDP 一个包一个包的发送,是有边界的,但可能会丢包和乱序。
7. 分片不同
- TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片。
- UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层,但是如果中途丢了一个分片,则就需要重传所有的数据包,这样传输效率非常差,所以通常 UDP 的报文应该小于 MTU。
TCP 和 UDP 应用场景:
由于 TCP 是面向连接,能保证数据的可靠性交付,常用于:
FTP
文件传输HTTP
/HTTPS
由于 UDP 面向无连接,它可以随时发送数据,再加上UDP本身的处理既简单又高效,常用于:
- 包总量较少的通信,如
DNS
、SNMP
等 - 视频、音频等多媒体通信
- 广播通信
为什么 UDP 头部没有「首部长度」字段,而 TCP 头部有「首部长度」字段呢?
原因是 TCP 有可变长的「选项」字段,而 UDP 头部长度则是不会变化的,无需多一个字段去记录 UDP 的首部长度。
为什么 UDP 头部有「包长度」字段,而 TCP 头部则没有「包长度」字段呢?
TCP 负载数据长度:
1 | TCP数据总长度 = IP总长度 - IP首部长度 - TCP首部长度 |