TCP/IP协议

@wintry大约 9 分钟

TCP/IP族

互联网进行通信时,需要相应的网络协议,TCP/IP 原本就是为使用互联网而开发制定的协议族。

因此,互联网的协议就是 TCP/IP,TCP/IP 就是互联网的协议。

TCP/IP协议分层模型

OSI 参考模型注重 “通信协议必要的功能是什么”

而 TCP/IP模型 则更强调“在计算机上实现协议应该开发哪种程序”。

OSI七层模型

1、物理层:负责0、1比特流与电压的高低,光的闪灭之间的互换;双绞线,无线,光纤

2、数据链路层:负责物理层面上的互连、节点间的通信传输;以太网,无线网,PPP

3、网络层:负责寻址和路由选择;ARP,ICMP、IPv4,IPv6,IPsec

4、传输层:起可靠传输作用,只在通信双方节点进行处理;TCP,UDP

5、会话层:负责建立和断开通信连接

6、表示层:负责数据格式的转换

7、应用层:提供应用程序通信细节;Telnet,SSH,HTTP,SMTP,SSL/TLS,FTP,SNMP

TCP/IP四层模型

1、应用层:对应OSI参考模型的高层,提供各种服务

2、传输层:为应用层实体提供端到端的通信功能,保证了数据包的顺序传送及数据的完整性

3、网际互联层:主要解决主机到主机的通信问题 ,有三个主要协议:

  • 网际协议(IP)
  • 互联网组管理协议(IGMP)
  • 互联网控制报文协议(ICMP)

4、网络接入层: 负责监视数据在主机和网络之间的交换

三次握手

TCP3

第一次握手

客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN(c)

此时客户端处于 SYN_Send 状态

第二次握手

服务器收到客户端的 SYN 报文之后,以自己的 SYN 报文作为应答,并指定自己的 ISN(s)

同时会把客户端的 ISN + 1 作为 ACK 的值,表示自己已经收到了客户端的 SYN

此时服务器处于 SYN_REVD 的状态

第三次握手

客户端收到 SYN 报文之后,会发送一个 ACK 报文

把服务器的 ISN + 1 作为 ACK 的值表示已经收到了服务端的 SYN 报文

此时客户端处于 establised 状态

服务器收到 ACK 报文之后,也处于 establised 状态,此时,双方已建立起了链接

三次握手的作用

确认双方的接受能力、发送能力是否正常

指定自己的初始化序列号,为后面的可靠传送做准备

如果是 HTTPS 协议的话,三次握手这个过程,还会进行数字证书的验证以及加密密钥的生成

四次挥手

断开连接时会进行四次挥手

TCP4

第一次挥手

客户端发送一个 FIN 报文,报文中会指定一个序列号

此时客户端处于 FIN_WAIT1 状态。

第二次挥手

服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值

表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态

第三次挥手

如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。

第四次挥手

客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答 且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。

需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态。

服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。

TCP-11种网络状态

含义状态
LISTEN侦听来自客户端的TCP端口连接请求
SYN-SENT在发送连接请求后等待匹配的连接请求
SYN-RECEIVED在收到和发送一个连接请求后等待对连接请求的确认
ESTABLISHED代表一个打开的连接,数据可以传送给用户
FIN-WAIT-1等待远程TCP的连接中断请求,或先前的连接中断请求确认
FIN-WAIT-2从远程TCP等待连接中断请求
CLOSE-WAIT等待从本地用户发来的连接中断
CLOSING等待远程TCP对连接中断的确认
LAST-ACK等待原来发向远程TCP的连接中断请求的确认
TIME-WAIT等待足够的时间以确保远程TCP接收到连接中断请求的确认
CLOSED没有任何连接状态

问题汇总

三次握手过程中可以携带数据吗?

很多人可能会认为三次握手都不能携带数据,其实第三次握手的时候,是可以携带数据的。

也就是说,第一次、第二次握手不可以携带数据,而第三次握手是可以携带数据的。

为什么这样呢?大家可以想一个问题,假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据。

因为攻击者根本就不理服务器的接收、发送能力是否正常,然后疯狂着重复发 SYN 报文的话,这会让服务器花费很多时间、内存空间来接收这些报文。

