TCP基本知识
TCP关键校验位
序列号:建立连接时计算机生成一个随机数作为初始值,每发送一次数据增加一次数据字节数的大小,可以解决网络报乱序问题。
确认应答号:下一次期望收到的数据的序列号,发送端接收到这个应答后就认为这个序列号之前的数据全部被正常接受,可以解决丢包问题。
控制位:
- ACK:置为1时表示确认应答有效,TCP规定除了最初建立时的SYN包之外,其他包该位必为1
- RST:置为1时表示TCP连接异常需要断开连接
- SYN:置为1时表示希望建立连接,并初始化序列号字段
- FIN:置为1时表示数据发送完毕,希望断开连接。客户端和服务端互换FIN为1的报文后结束通信
TCP能保证接收端收到的包是无损、无间隔、非冗余且有序的
TCP是面向连接、可靠、基于字节流的传输层通信协议
TCP消息的有序体现在接收方内核TCP首收发缓冲区会根据序列号对收到的报文进行排序,如果某段报文未到达,即使收到了后续的报文段,这些保温也会暂存在内核缓冲区,等待确实的报文补全后才会发送给上层应用。
通过源地址、源端口、目标地址、目标端口可以唯一确定一个TCP连接
最大TCP连接数=客户端IP数*客户端的端口数
客户端最大并发TCP连接数因为文件描述符限制和内存限制而远不能达到理论上限
TCP和UDP的主要区别:
- 连接
- TCP是面向连接的传输层协议
- UDP不需要连接
- 服务对象
- TCP只支持一对一通信
- UDP支持一对一、一对多、多对多的交互通信
- 可靠性
- TCP是可靠的
- UDP不保证可靠交付数据
- 拥塞控制、流量控制
- TCP有拥塞控制和流量控制保证数据传输的安全性
- UDP无该机制,因此发送速率不受影响
- 首部开销
- TCP首部较长,未使用选项字段时占20个字节,使用后占用更多字节
- UDP首部只有8个字节且固定不变
- 传输方式
- TCP通过stream传输,数据之间没有边界,但保证顺序和可靠传输
- UDP是一个一个包发送的,是有边界的,但可能丢包和乱序
- 分片不同
- TCP的数据大小如果大于MSS,则会在传输层分片,目标主机收到后会在传输层拼装TCP数据包,如果丢失分片就需要重传丢失的分片
- UDP的数据大小如果大于MTU,会在网络层分片,目标主机收到后在网络层拼装完数据再发给传输层
TCP和UDP可以共用一个端口,原因在于TCP和UDP在内核中是两个完全独立的软件模块,主机收到数据包后通过IP包头的协议号判断交给哪个模块处理
TCP建立连接
三次握手过程
1、 初始状态下,客户端和服务端都是closed状态,然后,服务端主动监听某个端口
2、 客户端发送SYN包携带一个序列号seq num
3、 服务端收到SYN包后会发送SYN-ACK包,携带ack num和seq num,其中ack num为客户端发送的sync包内的seq num加一,同时服务端变为sync_rcvd状态
4、 客户端收到两个包后向服务端发送ACK包,包含ack num,其为服务器发送的seq num加一,同时客户端变为established状态
5、服务端收到后变为established状态
第三次握手可以携带数据
为什么需要三次握手?
防止历史连接初始化连接
三次握手主要是为了让服务器有一个中间状态处理异常情况,如果只有两次握手,那么服务器在收到客户端的sync包后就会进入established状态,此时如果客户端因为一些原因断开连接,那么服务端的这个连接就被浪费了
假设客户端发送的ack包未被服务端正常接收还能正常连接吗?
可以,只要服务端还在syn_received状态,在收到服务端发送的数据包后还能建立连接,因为数据包中包含ack标识位,正如上面提到的,第三次连接可以携带数据
每次建立TCP连接时初始化的序列号的都是不一样的,原因在于:尽可能避免历史报文被下一个相同四元组的连接接收, 防止伪造的相同序列号的TCP报文被对方接收
在传输层,TCP会根据MSS(最大报文段长度)将数据分段,确保每个TCP段生成的IP包不会超过MTU,尽量避免IP层分片。这样可以降低网络层分片带来的开销和丢包风险。一旦某个TCP段丢失,TCP根据序列号和确认机制只需要重传该丢失的段,而无需重传整个数据流,提高了传输效率和可靠性。
握手失败的后果
第一次握手丢失后客户端一直未收到服务端的syn-ack报文就会触发超时重传机制,且重传的的syn报文的序列号和丢失报文的序列号一致
第二次握手丢失后客户端一直无法收到服务端的sync-ack报文,就会向服务端重传sync包,而服务端一直收不到客户端发送的第三次握手信息就会重传sync-ack报文,因此如果第二次握手丢失,客户端和服务端都会触发重传
第三次握手丢失后,因为ack报文无法重传,如果等待一段时间后还未收到客户端的第三次握手,那么服务端就会断开连接
四次挥手过程
1、客户端准备关闭连接,发送一个TCP头部FIN置为1的报文,然后进入FIN_WAIT_1状态。
2、服务端收到后向客户端发送ACK报文,然后进入CLOSE_WAIT状态
3、客户端收到服务器的ACK应答后进入FIN_WAIT_2状态
4、服务端处理完剩余数据后向客户端发送FIN报文,然后进入LAST_ACK状态
5、客户端收到服务端的FIN报文后回复ACK,然后进入TIME_WAIT状态
6、服务端收到ACK报文后,进入CLOSE状态,至此服务端关闭连接
7、客户端经过2MSL时间后,自动进入CLOSE状态,至此客户端关闭连接
客户端和服务端个发送一个FIN和一个ACK之后才会断开连接,因此成为四次挥手
主动关闭连接的一方才有TIME_WAIT状态
挥手失败的后果
第一次挥手丢失后客户端会重传FIN报文,重传次数由tcp_orphan_retries控制
第二次挥手丢失后由于ACK包不会重传,所以客户端就会触发超时重传,重传FIN报文,直到收到服务端的ACK报文或者达到最大重传次数
第三次挥手丢失后,服务端重传第三次挥手报文,如果达到tcp_orphan_retries后还未收到客户端的ACK就会断开连接。客户端如果在FIN_WAIT_2状态内一直未收到服务端的FIN也会断开连接,防止资源泄露
第四次挥手丢失后服务端主动断开连接。客户端在收到第三次挥手后进入TIME_WAIT状态,开启时长为2MSL的定时器,如果期间再次收到第三次挥手就会重置定时器,等待2MSL后客户端断开连接
TIME_WAIT状态的用处
- 防止历史连接中的数据被后面相同四元组的连接错误接收
- 保证被动关闭的一方能被正常关闭
TIME_WAIT过多的危害
- 占用系统资源
- 占用端口资源
如果客户端的TIME_WAIT状态过多,占满所有端口资源,就无法对目标IP+目标PORT都一样的服务端发起连接
如果服务端的TIME_WAIT状态过多会占用系统资源,但不会导致端口资源限制
服务器出现大量TIME_WAIT状态的原因
- 客户端和服务端是否开启了HTTP Keep-Alive
- HTTP长连接超时,服务端主动关闭连接
- HTTP长连接的请求数量达到上限
服务器出现大量CLOSE_WAIT状态的原因
- 通常是代码的问题,需要排查为什么服务端没有调用close
建立连接后客户端出现故障
- TCP使用保活机制避免这种情况,探测几次后如果还是没响应,TCP会报告改TCP连接已死亡
- 如果对端程序正常工作,能响应心跳包,那么TCP保活时间会被重置
- 对端主机宕机后重启,当TCP保活的探测报文发送给对端后,对端可以响应,但由于没有该连接的有效信息,会产生一个RST报文
- 对端主机宕机,连续探测几次没有相应后,TCP报告该TCP连接已死亡
建立连接后服务端进程崩溃
- 服务器的进程崩溃后,内核会主动发起挥手请求结束连接并释放资源
About this Post
This post is written by ByronGu, licensed under CC BY-NC 4.0.