三次握手

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了三次握手相关的知识,希望对你有一定的参考价值。

网络连接状态

网络连接状态非常重要这里既包含三次握手中的也包括四次断开中的,所以要熟悉。

状态
说明
LISTEN
首先服务器需要打开一个socket进行监听,监听来自远方TCP端口的连接请求
SYN_SENT
表示主动连接,客户端能通过应用程序调用connect()函数进行active open。于是客户端TCP发送一个SYN以请求建立一个连接,之后状态为SYN_SEND,表示已发送一个SYN到服务器端,等待SYN 1+ACK 0响应。
SYN_RECV
服务器端收到客户端的SYN 1+ACK 0,然后状态变为SYN_RECV。表示服务器收到了客户端发来的SYN,然后自己也响应了给客户端一个SYN 1+ACK 1,然后等待客户端确认。 这时候客户端过来的连接(属于半连接状态)被放在一个SYN队列里面,SYN泛洪攻击也是这样的,就是服务器响应了SYN+ACK之后,客户端就不在发送ACK了,然后继续发送SYN,直到把服务器的最大连接数量耗尽。 半连接队列长度是由内核参数tcp_max_syn_backlog来决定的。
ESTABLISHED
代表一个打开的连接,客户端收到服务器发送的SYN 1+ACK 1,就变为这个状态,然后向服务器发送ACK,如果服务器收到这个ACK,那么它也变为这个状态。这个状态就是表示连接以及建立,正在或即将传输数据。服务器收到ACK以后就会把半连接从上面提到的SYN队列中删除,然后放到ACCEPT队列中,这时这个半连接的状态就变成了ESTABLISHED。
FIN_WAIT1
主动关闭端(可以是服务器也可以是客户端)应用程序调用了close,于是其TCP发出FIN主动关闭请求,也就是四次断开的第一次,之后就进入了FIN_WAIT1状态,等待远程主机的ACK请求。
CLOSE_WAIT

被动关闭端(可以是服务器有可以是客户端)收到了对方发来的FIN后,进入该状态,然后发出ACK+1以回应FIN请求(它的接收也作为文件结束符传递给上层应用程序)。这个状态实际上是说客户端告诉服务器我没有请求或者数据要发送了,等待看看服务器或者说是进程还有没有数据要发送,如果有则继续发送,如果没有的话,就发送反向关闭指令。

如果服务器大量连接是这个状态就要去查看程序,很有可能是程序设计的问题。

FIN_WAIT2
主动关闭端收到ACK+1后,就进入的FIN_WAIT2状态,也就等服务器是否还有数据发来,如果服务器没有数据了,那么服务器就发送的反向关闭指令。也就是反向关闭连接指令FIN1+ACK1。实际上是告诉客户端我的数据发送完了,可以关闭连接了。
LAST_ACK
被动关闭端,发送反向结束连接请求FIN 1+ACK 1,然后进入LAST_ACK状态,等待主动关闭端发送ACK。
TIME_WAIT

主动关闭端收到FIN 1 +ACK 1后,并进入TIME_WAIT状态,然后发送ACK+1,等待一段时间(2MSL)以确保服务器收到了ACK+1,然后自己进入CLOSED状态。这个阶段主要是客户端为了再次确认一下服务器是否可以关闭连接,因为网络毕竟是不可靠的。

对于服务器有大量TIME_WAIT这个问题通常调整sysctl来解决。

CLOSING
比较少见,表示等待远程TCP对连接中断的确认。
CLOSED

被动关闭端在收到ACK包以后,就进入closed状态,连接结束。

UNKNOWN
未知的Socket状态


三次握手过程


半连接和全连接

技术分享

未完成连接队列:客户端发送SYN到服务器,服务器正在等待完成三次握手,此时就会把客户端发起的这个连接请求放在该队列里,也就是sync队列。这个队列由net.ipv4.tcp_max_syn_backlog参数决定。

已完成连接队列已经完成握手的连接从SYN队列移动到这个队列,也就是accept队列。

比如syn泛洪攻击就是针对syn队列的,攻击方不同的建立连接,但是只做连接的第一步,当攻击者收到SYN+ACK后直接丢弃,导致受攻击的服务器上这个队列满了然后其他正常请求就无法进入。


关于Backlog

TCP连接客户端connect()返回并不代表TCP连接成功,有可能是服务器接收队列满了,系统会丢弃后续的ACK请求,
客户端以为建立了连接,然后就执行后续操作,然后就等待到超时。服务器则会等待ACK超时,会重传SYN。