也就是说,第一次握手可以放数据的话,其中一个简单的原因就是会让服务器更加容易受到攻击了。

而对于第三次的话,此时客户端已经处于 established 状态,也就是说,对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据没啥毛病。

什么是半连接队列?

服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列。

当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。

SYN-ACK 重传次数的问题

服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传。如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。

注意,每次重传等待的时间不一定相同,一般会是指数增长,例如间隔时间为 1s,2s,4s,8s

TIME_WAIT 状态

什么场景下会出现大量TIME_WAIT ?

TCP 连接中,主动发起关闭连接 的一端,会进入 TIME_WAIT 状态

高并发场景下会产生大量的TIME_WAIT(如端口扫描器),TIME_WAIT 连接存在,属于正常现象

TIME_WAIT 多少算多,会有什么影响 ?

每一个TIME_WAIT状态会占用一个本地端口,上限为65535

当大量的连接处于 TIME_WAIT 时,新建立 TCP 连接会出错,address already in use : connect 异常

如何优化TIME_WAIT问题?

使用快速回收和连接复用

查看各个状态的连接数量

netstat -an | awk '/^tcp/ {++State[$NF]}END{for(key in State)print key "\t" State[key]}'

#LISTEN	14
#ESTABLISHED	9

优化Linux网络内核参数

net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_timestamps = 1
#默认开启, 记录标记时间戳(开启tw_recylce和tw_reuse一定需要timestamps的支持)
tcp_tw_recycle 是怎么工作的 ?

回收(recycle),最快时间回收

如果开启了 tcp_tw_recycle, 则内核会记住客户端上次发来数据包的时间戳

如果发来的数据包时间戳小于内核记录的最后发来的数据包时间戳,那么将会丢弃此数据包

这种情况在 NAT 模式下多机器时间滞后或同时发送, 会有很大危险, 会造成难以排查的异常情况

tcp_tw_reuse 是怎么工作的 ?

复用(reuse),不改变 TIME_WAIT 状态

如果开启了 tcp_tw_reuse, 如果客户端发来的时间戳大于先前连接内核记录的最新时间戳

则 Linux 将重新使用状态中的现有连接以 TIME-WAIT 用于新的对外请求连接,

状态中的传出连接 TIME-WAIT可在仅一秒之后重复使用

TIME_WAIT 必须快速回收处理吗 ?

不必须,存在TIME_WAIT状态是正常现象,只有影响到建立新连接的情况下才需优化

实际上TIME_WAIT是TCP为了解决复杂的网络问题提出的一种解决方案

TIME_WAIT 是为了保证全双工的 TCP 连接正常终止和保证网络中迷失的数据包正常过期

更深入的请看这篇文章:https://juejin.cn/post/7018785895515947039open in new window

除了快速回收和连接复用,还有什么方法可以解决TIME_WAIT 的问题?

1)客户端,HTTP 请求的头部,connection 设置为 keep-alive,保持存活一段时间

net.ipv4.tcp_keepalive_time = 1200
#表示当keepalive起用的时候,TCP发送keepalive消息的频度;缺省是2小时,改为20分钟

2)服务端,缩减 TIME_WAIT 时间,net.ipv4.tcp_fin_timeout = 30

close_wait过多原因和解决方案

close_wait是出现在服务端的一个状态,用来关闭服务端连接的

当close_wait过多时服务端的线程可能被阻塞,没法及时发起关闭动作

解决方案

1 业务上使用完socket主动关闭

2 监控tcp连接,如果长时间没有活动,则主动关闭

3 程序上当socket读数据时,长度为0,则主动关闭

为什么客户端发送 ACK 之后不直接关闭,而是要等一阵子才关闭?

因为要确保服务器是否已经收到了我们的 ACK 报文,如果没有收到的话,服务器会重新发 FIN 报文给客户端。客户端再次收到 ACK 报文之后,就知道之前的 ACK 报文丢失了,然后再次发送 ACK 报文。

至于 TIME_WAIT 持续的时间至少是一个报文的来回时间。 一般会设置一个计时,如果过了这个计时没有再次收到 FIN 报文,则代表对方成功,就是 ACK 报文,此时处于 CLOSED 状态