一、初识TCP/IP协议栈

osi七层模型和tcp/ip四层模型如下图
image.png

二、tcp三次握手和四次挥手

tcp三次握手四次挥手流程图如下

image.png

2.1、TCP三次握手

第一次握手:client发送syn包(syn=x)到server,并进入SYN_SEND状态,等待服务器确认;
第二次握手:server收到syn包,必须确认client的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时server进入SYN_RECV状态,进入半连接队列;
第三次握手:client收到server的SYN+ACK包,向server发送确认包ACK(ack=y+1),此包发送完毕,client和server进入ESTABLISHED状态,完成三次握手,进入全链接队列,并由accept()函数接管处理。

2.2、TCP四次挥手

第一次挥手:主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不会再给你发数据了(当然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可以接受数据。
第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)。
第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。
第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手。

三、常见问题

3.1、半连接队列和全连接队列

半连接队列由net.ipv4.tcp_max_syn_backlog来控制,默认64,可以放大已满足高性能场景。
全连接队列由net.core.somaxconn的最小值和backlog来控制。
可通过如下命令观察全连接队列大小

  1. [root@VM-0-3-centos ~]# ss -lnt
  2. State Recv-Q Send-Q Local Address:Port Peer Address:Port
  3. LISTEN 0 128 *:22 *:*
  4. LISTEN 0 128 *:443 *:*
  5. LISTEN 0 128 *:15432 *:*
  6. LISTEN 0 128 *:80 *:*
  7. LISTEN 0 50 [::]:8098 [::]:*
  8. LISTEN 0 50 [::]:8099 [::]:*
  9. LISTEN 0 128 [::]:15432 [::]:*
  10. LISTEN 0 90 [::]:8306 [::]:*
  11. Send-Q:全连接队列的最大值,其值为min(backlog,somaxconn)
  12. Recv-Q:以建立成功,但未交付给应用的“TCP连接的数量”,最大值为Send-Q+1

全连接队列积压,这里要看net.ipv4.tcp_abort_on_overflow的值,若为0,则会导致linux在队列满了之后,将会把连接还原至SYN_ACK状态,以造成3次握手最后的ACK包丢失的假象,等待客户端超时后重发ACK包;若为1,则直接发RST包给客户端终止连接,客户端收到http_code为104
当出现全连接队列溢出现象时,可以调节队列大小,也可以将tcp_abort_on_overflow置为1

3.2、三次握手为什么是三次

可用性:同步初始序列号,四次握手虽然可以实现,但是中间两次可以合并。两次握手可以实现初始序列号,但是无法实现同步的目标,防止旧的重复连接造成混乱
安全性:防止ddos攻击,两次握手无法验证客户端的真伪
效率:四次握手效率低

3.3、三次握手中发生丢包,应该如何处理

第一个SYN包丢失,A会周期性发起超时重传,直到B的确认
第二个SYN+ACK包丢失,B会周期性发起超时重传,直到A的确认
第三个ACK包,A发完包后会进入TCP的Established状态,此时

  • 假设双方均未发送数据,B会周期性超时重传,直到收到A的确认,并进入Established状态
  • 假设此时A有数据发送,B收到DATA+ACK,进入Established状态
  • 假设B发送数据,会一直周期性重传SYN+ACK,直到收到A的确认才可以发送数据

    3.4、CLOSE-WAIT积压

    通常,CLOSE_WAIT 状态在服务器停留时间很短,如果你发现大量的 CLOSE_WAIT 状态,那么就意味着被动关闭的一方没有及时发出 FIN 包,这意味着:

  • 程序问题,未关闭socket

  • 响应太慢或者超时设置过小
  • BACKLOG 太大,导致无法被消费完

出现CLOSE_WAIT过多,可以看Recv-Q字段是否为空,lsof -i:来寻找对应的进程

3.5、TIME-WAIT积压

3.6、为什么要2MSL

MSL为报文最大生存时间,确认ACK包丢失到重发FIN+ACK包需要2MSL,若2MSL内未接到FIN+ACK包,则认为包已送达

3.7、MSL大小为多少?是否可以配置?

2MSL默认60s,由/proc/sys/net/ipv4/tcp_fin_timeout来控制,可修改