我们知道tcp协议是有连接、可靠、面向字节流传输。接下来将会介绍具体建立连接过程和断开连接,即三次握手和四次挥手字
主动请求连接的一方会最后发送ACK,主动断开连接的一方会最后发送ACK。
服务端状态改变:
- [CLOSED -> LISTEN] 服务器端调用listen后进入LISTEN状态, 等待客户端连接;
- [LISTEN -> SYN_RCVD] ⼀旦监听到连接请求(同步报文段), 就将该连接放入内核等待队列中, 并向 客户端发送SYN确认报文.
- [SYN_RCVD -> ESTABLISHED] 服务端⼀旦收到客户端的确认报文, 就进入ESTABLISHED状态, 可以进行发送接收数据了.
- [ESTABLISHED -> CLOSE_WAIT] 当客户端主动关闭连接(调用close), 服务器会收到结束报文段, 服务器返回确认报文段并进入CLOSE_WAIT;
- [CLOSE_WAIT -> LAST_ACK] 进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的 数据); 当服务器真正调用close关闭连接时, 会向客户端发送FIN, 此时服务器进入LAST_ACK状态, 等待最后⼀个ACK到来(这个ACK是客户端确认收到了FIN)
- [LAST_ACK -> CLOSED] 服务器收到了对FIN的ACK, 彻底关闭连接.
客户端状态改变:
1.[CLOSED -> SYN_SENT] 客户端调用connect, 发送同步报文段;
2.[SYN_SENT -> ESTABLISHED] connect调用成功, 则进入ESTABLISHED状态, 开始接收发送数据;
3.[ESTABLISHED -> FIN_WAIT_1] 客户端主动调用close时, 向服务器发送结束报文段, 同时进入 FIN_WAIT_1;
4.[FIN_WAIT_1 -> FIN_WAIT_2] 客户端收到服务器对结束报文段的确认, 则进入FIN_WAIT_2, 开始 等待服务器的结束报文段;
5.[FIN_WAIT_2 -> TIME_WAIT] 客户端收到服务器发来的结束报文段, 进⼊入TIME_WAIT, 并发出 LAST_ACK;1
6.[TIME_WAIT -> CLOSED] 客户端要等待一个2MSL(Max Segment Life, 报文最大生存时间)的时间, 才会进入CLOSED状态.
接下来看几个问题:
1.为什么主动方要等待或者TIME_WAIT状态有什么用?并且为什么是2个MSL时间?
如果启动server端,然后启动client,然后用ctrl+c使程序server终止,紧接着再运行server,结果如下:
为什么会出现这种错误呢?
是因为虽然server程序终止,但是tcp协议的连接并没有断开,那么就不会再监听同样的server端口。
TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待2个MSL(MSL是最大报文生存周期)的时间才能回到CLOSED状态;由于server是主动关闭一方,那么在TIME_WAIT期间就不能再次监听同样的端口;
为什么会有TIME_WAIT?
可靠的终止TCP连接,若处于time_wait的client发送给server确认报文段(ack)丢失的话,serve 等待一段时间后,没有收到ack会认为fin包没有发送过去,会重新再次发送fin包到主动方。如果client发送ack直接关闭,如果同样的端口再次建立连接,就会有新的socket,那么刚被动方发送的fin包就有可能对这个新的连接造成影响,这是不希望看到的,所以主动方发送最后一个ack后需要等待一段时间,那么等待多久合适,这里需要是2个MSL时间:
1.确保ack发送成功,
2.假设ack没有发送成功,被动方再次发送fin包,这个fin包在网络中最大生存周期是MSL,过了MSL,这个fin包就算没有发送到主动方,会被丢弃在网络中,那么如果同样的端口再次建立连接,这个重新发送的fin包将不会造成影响(因为被丢弃),如果fin包发送成功,主动方接受到fin包在剩余时间内回复ack。
TIME_WAIT为了保护主动关闭方。
为什么TIME_WAIT是 2MSL?
1. time_wait持续的时间是2MSL,保证旧的数据能够丢弃。由于网络中的数据最大存在MSL。保证旧的数据丢弃的好处是:保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失,否则服务器重启后可能会收到来自上一个进程的迟到的数据,但这种数据很有可能就是错误的。
2.同时也保证最后一个报文可靠到达,假设最后一个ACK丢失,那么服务器会重新发送一个FIN包,这时虽然客户端的进程不在了,但是TCP连接依然在,会重发最后一个ACK。
为什么建立连接是3次握手,断开连接是4次挥手?
这是由于TCP的半关闭造成的。因为TCP连接是全双工的(即数据可在两个方向上同时传递)。所以进行关闭时每个方向上都要单独进行关闭。这个单方向的关闭就叫半关闭。当一方完成它的数据发送任务,就发送一个FIN来向另一方通告将要终止这个方向的连接。发送了FIN只是表示这端不能继续发送数据(应用层不能再调用send发送),但是还可以接收数据。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据,比如:如主机A收到主机B的FIN断开TCP连接请求,只是表示主机B已经发送完数据,主机A收到FIN后作出应答,并终止这个方向的数据传输,此时处于半关闭状态。但是主机A仍然可以发送数据的,只有当主机A发送完数据并发送FIN给主机B时,主机B才停止这个方向的数据传输,并关闭TCP连接。
为什么三次握手最后一次要回复ack ?
一句话,主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。
如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。
如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。
两次握手:服务器回复ACK和SYN,服务器以为连接建立成功,会消耗资源维护这个连接,但是这个回复可能由于一些原因丢失,即连接并没有建立成功,那么就消耗了服务器资源。
三次握手:即使第三次ack回复失败,也只是客户端认为将连接建立成功,异常在客户端。
如果是四次握手:服务器回复ack,ack丢失,异常在服务器,同二次握手。
2)在很多时候,TCP连接的断开都会由TCP层自动进行,例如当ctrl+c终止你的程序,TCP连接依然会正常关闭。
当第三次握手失败服务器并不会重传ack报文,而是直接发送RTS报文段,进入CLOSED状态。这样做的目的是为了防止SYN洪泛攻击。
泛洪攻击
SYN泛洪攻击则是客户端向服务器发送SYN报文之后就不再响应服务器回应的报文。由于服务器在处理TCP请求时,会在协议栈留一块缓冲区来存储握手的过程,当然如果超过一定的时间内没有接收到客户端的报文,本次连接在协议栈中存储的数据将会被丢弃。攻击者如果利用这段时间发送大量的连接请求,全部挂起在半连接状态。这样将不断消耗服务器资源,直到拒绝服务。