TCP三次握手
在socket编程中,客户端执行connect()时,将触发三次握手。
三次握手状态图
需要注意的是:
- 客户端和服务端的状态变化。
- 发送报文的SYN、ACK标志。
- 报文的seq和ack序列号。
首先服务端创建socket,然后用bind函数绑定端口号,再调用listen函数去监听这个端口。客户端这边创建socket,然后调用connect函数去与服务端对应的端口建立连接(期间有三次握手,半连接队列的内容)。连接成功后服务端调用accept函数从连接队列中取出socket,接收客户端请求。客户端使用send函数向socket写入信息,服务端使用recv函数从socket读取信息。
半连接队列
内核为服务器的每一个LISTEN状态的socket维护两个队列:
- SYN队列(半连接队列):表示处于 SYN_RECV 状态的队列;
- ACCEPT队列(全连接队列):表示已完成连接的队列,等待被 accept系统调用取走。
客户端使用connect函数向服务器发起TCP连接,
- 第一次握手客户端的SYN包到达了服务器后,内核会把这个socket放到半连接队列中。
- 服务器回一个SYN+ACK包给客户端。
- 客户端发来了ACK包,内核把对应的socket从半连接队列中取出,再把这个连接放到全连接队列中
随后服务器调用accept函数时,其实就是直接从全连接队列中取出已经成功建立连接的socket而已。
为什么需要三次握手,两次不行吗?
不行。
原因一:如果没有第三次握手,服务器第二次握手后直接分配缓存资源、建立连接,然后等待数据,会导致服务器受到syn洪泛攻击时损失更大。
原因二:其次还要考虑这种情况:客户端共发出了两个连接请求报文段,其中第一个丢失,第二个到达了服务端建立连接,后来释放。但是第一个丢失的报文段在网络中长时间滞留后到达服务端,此时服务端误认为客户端又发出一次新的连接请求。
- 使用三次握手:服务端分配资源,向客户端发出确认报文段,而客户端不返回ack包,则服务端超时后不建立连接,释放资源。
- 使用两次握手:服务端分配资源,向客户端发出确认报文段,并马上建立连接,但客户端忽略服务端发来的确认,也不发送数据,则服务端一直等待客户端发送数据,浪费资源。
原因三:此外还有一个说法就是,需要三次握手才能让双方确认双方的接收与发送能力是否正常。
- 一次握手:客户端发包,服务端接收。
- 服务端:确定了客户端的发送能力正常、服务端的接收能力正常。
- 二次握手:服务端发包,客户端接收。
- 客户端:确定了客户端的发送、接收能力正常,服务端的发送、接收能力正常。
- 三次握手:客户端发包,服务端接收。
- 服务端:确定了客户端的发送、接收能力正常,服务端的发送、接收能力正常。
如果没有第三次握手,服务端就不清楚客户端能否接收报文,不清楚服务端自己能否发送报文。
SYN洪泛攻击
SYN洪泛攻击就是伪造大量IP地址,然后向服务器不断地发送SYN包,服务器则回复syn+ack包,并等待客户端确认。
- 这些伪造的SYN包将长时间占用半连接队列,导致正常的SYN请求因为队列满而被丢弃。
- 由于源地址不存在,因此服务器需要不断重发,开销很大。
SYN cookie防御机制
背景:服务端在接收到第一次握手后把socket放进半连接队列中,等到收到第三次握手时,从半连接队列里找出对应的socket,然后放进全连接队列,并且分配缓存等资源。因此服务端在受到syn洪泛攻击时,最主要的影响就是半连接队列会被占满,导致无法处理真正的用户的请求。
SYN cookie防御机制:
- 服务器接收到第一次握手后,不分配数据区,而是根据这个SYN报文的首部信息计算出一个cookie值。这个cookie作为第二次握手报文的初始序列号。
- 服务器接收到第三次握手后,根据ACK报文首部信息计算cookie值,如果确认序列号等于cookie值+1,则为它分配资源,建立连接。
SYN cookie防御机制主要思想就是避免为半连接的socket分配专门数据区,从而降低SYN洪泛攻击所带来的资源开销,同时借助序列号来保存半连接socket的信息,不影响后面第三次握手建立连接。
ISN(Initial Sequence Number,初始化序列号)是固定的吗?
三次握手的其中一个重要功能是客户端和服务端交换初始序列号,以便让对方知道接下来接收数据的时候如何按序列号组装数据。
ISN是动态生成的。不用固定的初始化序号的原因是:
- 容易被攻击者伪造序列号进行攻击。
- 容易混淆,网络中滞留的报文和客户端新发的报文序列号相同,二者到达服务器时无法区分。
三次握手过程中可以携带数据吗?
第一握手不可以携带数据,第三次握手可以。
- 第一次握手不可以带数据,否则会让服务器在受到SYN洪泛攻击时,不得不接收大量无用的数据。
- 第三次握手的时候,客户端已经处于 ESTABLISHED 状态。客户端已经知道双方的接收、发送能力正常,所以能携带数据。
TCP四次挥手
在socket编程中,任何一方执行close()操作即可产生挥手操作。
四次挥手状态图:
需要注意的是:
- 客户端和服务端的状态变化。
- 发送报文的SYN、ACK标志。
- 报文的seq和ack序列号。
第二次挥手完成后,TCP处于半关闭状态,即客户端到服务端的连接关闭了,但服务端到客户端的连接还没有关闭 ,服务端继续发送剩下的数据。
挥手为什么需要四次?
关闭连接需要4次挥手,是因为TCP的半关闭机制。第二次握手服务器只是发送了一个ACK包告诉客户端自己收到了FIN报文,随后还要继续发送剩下的数据,然后才发送FIN+ACK报文。如果只有三次挥手的话,服务器就不允许发送剩下的数据,应该立刻发送FIN+ACK报文回应。
TIME_WAIT的作用
2MSL = 去向ACK包最大存活时间(MSL) + 来向重发的FIN+ACK包的最大存活时间(MSL)。
主动关闭方发出最后一次握手后要等待2MSL的原因:
- 保证客户端发送的第四次挥手能够到达服务端(ACK报文超时后,服务端重发第三次挥手),如果不等待2MSL,直接释放连接,可能ACK报文丢失导致对方不能正常进入CLOSED状态。。
- 可以使本连接所产生的所有报文段都从网络中消失,不会影响到下一条连接。
报文段最大生存时间MSL(Maximum Segment Lifetime):MSL是指任何报文段被丢弃前在网络内滞留的最长时间,它与ip数据包的TTL(可经过的最多路由跳数)有关。
服务端出现大量TIME_WAIT和CLOSE_WAIT
linux服务器下可以用netstat 命令看到处于各个状态下的连接数。比如ESTABLISHED 表示正在通信,TIME_WAIT 表示主动关闭,CLOSE_WAIT 表示被动关闭。
大量连接处于TIME_WAIT或CLOSE_WAIT状态会导致大量的socket被占用,服务器无法处理新的请求。
TIME_WAIT
原因:服务端主动关闭连接,但因为网络问题或者客户端问题导致一直需要重发第四次握手。
解决方法:修改系统内核的TCP参数,快速回收和重用TIME_WAIT的连接。(开启net.ipv4.tcp_tw_reuse和net.ipv4.tcp_tw_recycle可以快速回收处于TIME_WAIT状态的资源。)
在高并发短连接的TCP服务器上,当服务器处理完请求后立刻主动正常关闭连接。这个场景下会出现大量socket处于TIME_WAIT状态(这里短连接表示“业务处理+传输数据的时间 远远小于 TIMEWAIT超时的时间”的连接)。
CLOSE_WAIT
服务端被动关闭连接,如果有大量连接处于CLOSE_WAIT状态(半关闭),那么原因肯定就是服务端还没有调用close函数关闭连接,具体有两种情况。
1、服务器在半关闭的状态下处理速度过慢,或者说仍在发送大量的数据给客户端。(具体情况具体处理)
2、服务器出现了错误,比如说忘记写发送第三次挥手的语句之类的,需要检查代码。