TCP队列的一些问题

  1. 客户端通过connect向服务器发出SYN包,客户端会维护一个socket等待队列,而服务器则会维护一个SYN队列

  2. 此时是半连接状态,如果socket等待队列满了,服务器则会丢弃,而客户端会返回超时。只要客户端没有收到SYN+ACK,3秒后客户端会再次发送,然后依然没有收到,9秒后再继续发送。

  3. 半连接SYN队列长度由tcp_max_syn_backlog决定

  4. 当服务器收到客户端SYN后,会返回SYN+ACK包,客户端的TCP协议栈会唤醒socket等待队列,发出connect调用

  5. 客户端返回ACK后,服务器会进入一个新的叫做accept的队列,这个队列长度为min(backlog,somaxconn)默认情况下somaxconn是128,表示最多有129的ESTAB的连接等待accept(),而backlog的值由int listen(int sockfd,int backlog)中的第二个参数指定,其含义是设置listen()函数最多允许多个网络连接同时处于挂起状态,大部分平台都是511

  6. 当accept队列满了之后,即是客户端继续向服务器发送ACK包,也不会得到响应,此时服务器通过tcp_abort_on_overflow来决定如何返回,0表示直接丢弃,1表示发送RST通知;客户端则会分别返回read timeout或者connection reset by peer。从上面可以看到有2个队列,一个保存SYN_SEND以及SYN_RECV,另外一个accept队列保存ESTAB的状态。

比如客户端通nginx通信,Nginx立即返回ACK,但是3秒后才返回响应数据,Nginx同后端通信,发送SYN请求等待3秒后端才响应,就可能是backlog值设置过小,导致accept queue溢出,SYN被丢弃导致3s重传。


Linux内核中的TCP/IP参数

tcp_abort_on_overflow 默认为0

TCP全连接队列也就是accept队列满了之后如何处理,默认是0,也就是丢弃,可以改为1,表示如果队列满了这时候有客户端建立连接则发送一个reset包给客户端,表示废除这个握手。


net.core.netdev_max_backlog 默认为128

表示当每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许发送到队列的数据包最大数目。就是说当接口接收
包的速度比内核处理的快时,那么多出来的数据包要存放到队列中,那么这个队列最大可以放多少个呢?就是这个参数设置的。
 
net.ipv4.tcp_max_orphans
用于设定系统中最多允许存在多少TCP套接字不被关联到任何一个用户文件句柄上,如果超过这个值,那么没有与用户文件句柄关联的
TCP套接字就会被复位,同时给出警告信息。这个值主要是为了防止DOS攻击。一般在系统内存比较大的情况下可以调大。

net.ipv4.tcp_max_syn_backlog
用于记录尚未收到客户端ACK信息的连接请求最大值。内存比较多可以设置大一点。也就是半连接的队列,表示服务器收到了客户端的SYN包同时服务器也发送了ACK+SYN,但是还没有收到客户端返回的ACK包,此时连接处于SYN_RECV状态,当服务器收到客户端的ACK包时,则删除该半连接条目,服务器进入ESTABLISHED状态。

net.core.somaxconn
表示用于调节系统同时发起的TCP连接数,一般为128,当高并发的情况下,如果这个值比较小,就会导致连接超时或者重传现象。Nginx服务器中定义的NGX_LISTEN_BACKLOG默认是511,所以需要调整这个参数。当服务器收到ACK包之后,就会进入一个叫accept的队列这个队列的最大长度就是由这个参数决定的。表示最多可有多少个ESTAB的连接等待accept()。

net.ipv4.tcp_timestamps
该参数用于设置时间戳,可以避免序列号重复,在一个端口速率比较大的网卡下,遇到重复的序列号的概率还是比较大的。如果设置为0表示禁用对TCP时间戳的支持。默认情况下,系统是允许重复的。但是对于Nginx来说还是建议关闭。

net.ipv4_tcpsynack_retries
用于设置内核放弃TCP连接之前向客户端发送ACK+SYN包的数量。这个参数主要影响三次握手中的第二次,也就是服务器向客户端发送SYN+前一个SYN的ACK。一般设置为1,表示内核放弃连接之前发送一次SYN+ACK包。

net.ipv4.syn_retries
参数和上一个类似,这是这次是设置内核放弃建立连接之前发送SYN包的数量。也建议设置为1.  

net.ipv4.tcp_tw_reuse = 1
表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;

net.ipv4.tcp_tw_recycle = 1
表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。

net.ipv4.tcp_fin_timeout = 30
表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间。

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

net.ipv4.ip_local_port_range = 1024 65000
表示用于向外连接的端口范围。缺省情况下很小,改为1024到65000。

net.ipv4.tcp_max_syn_backlog = 8192
表示SYN队列的长度,也就半连接队列,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。

net.ipv4.tcp_max_tw_buckets = 5000
表示系统同时保持TIME_WAIT套接字的最大数量,如果超过这个数字,TIME_WAIT套接字将立刻被清除并打印警告信息。默认为180000,改为5000。对于Apache、Nginx等服务器,上几行的参数可以很好地减少TIME_WAIT套接字数量,但是对于Squid,效果却不大。此项参数可以控制TIME_WAIT套接字的最大数量,避免Squid服务器被大量的TIME_WAIT套接字拖死。

本文出自 “小恶魔的家” 博客,请务必保留此出处http://littledevil.blog.51cto.com/9445436/1964970

以上是关于三次握手的主要内容,如果未能解决你的问题,请参考以下文章

三次握手,四次挥手(详解)

一文搞懂TCP的三次握手和四次挥手

TCP三次握手,四次挥手原因

TCP三次握手在服务器端是由哪个函数完成的?

TCP连接时的三次握手,四次挥手

三次握手&&四次挥手