一、问题说明
在某次聊netty优化相关问题的时候,被问到了netty中可以如何优化TCP三次握手的效率?
如何优化TCP三次握手的效率???起初根本没有听过这类似的问题,也没有任何思路回答。后面查阅了一些资料做了一个总结
二、问题答案
主要可调整点分为以下四点:
- server端根据实际情况增大半连接和全连接队列的容量,让server端可以缓冲更多client握手请求,等server端的处理建立socket连接的线程慢慢的去执行accept,为client建立socket连接。
- 半连接队列大小,调整Linux文件
/proc/sys/net/ipv4/tcp_max_syn_backlog 参数 - 全连接队列大小,调整Linux文件
/proc/sys/net/core/somaxconn 参数) - 在netty中体现为为
ServerSocketChannel 设置参数值,可以通过 option(ChannelOption.SO_BACKLOG, 值) 来设置全连接队列大小 - client根据实际情况调整握手包(
SYN )重试次数(调整net.ipv4.tcp_syn_retries 参数),尽早的把错误暴露给应用程序。 - server端根据实际情况调整
SYN + ACK 握手包重发次数(调整 tcp_synack_retries 参数). - RST 复位调整
三、经典三次握手流程分析
通过分析三次握手的流程,找出该流程中存在的可优化点!!!
1、三次握手流程图
client
server
syns queue(半连接队列)
accept queue(全连接队列)
bind()
listen()
connect()
1. SYN
SYN_SEND
put
SYN_RCVD
2. SYN + ACK
ESTABLISHED
3. ACK
put
ESTABLISHED
accept()
client
server
syns queue(半连接队列)
accept queue(全连接队列)
2、三次握手流程分析
- server端启动,绑定端口并监听端口
- 第一次握手
- 首先client先通过connect()方法向server端发起连接,发起握手
- client先发送一个syn握手包,此时client的状态为
syn_send - server端收到这个握手包以后将这个握手连接put到半连接队列中,然后server端的状态为
syn_rcvd - 第二次握手
- server端向client返回一个握手包syn + ACK报文
- 一般情况下,ACK 报文会在几毫秒内返回。but,如果客户端迟迟没有收到 ACK,客户端会一直重发 SYN 报文,默认是 6 次。
- 第 1 次重试在 1 秒钟后,接着会以翻倍的方式在第 2、4、8、16、32 秒共做 6 次重试,最后一次重试会等待 64 秒。如果依然没有收到 ACK,会终止三次握手。所以,总耗时是 1+2+4+8+16+32+64=127 秒。
- 我们可以根据网络的稳定性和服务器的繁忙程度修改 retries,调整客户端的三次握手时间上限。可以适当调低 retries,尽早的把错误暴露给应用程序。
- client收到server发送的握手包
- 第三次握手
- client向server回复一个ack,代表自己已经收到了server的响应,此时客户端进入
established状态 - server端收到这个客户端的ack响应,并且将半连接队列的这个客户端消息移动到全连接队列里,此时server进入
established状态 (注意:此时server端并没有调用accept完成socket的建立)。这一步将消息从半连接队列转移到全连接队列只能说明三次握手正常完成了,但socket并没有建立
- 如果服务端迟迟没有收到 ACK,就会一直重发 SYN+ACK 报文,tcp_synack_retries 的默认次数是 5 次。第 1 次重试在 1 秒钟后,接着会以翻倍的方式在第 2、4、8、16 秒共做 5 次重试,若仍然没有收到 ACK,才会关闭连接,总耗时是 63s
- 所以,我们可以根据实际情况调整server端的重发次数参数(调整
tcp_synack_retries 参数),如果迟迟没有收到client端的ACK应答,尽早关闭连接。 - 三次握手完成
- 三次握手完成后server端和client端并没有真正建立连接,还不能够进行通信。
- 此时只能说该客户端完成了和server端的三次握手。该client被server端放在了全连接队列里,所以这里全连接队列的容量也是一个优化点,队列容量越大那么可缓冲的client端数量就越多。
- 等server端处理连接的进程空闲了,就执行accept方法从全连接队列取出握手完成的client,为这个client创建一个socket(在Linux中表现为一个fd文件描述符),此时socket才创建完成并且将全连接队列的这个client的消息移除,client和server才能相互正常通信。
四、参考
|