你能否讲解一下TCP的三次握手与四次挥手呢?
面试官如果从整体到局部入手,那我们就先讲讲整个三次握手和四次挥手的过程,但不要忘记,讲的同时应该适当体现你对该知识点掌握的深度和广度,具体怎么说,我们后面慢慢道来。
所谓的握手即一次发包到接收的过程,可能从客户端发送到服务端,也可能从服务端发送到客户端。
先上一张TCP报文结构图,待会我们会回来看这张图:

TCP报文结构
先上三次握手的流程图:

三次握手
接下来我们来详细讲解下上图的过程:



这个细节和问题深究第3题是一致的,服务器使用特定的初始序列号 server_isn(从源和目的地IP和端口的散列中获取)可以用来抵御SYN洪水攻击。具体为什么,以及什么是SYN 洪泛攻击,问题深究部分我们会再详谈。
为了下文描述方便,我们将三次握手分别称为:SYN 报文、SYNACK 报文、ACK 报文
简单来说,三次握手的目的是为了让双方验证各自的接收能力和发送能力。
如果使用两次握手,就不能确认上述所说的四种能力,那么就会导致问题。
假定不采用第三次报文握手,那么只要B发出确认,新的连接就建立了。
现假定一种异常情况,即A发出的SYN报文段并没有丢失,而是在某些网络节点长时间滞留了,以致延误到连接释放后的某个时间才到达B。本来这是一个早已失效的报文段。但B收到此失效的连接请求报文段后,却误以为是A又发出一次新的连接请求,于是就向A发出确认报文段,同意建立连接。
由于现在A并没有发出建立连接的请求,因此不会理睬B的确认,也不会向B发送数据,但B却以为新的运输连接已经建立了,并一直等待A发来的数据。B的许多资源就这样白白浪费了。
相关视频推荐
网络原理tcp/udp,网络编程epoll/reactor,面试中正经“八股文”
需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

首先理解题意,我们知道三次握手正是建立TCP连接的过程,我们假设 A 是客户端,B 是服务端:

rfc793-同时启动
这里先给大家讲一下上图中一些符号的含义:
接下来我们详细来看看上图中,两个请求同时相互发起时,两个TCP均会经历如下状态的转换:

同步请求的状态转换
上述的状态转换图可以跟前面的三次握手的转换图进行对比理解,同时发起的两个请求最终只会建立一个连接。
SYN-RECEIVED 跟 SYN-RCVD是一样的,前者rcf793中的描述方法,后者是《计算机网络-自顶向下方法》中的使用方法。
其实这道题更加深挖了TCP 建立连接的过程,我们可以在rfc793中了解到详细信息。

rfc793-RST
从上图可以看到,第三行就是旧的SYN 连接到达服务器时,第四行是服务器照常返回,第五行是客户端给服务端发送RST 报文,将服务端重置为LISTEN。
我们需要从上图了解到的一点是,服务端在SYN_RECEIVED状态下,接收到旧的SYN 报文时是不能作出判断的,而是照常返回,当客户端接收到该报文后发现异常,才会发送RST 报文,重置连接。
关于RST 报文,我一开始也很疑惑,直到看到rfc793 原文:

rfc793-page33
确实说明了TCP B不能检测这个旧的SYN 报文是否正确,所以正常返回。而客户端收到会进行检测,发现是旧的报文,就会返回RST 报文。
这个问题在网上找到的答案质量参差不齐,翻阅了rfc793,仔细研究后,最终整理出以下答案:
首先考虑失败的情况:

ACK报文丢失导致第三次握手失败
当客户端收到服务端的SYNACK应答后,其状态变为ESTABLISHED,并会发送ACK包给服务端,准备发送数据了。如果此时ACK在网络中丢失(如上图所示),过了超时计时器后,那么服务端会重新发送SYNACK包,重传次数根据
/proc/sys/net/ipv4/tcp_synack_retries来指定,默认是5次。如果重传指定次数到了后,仍然未收到ACK应答,那么一段时间后,Server自动关闭这个连接。
问题就在这里,客户端已经认为连接建立,而服务端则可能处在SYN-RCVD或者CLOSED,接下来我们需要考虑这两种情况下服务端的应答:
这个结论也可以在STACKFLOW上找到验证:

What if a TCP handshake segment is lost?
上图圈住的部分:
总的来说,如果一个ACK 报文丢失了,但它的下一个数据包没有丢失,那么连接正常,否则,连接会被重置。
所谓SYN 洪泛攻击,就是利用SYNACK 报文的时候,服务器会为客户端请求分配缓存,那么黑客(攻击者),就可以使用一批虚假的ip向服务器大量地发建立TCP 连接的请求,服务器为这些虚假ip分配了缓存后,处在SYN_RCVD状态,存放在半连接队列中;另外,服务器发送的请求又不可能得到回复(ip都是假的,能回复就有鬼了),只能不断地重发请求,直到达到设定的时间/次数后,才会关闭。
服务器不断为这些半开连接分配资源(但从未使用),导致服务器的连接资源被消耗殆尽,不过所幸,我们可以使用SYN Cookie进行有效地防御。
所谓的SYN Cookie防御系统,与前面接收到SYN 报文就分配缓存不同,此时暂不分配资源;同时利用SYN 报文的源和目的地IP和端口,以及服务器存储的一个秘密数,使用它们进行散列,得到server_isn,然后附着在SYNACK 报文中发送给客户端,接下来就是对ACK 报文进行判断,如果其返回的ack字段正好等于server_isn + 1,说明这是一个合法的ACK,那么服务器才会为其生成一个具有套接字的全开的连接。

SYN Cookie 防御
当然这种方案也有一定缺点,最明显的就是服务器不保存连接的半开状态,就丧失了重发SYN-ACK消息的能力,这一方面会降低正常用户的连接成功率,另一方面会导致某些情况下正常通信的双方会对连接是否成功打开产生误解,如客户端发给服务端的第三次握手消息(ACK)半路遗失,客户端认为连接成功了,服务端认为没收到ACK,连接没成功,这种情况就需要上层应用采取策略特别处理了。
不固定,client_isn是随机生成的,而server_isn则需要根据SYN 报文中的源、ip和端口,加上服务器本身的密码数进行相同的散列得到,显然这也不是固定的。
讲过程的时候其实已经讲了,第三次握手是可以携带数据的,而前两次不行。
限于篇幅,此处暂时不讲,留意后续文章。
和握手类似,每次挥手也代表一次报文的发出和接收。
因为自顶向上这本书里面的图比较简略,这里采用谢希仁版本的计算机网络中的图:

计算机网络-谢希仁-四次挥手
首先,当前客户端和服务器的状态都为ESTABLISHED,接下来我们详细讲解下上图的过程:




这里我们再来看下rfc793中关于四次挥手的简单例子:

rfc793-正常的关闭例子
要确保服务器是否已经收到了我们的ACK 报文,如果没有收到的话,服务器会重新发FIN 报文给客户端,那么客户端再次收到FIN 报文之后,就知道之前的 ACK 报文丢失了,就会再次发送ACK 报文。
关键就在中间两步。