搞懂TCP/IP协议看这篇就够了

TCP是面向连接的、可靠的、有序的、无丢失无重复的数据传输协议,面向字节流。提高TCP传输性能是一个重要的话题,今天我们从TCP三次握手、TCP四次挥手和数据发送三个方面来进行优化。

连接:TCP三次握手

TCP三次握手的主要目的是同步序列号,只有同步序列号之后才能实现可靠传输。三次握手的信号由TCP头部中的SYN标志位和32位序列号两个部分实现。

第一次握手
client给server发送连接请求报文,在这个报文中,包含了SYN=1,client_seq=任意值i,发送之后处于SYN-SENT状态;

第二次握手
server端接收到了这个请求,并分配资源,同时给client返回一个ACK报文,这个报文中包含了标志位SYN和ACK都为1,而小ack为i+1,此时位于SYN-RCVD状态;

第三次握手
client收到server发来的ACK信息后,他会看到server发过来的小ack是i+1,这时他知道了server收到了消息,也给server回一个ACK报文,报文中包含了ACK=1和client_ack=k+1字段;
连接建立,client进入established(已建立连接)状态。

为什么使用三次握手?

如果使用两次握手的话,三次握手中的最后一次缺失,服务器不能确认客户端的接收能力。

在这里插入图片描述

客户端的超时重传机制

客户端发送SYN之后,没有收到服务器的ACK+SYN响应信号,则启动超时重传SYN包。每次等待超时时间为上一次时间的两倍,重传次数由tcp_syn_retries参数控制。
假设retries次数是5,则总耗时:1+2+4+8+16+32=63s,所以在网络稳定时为提高效率则可以适当调低次数,反之则调高次数。

服务器的半连接队列

在服务器第一次收到SYN信号后,服务器状态切换为SYN_RCV,同时维护一个队列来保存未完成的信息,一旦这个队列溢出,则无法建立新的连接。这个队列称为,半连接队列。

半连接队列由长度不仅需要tcp_max_syn_backlog,还有backlog和somaxconn的值(控制accept队列大小)共同控制。

SYN泛洪攻击发生在此处,攻击的就是这个半连接队列。攻击者不断向服务器发出请求之后就消失,造成服务器端的资源耗尽。解决办法就是tcp_syn_cookies功能开启后可以在不使用半连接队列的情况下成功建立连接。工作原理为,服务器根据当前状态计算出一个哈希值,放在SYN+ACK中一同发出,当客户端返回ACK报文时,去除该值验证,如果合法则认为建立连接成功。

图片

服务器端全连接队列

建立连接成功后,半连接队列移除,将新的连接添加到全连接队列。全连接队列的长度由somaxconn和backlog之间的最小值决定。somaxconn是内核参数,backlog是lisen中的第二个参数。

丢包 VS 复位

在新的连接无法连接时,TCP默认采用丢包的方式处理后面的请求。可以用netstat -s 查看丢弃连接的情况,输出值为累计值:

图片

除了丢包这种默认行为,我们还可以选择向客户端发送RST复位报文,参数tcp_abort_on_overflow可以打开此功能。打开之后,在客户端异常时会看到connection reset by peer,原因有很多种。

能否绕过三次挥手?

TCP fast open功能可以绕过三次挥手,可以通过设置tcp_fastopen内核参数来打开fast
open功能。具体为,第一次发起http+get请求的时候,需要三次握手流程,之后客户端再次向服务器建立连接时,可以绕过三次握手,减少一个RTT流程。
图片

断开:TCP四次挥手

通常关闭连接的方式有两种:RST暴力关闭方式和FIN四次挥手流程。关闭连接的函数有两种:关闭两头的close函数和只关闭一方的shutdown函数。四次挥手是温柔的断开方式,只有FIN和ACK两种信号。要注意的是,主动发起关闭的一方会有time_wait阶段。

