TCP握手,挥手
我们都知道 HTTP 是基于 TCP 协议进行传输的,那么:
- 最开始的 3 个包就是 TCP 三次握手建立连接的包
- 中间是 HTTP 请求和响应的包
- 而最后的 3 个包则是 TCP 断开连接的挥手包
为什么抓到的TCP挥手是三次,而不是书上说的四次?
-
因为服务器端收到客户端的
FIN
后,服务器端同时也要关闭连接,这样就可以把ACK
和FIN
合并到一起发送,节省了一个包,变成了“三次挥手”。 -
而通常情况下,服务器端收到客户端的
FIN
后,很可能还没发送完数据,所以就会先回复客户端一个ACK
包,稍等一会儿,完成所有数据包的发送后,才会发送FIN
包,这也就是四次挥手了。
握手异常的情况分析
-
TCP 第一次握手的 SYN 丢包了,会发生了什么?(连不上服务端)
从上图可以发现, 客户端发起了 SYN 包后,一直没有收到服务端的 ACK ,所以一直超时重传了 5 次,并且每次 RTO 超时时间是不同的:
- 第一次是在 1 秒超时重传
- 第二次是在 3 秒超时重传
- 第三次是在 7 秒超时重传
- 第四次是在 15 秒超时重传
- 第五次是在 31 秒超时重传
可以发现,每次超时时间 RTO 是指数(翻倍)上涨的,当超过最大重传次数后,客户端不再发送 SYN 包
linux中通过/proc/sys/net/ipv4/tcp_syn_retries进行设置重传次数
-
TCP 第二次握手的 SYN、ACK 丢包了,会发生什么?(连不上客户端)
客户端会重传SYN包,同时服务端没有收到客户端的ACK超时后也会重传SYN,ACK,接着一会客户端重传的SYN包又抵达服务端,服务端收到后,超时定时器重新计时,然后回了SYN,ACK包,相当于服务端的超时定时器只触发一次就被重置了,最后客户端SYN超过重试次数5就不再发了。
-
TCP 第三次握手的 ACK 包丢了,会发生什么?
如果第三次握手的 ACK,服务端无法收到,则服务端就会短暂处于
SYN_RECV
状态,而客户端会处于ESTABLISHED
状态。由于服务端一直收不到 TCP 第三次握手的 ACK,则会一直重传 SYN、ACK 包,直到重传次数超过
tcp_synack_retries
值(默认值 5 次)后,服务端就会断开 TCP 连接。而客户端则会有两种情况:
- 如果客户端没发送数据包,一直处于
ESTABLISHED
状态,然后经过 2 小时 11 分 15 秒才可以发现一个「死亡」连接,于是客户端连接就会断开连接。 - 如果客户端发送了数据包,一直没有收到服务端对该数据包的确认报文,则会一直重传该数据包,直到重传次数超过
tcp_retries2
值(默认值 15 次)后,客户端就会断开 TCP 连接。
- 如果客户端没发送数据包,一直处于
TCP头部数据分析
- 序列号:在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。
- 确认应答号:指下一次「期望」收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决不丢包的问题。
为什么需要TCP
IP
层是「不可靠」的,它不保证网络包的交付、不保证网络包的按序交付、也不保证网络包中的数据的完整性。如果需要保障网络数据包的可靠性,那么就需要由上层(传输层)的 TCP
协议来负责。
TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。
- 面向连接:一定是「一对一」才能连接,不能像 UDP 协议 可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的;
- 可靠的:无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端;
- 字节流:消息是「没有边界」的,所以无论我们消息有多大都可以进行传输。并且消息是「有序的」,当「前一个」消息没有收到的时候,即使它先收到了后面的字节已经收到,那么也不能扔给应用层去处理,同时对「重复」的报文会自动丢弃。
TCP && UDP异同
1. 连接
- TCP 是面向连接的传输层协议,传输数据前先要建立连接。
- UDP 是不需要连接,即刻传输数据。
2. 服务对象
- TCP 是一对一的两点服务,即一条连接只有两个端点。
- UDP 支持一对一、一对多、多对多的交互通信
3. 可靠性
- TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按需到达。
- UDP 是尽最大努力交付,不保证可靠交付数据。
4. 拥塞控制、流量控制
- TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。
- UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。
5. 首部开销
- TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是
20
个字节,如果使用了「选项」字段则会变长的。 - UDP 首部只有 8 个字节,并且是固定不变的,开销较小。
TCP的粘包和拆包
-
正常的情况下包的发送和接受,客户端发送p1,p2包,服务端先后接受到p1,p2包,没有发生粘包和拆包。
-
发生了拆包的现象。客户端发送p1,p2包,客户端对p1拆包分成p1_1和p1_2,服务端先后收到p1_1,p1_2和p2包。 拆包发生原因分2种情况
- 发送的数据大于套接字缓冲区剩余大小。
- 发送的数据大于MTU(最大传输单元)大小。
-
发生了粘包的现象。客户端发送p1,p2包,p1,p2包到达接收端的缓存,服务端应用读取缓存时无法区分p1,p2各自的大小。因为在TCP通讯协议中TCP是面向流的,包和包之间没有界限。粘包可发生在发送端也可发生在接收端:
-
粘包场景
发送端原因导致的粘包,客户端在发送p1包时,先将p1包放入发送缓存,由于Nagle算法判断其发送的可用数据(去头数据)过小等待一小段时间,这时又发送了p2包,系统将p1和p2合成一个大包发送给服务端。服务端读到大包,无法区分p1和p2包。
接收端原因导致的粘包,服务端缓存接收到客户端发送的p1包,服务端应用未能及时读取缓存,此时服务端缓存又接收到客户端发送的p2包,服务端应用读取缓存,无法区分p1和p2包。
-
粘包解决办法
无论拆包还是粘包本质问题都是无法区分包界限,解决包界限的问题主要有以下几种方式:
- 消息数据的定长,比如定长100字节,不足补空格,接收方收到后解析100字节数据即为完整数据。但这样的做的缺点是浪费了部分存储空间和带宽。
- 消息数据使用特定分割符区分界限,比如使用换号符号做分割。
- 把消息数据分成消息头和消息体,消息头带消息的长度,接收方收到后根据消息头中的长度解析数据。
- 在实际开发中很多网络框架对TCP拆包粘包问题的解决做了很多支持,比如netty中LineBasedFrameDecoder解析器就是利用换号符号做分割。
-
TCP怎么保证可靠传输
- 应用数据被分割成TCP认为最适合发送的数据块
- TCP给发送的每一个包进行编号,接收方对死数据包进行排序,把有序数据传给应用层
- 校验和:TCP将保持它首部和数据的校验和。如果再传输过程发生变化,收到的校验和有差错,TCP将丢弃这个报文段和不确定收到此报文段
- TCP接收端会丢弃重复的数据
- 流量控制:TCP连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据,当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP使用的流量控制协议是可变大小的滑动从窗口协议
- 停止等待协议:没发完一个分组就停止发送,等待对方确认,在收到确认后再发一下个分组。
- 超时重传:当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段,如果不能及时收到确认,将重发这个报文段