以下是这篇文章讲解的思维导图,整理完,我也脑瓜子嗡嗡的,怎么这么多,那是因为太重要了,防止面试官把你问死,那就必须去了解,加油啊~~~
参考 : 小林coding
书籍 : TCP/IP 卷一
网站 : 计算机网络-41-60 | 阿秀的学习笔记
知乎文章 : 看到有人说,只看到过TCP状态位为’FIN +ACK’,但从来没有看过状态位只有‘FIN’,你应该怎样给他解释? - 搜索结果 - 知乎
目录
建立连接(三次握手)
三次握手的流程
在三次握手的过程中的一些问题
三次握手的异常问题
在仔细讲讲初始序号ISN的一些常见问题
四次挥手
四次挥手的流程
四次挥手的重要状态
在使用TCP协议进行传输数据之前必须要经历的过程就是先要进行建立连接也就是三次握手,完成三次握手后就可以传输数据了.三次握手究竟是什么,以及流程是什么各种细节,接下来我会讲解~~~.
举个例子 :
就像我们日常生活中一样,要先拨通电话,然后等待对方接听,进行确认双方的声音有没有问题,双方的耳机是否有问题---->来保证正常的通话.我们在接通电话会有以下的对话.
- 喂,你好能听见我说话么? SYN
- 喂,你好,我能听见你说话 ACK ,你能听见我说话么? SYN
- 好的能听见 ACK ,我们开始说正题吧.
以上的情形我们会经常见到,通过这三个对话,双方就能知道自己和对方能够正常通话.
我们来正式说一下三次握手的流程.
以上就是我们三次挥手的基本流程,但是这时面试官就会问你
好,我们来仔细分析三次握手的状态.
- 首先开始客户端与服务端都是在closed关闭状态,服务端主动监听端口处于LISTEN状态,接着客户端初始化序号(client_sin),将序号加到TCP报文里,将TCP报文的标志位的SYN设置为1,然后向服务端发送SYN报文,客户端处于SYN_SENT状态(也就是成功发送连接,等待一个匹配的连接的状态)
- 接着服务端接收到SYN报文,初始化序号(server_sin),将确认应答好设置为(client_sin+1),然后将标志位ACK和SYN设置为1,给客户端返回SYN + ACK 服务端进入SYN_RCVD状态(此状态表示接收到匹配的连接并且发送连接请求,等待确认的状态).
- 客户端接收到ACK报文代表连接发送成功,接收到SYN表示服务端也请求与客户端建立连接,接着客户端将确认应答号设置为(server_sin+1),将ACK报文发送给服务端,此时客户端连接建立好了,进入ESTABLISHED状态,此时的第三次握手的报文(ACK)可以携带数据.
- 服务端收到客户端的ACK报文代表SYN发送成功,连接已经建立好了,进入ESTABLISHED状态,此时双方就都可以传输数据了.
三次握手的重点就是同步初始化序号.
- 三次握手的意义
1.三次握手就是在传输数据之前,先要建立连接,此次连接能够判断通信双方的发送能力和接收能是否正常.
2.三次握手也能够协商一些重要的参数(比如初始化序号,MSS等).
传回ACK表示服务端已经接收到客户端的SYN报文证明客户端到服务器通信正常,传回SYN表示服务端请求与客户端建立连接,此时服务端已经匹配一个连接并发送连接请求等待确认.
因为三次握手就可以知道双方通信的接收能力和发送能力是否正常,同时也能够完成对双方的初始序号确认,能够以最少的通信次数完成可靠连接建立就不需要第四次握手.四次握手反而浪费资源,代价高一些.所以三次握手就能够完成可靠连接建立,知道双方的发送和接收能力正常
举个例子 :
就好像邮寄快递一样,本来两个东西可以装载同一个包裹里,非要两个东西分不同的包裹装邮寄,反而浪费资源,代价大一些,
三次握手能够保证通信双方接收能力和发送能力正常.
第一次握手和第二次握手不能携带数据,而第三次可以携带数据.
原因是第一次握手和第二次握手任何一方并不能保证自己和对方的发送能力和接收能力是否正常(第一次握手完成服务端知道服务端接受能力和客户端的发送能力正常,第二次握手完成客户端知道双方发送和接收能力都正常),如果第一次握手和第二次握手携带数据有很有可能遭受到攻击者的恶意攻击,因为可以在发送SYN报文的时候,攻击者不管服务端的数据有没有接收,接收能力是否正常,可以大量的发送数据,这就会导致服务器因花费很大的时间和内存接收这些数据,导致内存耗尽,无法通信,而第三次握手开始时客户端已经知道双方的接收和发送能力都正常所以可以携带数据
最后总结 : 第一次握手和第二次握手任何一方并不能保证自己和对方的发送能力和接收能力是否正常,所以可能会让服务器更容易受到攻击,而第三次握手开始时客户端已经知道双方的接收和发送能力都正常所以可以携带数据
服务端 : 如果最后一次ACK丢失,服务端就会触发超时重传机制,如果达到最大重传次数,等待一段时间(重传等待时间呈指数增长),服务端还没有收到ACK,服务端就会断开连接.
客户端 : 此时客户端已经建立连接,就会发送数据,当发送数据时,服务器返回一个RST报文代表异常终止,这时客户端就知道第三次握手失败.
第一次握手丢失,代表客户端给服务器发送的SYN报文丢失,这时客户端就会触发超时重传机制,达到最大重传次数,等待一段时间(这个时间是呈指数增长),如果服务端还没有给客户端发送ACK,就会放弃连接.
第二次握手丢失意味着SYN(也就是服务端请求与客户端连接) 和 ACK(这个ACK是回应客户端请求连接的确认报文也就是第三次握手),这就会导致客户端与服务器都会进行重传,客户端会触发超时重传机制,达到最大重传次数,等待一段时间(该时间呈指数增长),如果还没有收到ACK,就会断开连接,对于服务器这边由于向客户端请求连接会等待第三次握手发的ACK,还是会触发重传机制,达到最大重传次数,等待一段时间(时间呈指数增长),如果还没有收到第三次握手(ACK),就会断开连接.
参考 : 16.如果已经建立了TCP连接,但是客户端突然出现故障了怎么办? - 菜鸟面试手册
如果客户端出现故障,服务端不会一直等待,而是等一段时间就会关闭连接,这是因为TCP连接有一个保活机制,该机制是服务器来检测客户端的TCP连接状态,为了避免客户端出现故障或者网络问题而导致服务端一直与客户端保持连接,浪费服务端大量资源.保活机制的原理就是设置保活机制的保活时间,如果超过该时间还是没有数据交互,就会发送保活探测报文,同时会设置保活探测报文发送时间间隔,设置保活探测报文最大发送次数,如果达到最大发送次数还是没有收到客户端的回应,服务端就会关闭与客户端的连接.
初始化序列号基于时钟,每4微秒+1,转一圈要4.55小时.
会有一个初始化序列号生成随机算法 ISN = M+F
M是一个时钟计时器,每4微秒+1
F是一个Hash算法,会根据源IP地址,源端口号,目的IP地址,目的端口号生成的一个随机值,要保证Hash算法不能被外部推算出来,还可以使用MD5算法.
由于随机数会基于时钟器递增的,所以基本不会出现一样的初始化序列号.
SYN攻击就是服务器处于SYN_RCVD(服务端接收到匹配的连接并向对方发送连接请求确认的状态)期间使用虚假的IP地址构造出大量的SYN数据报,请求连接服务器,但是因为服务器无法收到用户的ACK,就导致服务端的半连接队列很快就被占满了,正常的请求连接只能被丢弃,同时服务器还不断进行ACK超时重发而占用资源,使得服务器不能与正常用户连接
缩短超时(SYN Timeout)时间
增加最大半连接数
过滤网关防护
SYN cookies技术
服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列。
当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。
这里在补充一点关于SYN-ACK 重传次数的问题: 服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传。如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。 注意,每次重传等待的时间不一定相同,一般会是指数增长,例如间隔时间为 1s,2s,4s,8s.....
开启 tcp_tw_recycle 参数,并且在 NAT 环境下,造成 SYN 报文被丢弃
TCP 两个队列满了(半连接队列和全连接队列),造成 SYN 报文被丢弃
处于 establish 状态的服务端如果收到了客户端的 SYN 报文(注意此时的 SYN 报文其实是乱序的,因为 SYN 报文的初始化序列号其实是一个随机数),会回复一个携带了正确序列号和确认号的 ACK 报文,这个 ACK 被称之为 Challenge ACK。
接着,客户端收到这个 Challenge ACK,发现序列号并不是自己期望收到的,于是就会回 RST 报文,服务端收到后,就会释放掉该连接。
在发送数据之前建立连接也就是三次握手是必要的,数据传输完毕之后断开连接也是必要的,断开连接也就是4次挥手.
客户端和服务端都会发送一个FIN报文,接收到一个ACK报文.所以为四次挥手.
客户端发送FIN报文代表客户端是不能发送数据,但可以接收数据,这时第一次挥手,当服务端接收到客户端发送的FIN报文,服务端会给客户端返回一个ACK,这时第二次挥手,但是由于服务端可能还会有一些数据没有处理完成,所以就需要等待一段时间在给客户端发送FIN报文,当服务端给客户单发送FIN报文要请求断开连接,这时第三次挥手,因为服务端可能有数据没有处理完成需要等待本地用户发送中断请求,所以第二次挥手和第三次挥手一般情况下不能合并.客户端收到服务端的FIN报文返回一个ACK,这就是第四次挥手,所以整个断开连接的过程就是四次挥手.
如果第一次挥手丢失(也就是客户端发送给服务端的FIN报文丢失),这时会触发超时重传机制,达到最大重传次数之后,等待一段时间之后(重传时间呈指数增长),客户端还没有收到服务端的中断连接请求确认报文ACK的时候,客户端就会断开连接.
如果第二次挥手丢失也就是服务端返回给客户端的ACK丢失,客户端没有收到服务端的ACK,这时候客户端就会触发超时重传机制,达到最大重传次数,等待一段时间(该时间呈指数增长),如果客户单还没有收到服务端返回的ACK报文,这时客户端就会断开连接.
注意这里还有个问题,就是客户端在接收到第三次握手(服务端发送的FIN报文)前都处于FIN_WAIT_2状态
- 如果客户端是调用close函数关闭连接,就无法关闭和接收数据,所以在FIN_WAIT_2得状态不能持续太久,该时间由tcp_fin_timeout参数来指定,默认为60s,也就是说如果客户端是调用close()函数关闭连接,如果60s后还没有收到服务端发送的FIN报文那么客户端就会关闭连接.
- 如果客户端是使用shutdown()函数来关闭连接,就只关闭了发送方向,如果客户端一直没有收到服务端发送的FIN报文,那么就一直死等.
这里要注意当服务端收到客户端发送FIN报文(第一挥手),内核会立即返回ACK确认应答报文,服务端就进入了CLOSE_WAIT状态,此时要等待服务端处理完数据,也就是要等待用户代码调用close(这里要注意内核没有权利本地用户来关闭连接,必须本地用户主动关闭连接),内核就会自动给客户端发送FIN报文,服务端进入LAST_ACK状态等待客户端返回ACK.
如果第三次握手丢失(也就是服务端发送给客户端的FIN报文丢失),服务端就会触发超时重传机制,达到最大重传次数,等待一段时间后(呈指数增长),如果服务端还没有收到客户端的ACK,那么客户端就断开连接.
此时也要注意,如果客户端是调用close()函数来关闭连接的,客户端在FIN_WAIT_2状态的时间不能持续太久,那么如果超过参数(tcp_fin_timeout)所规定的时间还没有收到服务端的FIN报文,那么客户端就会断开连接
当服务端发送FIN报文时(第三次挥手),服务端进入LAST_ACK状态等待客户端返回ACK,客户端接收到FIN报文,内核就会返回个ACK,客户端就进入了TIME_WAIT状态,此时客户端等待2MSL后,客户端就会关闭连接.
当第四次挥手丢失,也就是客户端返回给服务端的ACK丢失,这时服务端就会触发超时重传机制,达到最大重传次数,等待一段时间(时间呈指数增长)后,如果服务端还没有收到ACK,服务端就断开连接.
如果客户端进入TIME_WAIT状态时,如果服务端又发来FIN报文,客户端就会重置定时器,等待2MSL后客户端就关闭连接.
在三次握手中,SYN+ACK合并成一次也就是第二次握手,为什么可以合并,这是因为三次握手就能够最少通信次数的保证可靠的连接,如果将SYN和ACK分开变为四次握手,那么效率会下降,数据报本可以一次封装分用发送非要两次. 而对于四次挥手来说,服务端接收到客户端FIN报文返回ACK报文之后,可能还要进行数据处理,所以需要等待数据处理完成,应用程序调用关闭连接的函数才关闭服务端到客户端的连接,所以中间两次不能合并,所以为4次挥手.
不一定,如果进程退出,不管是正常退出,还是异常退出,内核都会发送FIN报文,完成四次挥手.
close() : 使用close()方法来关闭连接,关闭了socket的发送方向和接受方向,也就是socket不具有接收和发送能力了.如果在多线程/进程的情况下,多个线程/进程共用一个socket连接,如果一个进程使用了close关闭只是让socket引用数-1,不会导致socket不可用,不会发送FIN报文,其他进程正常读写socket,直到socket引用数减为0,才会发送FIN报文关闭连接.
shutdown() :使用shutdown函数关闭连接,只关闭一个方向,发送方向或者接收方向,如果在多线程/多进程的环境下使用socket,使用shutdown()来关闭连接,直接会使得socket不可用,发送FIN报文,如果其他进程使用则会受到影响.
在四次挥手中 :
使用closed()来关闭连接,当服务端发送数据报给客户端时,由于客户端没有发送能力和接收能力,客户端会给服务端发送RST报文,内核会释放连接,就不会经历完整的四次挥手.
使用shutdown()来关闭连接,当服务端发送数据报给客户端,客户端关闭客户端给服务端发送方向,也就是说客户端有接受能力,所以就可以经历完整的四次挥手.
在没有数据发送的时候并且TCP开起了延迟应答机制时,四次挥手是有可能变为3次挥手的.
服务端数据还没有处理完成,或者在进程不退出的情况下,应用程序还没有调用关闭连接的函数.
RFC793明确规定,除了第一个握手报文SYN除外,其它所有报文必须将ACK = 1。
很好,RFC规定的背后肯定有合理性的一面,能否深究一下原因?
TCP作为一个可靠传输协议,其可靠性就是依赖于收到对方的数据,ACK对方,这样对方就可以释放缓存的数据,因为对方确信数据已经被接收到了。
但TCP报文是在IP网络上传输,丢包是家常便饭,接收方要抓住一切的机会,把消息告诉发送方。最方便的方式就是,任何我方发送的TCP报文,都要捎带着ACK状态位。
ACK状态位单独能承担这个消息传递的任务吗?
还需要确认应答号,确认应答号代表之前的数据已经被接收.
如果确认序号是应用层数据,那么就确认应用层数据,如果确认序号带有FIN标志位那么就是确认FIN报文.
LISTEN状态
LISTEN状态是服务端的状态,表示服务端已经启动,绑定端口成功.
ESTABLISHED状态
ESTABLISHED状态是连接已经建立的状态.
FIN_WAIT_1状态
客户端主动关闭连接也就是给服务端发送FIN报文后就进入了FIN_WAIT_1状态.
FIN_WAIT_2状态
FIN_WAIT_2:为半关闭状态,等接受到服务端的中断请求确认就进入了FIN_WAIT_2状态,等待连接中断请求,该状态下不能发送数据(注意返回ACK是内核返回的),但是可以接收数据.
CLOSE_WAIT状态
CLOSE_WAIT状态是被动关闭形成的状态,当服务端接受到客户端的FIN报文返回ACK后就进入了CLOSE_WAIT状态,该状态下要等待本地用户发送中断连接请求(用户代码调用close()),需要等待数据处理完成的状态.
TIME_WAIT状态
TIME_WAIT状态是客户端接受到服务端的FIN报文返回给服务端ACK之后就进入了TIME_WAIT状态,为了防止最后一次握手丢失保证服务端能够正常的关闭连接需要等待2MSL后客户端在关闭连接,在这2MSL内如果最后一个ACK报文丢失,服务端就会重传FIN,客户端收到FIN报文,计时器就会重新计时,保证了ACK报文到达服务端,服务端能够正常的关闭连接.TIME_WAIT也能避免历史连接的报文在新连接中接收,2MSL内能够让本连接的所有报文从网络中消失,使得新连接中的报文都是新产生的.
MSL : MSL是报文最大生存时间,是任何报文在网络中最大的生存时间,超过时间该报文被丢弃.
TTL : 是IP数据报可以经过的最大路由数,每经过他的路由器一次,TTL的值就减一,当TTL的值为0的时候数据报会被丢弃,同时发送ICMP通知源主机.
MSL和TTL的区别是 : MSL是单位时间,而TTL是路由跳数,MSL要大于等于TTL消耗为0的时间,以确保报文已经被自然消亡.
比如Linux系统将TTL设置为64,MSL设置为30,认为经过64个路由器不会超过30s,如果超过30s,该报文就会丢弃.
2MSL是指发送方发送数据报给接收方,接收方接收到数据报进行处理完之后,返回给发送方一个响应,报文一来一回的时间.
要注意的是 : 当客户端在TIME_WAIT状态时候,再次收到SYN报文之后,客户端这边就会重新计时(2MSL的时间).
可能会出现特殊情况,因为序号(序列号是32位无符号数字,达到4G的时候会再次循环到0)和初始化序列号ISN(每4微秒+1,每4.55小时循环一次)就有可能造成回绕现象,而当报文序列段出现网络延迟,就会重传此报文段,这时当断开连接时客户端的TIME_WAIT状态等待时间太短或者没有等待就没有等待延迟报文的时间,直接关闭连接,当下次建立连接时,复用上一次TCP连接,而又刚好出现序列号出现回绕现象,延迟的报文和客户端下一次发送的报文一样,这时服务端就会接收历史连接的报文造成错误.
总结 :
2MSL是指发送方发送数据报给接受放,接受方处理完之后给发送方一个响应,数据报这样一来一回的时间就为2MSL.
客户端进入TIME_WAIT状态的时候等待2MSL才关闭客户端的连接原因有如下两点:
- 第一点 : 防止四次挥手中的最后一个ACK丢失,而导致服务端无法正常关闭连接.当服务端没有收到中断请求的确认报文ACK,服务端就会重传FIN报文给客户端,当客户端接收到重发的FIN报文后,计时器会重新计时,这样在1MSL时间丢失,在2MSL时间内客户端就会收到重发的FIN报文,这样就保证服务端收到最后一个ACK报文确保正常的关闭连接.如果等待时间过短或者没有等待时间客户端直接关闭,就有可能服务端没有收到最后一个ACK,无法正常的关闭连接.
- 第二点 : 防止历史连接的报文段出现在建立相同的新连接而造成错误接收.如果出现网络延迟现象,可能导致客户端会重传这个报文段,而在断开连接进行挥手的时候,客户端进入TIME_WAIT状态等待时间太短/没有等待时间,就有直接关闭连接,当再次建立连接的时复用上一次TCP的连接,而初始序列号/序列号刚好出现回绕的现象就有可能导致客户端发送的报文与延迟的报文序号一样,最终服务端就会收到历史连接的报文造成错误.而如果等待2MSL的时间,这个时间内可以足够的让本次连接的所有报文自然地从网络中消失,让新连接中的数据报都是新产生的数据报.
TIME_WAIT状态是指客户端接收到服务端的FIN报文,给服务端发送最后一个ACK报文也就是第四次挥手后进入lTIME_WAIT状态,该状态是为了保证最后一个ACK报文到达服务端,能够让服务端正常的关闭连接,还能够防止历史连接的报文出现在新的连接中造成错误的接收,等待时间是2MSL,对于保证服务端能够正常关闭连接的2MSL是指在1MSL最后一个ACK丢失,而在2MSL内客户端能够收到重发的FIN报文.对于防止历史连接的报文出现在建立相同的新连接中造成错误接收,在2MSL时间内能够让本连接中的所有报文中在网络中自然消失,在新的连接中只会有新产生的报文不会有历史连接的报文.
对于大量出现TIME_WAIT状态 : 1.有大量的短连接存在就会出现大量的TIME_WAIT状态,比如在高并发的场景下每秒要进行上千万次的查询,如果以短连接的方式与系统进行交互的时候,就会产生很多的连接,也就会有大量的TIME_WAIT状态,如果大量的TIME_WAIT状态占满了所有的可用端口并且连接还没有被系统回收,那么就无法在服务器上创建新的socket连接,就会导致系统停转.并且大量的TCP连接还会占用CPU资源,文件描述符,线程资源,内存资源等.
2.在四次挥手中由于要防止最后一个ACK丢失以保证ACK可靠的到达服务端,最大限制的保证tcp的网络传输的可靠性.就需要等待2MSL
对于大量出现CLOSE_WAIT的状态 : (server端没有发FIN报文和ACK报文)原因是服务器没有正确的关闭socket,导致四次挥手没有正确的完成.解决办法就是加上对应的close方法.危害就是大量的CLOSE_WAIT状态会占用大量的文件描述符,一个机器分配文件描述符优先,超过文件描述符的占用次数就无法建立连接.
TIME_WAIT状态过多也就是连接过多,就会造成占用所有的可用端口,占用端口资源,就无法在服务器上创建新的连接,导致系统停转.
其次,TCP连接过多可能会导致浪费大量的内存资源,线程资源,文件描述符等资源.
CLOSE_WAIT状态是服务端接受到客户端的FIN报文返回ACK之后就进入了CLOSE_WAIT状态,是被动关闭连接形成的.
而TIME_WAIT状态是收到服务端的FIN报文返回ACK就进入了TIME_WAIT状态,是主动关闭连接形成的
FIN_WAIT_2:为半关闭状态,等接受到服务端的中断请求确认就进入了FIN_WAIT_2状态,等待连接中断请求,该状态下不能发送数据(注意返回ACK是内核返回的),但是可以接收数据.
CLOSE_WAIT状态:CLOSE_WAIT状态是被动关闭形成的状态,当服务端接受到客户端的FIN报文返回ACK后就进入了CLOSE_WAIT状态,该状态下要等待本地用户发送中断连接请求(用户代码调用close()),需要等待数据处理完成的状态.
TIME_WAIT状态:TIME_WAIT状态是客户端接受到服务端的FIN报文返回给服务端ACK之后就进入了TIME_WAIT状态,为了防止最后一次握手丢失保证服务端能够正常的关闭连接需要等待2MSL后客户端在关闭连接,在这2MSL内如果最后一个ACK报文丢失,服务端就会重传FIN,客户端收到FIN报文,计时器就会重新计时,保证了ACK报文到达服务端,服务端能够正常的关闭连接.TIME_WAIT也能避免历史连接的报文在新连接中接收,2MSL内能够让本连接的所有报文从网络中消失,使得新连接中的报文都是新产生的.