第一次挥手
client给server发送一个FIN报文,挥手之后client进入FIN_WAIT_1的第一阶段;

第二次挥手
server收到client发来的FIN报文后,给client返回一个ACK信息,并且ack=seq+1,server进入CLOSE_WAIT阶段,而client收到之后处于FIN_WAIT_2;

第三次挥手
当server发完所有数据时,他会给client发送一个FIN报文,告诉client说“我传完数据了,现在要关闭连接了”,server变成LAST_ACK状态,等着client最后的ACK信息;

第四次挥手
当client收到这个FIN报文时,server发ACK信息,但是它怕server收不到信息,所以进入TIME_WAIT状态,等待server确认收到这个ACK信息则正式关闭了tcp连接,进入CLOSED状态。

图片

客户端FIN_WAIT1状态

客户端主动发送FIN报文后收不到ACK就会处于FIN_WAIT1状态,超时重传机制同上。一般如果FIN_WAIT1状态连接很多,我们需要考虑降低tcp_orphan_retries值,当重传次数超过时就会直接关闭。

当客户端遇到恶意攻击无法发出FIN报文时,就会处于FIN_WAIT1状态。调用了close函数的一方的叫做孤儿连接,为了防止孤儿连接过多资源占用,TCP提供了tcp_max_orphans控制,如果超过这个阈值,新增的孤儿连接不走四次挥手,选择直接关闭。close函数关闭的孤儿连接,这个状态无法持续太久,因为无法发送和接收数据。

客户端的FIN_WAIT2状态

客户端收到ACK报文后处于FIN_WAIT2状态,当FIN_WAIT2状态连接很多时,我们就需要降低tcp_orphan_retries值。当重传次数超过它时,连接就会直接关闭掉。

而调用shutdown函数关闭的连接可以一直处于FIN_WAIT2状态。tcp_fin_timeout控制了这个状态下的持续时长。默认时常是60s,与后面的TIME_WAIT状态持续时间相同的。

客户端TIME_WAIT为2MSL

原因:

  • 防止旧连接的发送延迟的包
  • 保证连接正确关闭,允许报文至少丢失一次,则重发FIN会在第二个MSL内到达。

虽然time_wait的存在是必要的,但是它毕竟会消耗资源,所以可以调整时间和控制time_wait上限个数(tcp_max_tw_buckets)。如果time_wait参数超过该参数时,新关闭的连接不再经历time_wait而直接关闭。

客户端time_wait状态连接+时间戳实现连接复用

为了提高TCP利用率,打开tcp_tw_reuse参数,但是该参数只用于客户端,在调用connect的时候才起作用。同时要打开时间戳tcp_timestamps,会因为时间戳过期而被丢弃,也可以防止重复的数据包绕回。

服务器的被动策略

在服务器收到FIN报文时,内核会自动回复ACK,处于close_wait状态等待应用进程调用close函数关闭连接。内核自己是没有权利去关闭的,如果用netstat命令发现有大量close_wait状态,则说明应用程序有问题,要去排查。

数据:TCP数据发送

TCP发送报文之后不会立刻删除,会用于重传。所以如果TCP是每发送一个数据都要进行一次确认应答,效率很低,所以进行并行批量发送报文。

滑动窗口是用来控制发送方发送数据量的。内核收到报文,用缓存区存放,接收窗口变小;用read函数后数据被读入用户空间,内核缓冲区被清空,窗口变大。

发送数据优化

TCP头部的窗口大小为2个字节,即64KB。可以通过tcp_window_scaling去扩大窗口,当然也不会无限增大,因为还受到网络限制。

调节发送缓冲区范围:tcp_wmem,自动调节功能自动开启。

调节接收缓冲区范围:tcp_rmem,根据tcp_moderate_rcvbuf来开启自动调节功能。

调节内存范围:tcp_mem

下面我们通过一张图来总结一下,有哪些方法可以提高TCP的性能:

图片

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值