TCP 详解
什么是TCP?
TCP 即 Transmission Control Protocol,可以看到是一个传输控制协议,重点就在这个控制。
所谓的连接其实只是双方都维护了一个状态,通过每一次通信来维护状态的变更,使得看起来好像有一条线关联了对方。
TCP 协议头
- Seq 就是 Sequence Number 即序号,它是用来解决乱序问题的。
- ACK 就是 Acknowledgement Numer 即确认号,它是用来解决丢包情况的,告诉发送方这个包我收到啦。
- 标志位就是 TCP flags 用来标记这个包是什么类型的,用来控制 TPC 的状态。
- 窗口就是滑动窗口,Sliding Window,用来流控。
TCP共有6个标志位
- SYN(synchronous),建立联机。
- ACK(acknowledgement),确认。
- PSH(push),传输。
- FIN(finish),结束。
- RST(reset),重置。
- URG(urgent),紧急。
三次握手
握手主要就是为了初始化Seq Numer,这个序号是用来保证之后传输数据的顺序性。重点在于同步序号
那为什么要三次,就拿我和你这两个角色来说,首先我告诉你我的初始化序号,你听到了和我说你收到了。
然后你告诉我你的初始序号,然后我对你说我收到了。
这好像四次了?如果真的按一来一回就是四次,但是中间一步可以合在一起,就是你和我说你知道了我的初始序号的时候同时将你的初始序号告诉我(SYN+ACK )。
因此四次握手就可以减到三次了
为什么要四次挥手?
四次挥手和三次握手成双成对,同样也是 TCP 中的一线明星,让我们重温一下熟悉的图。
为什么挥手需要四次?因为 TCP 是全双工协议,也就是说双方都要关闭,每一方都向对方发送 FIN 和回应 ACK
就像我对你说我数据发完了,然后你回复好的你收到了。然后你对我说你数据发完了,然后我向你回复我收到了。
所以看起来就是四次。
假设 client 已经没有数据发送给 server 了,所以它发送 FIN 给 server 表明自己数据发完了,不再发了,如果这时候 server 还是有数据要发送给 client 那么它就是先回复 ack ,然后继续发送数据。
等 server 数据发送完了之后再向 client 发送 FIN 表明它也发完了,然后等 client 的 ACK 这种情况下就会有四次挥手。
那么假设 client 发送 FIN 给 server 的时候 server 也没数据给 client,那么 server 就可以将 ACK 和它的 FIN 一起发给client ,然后等待 client 的 ACK,这样不就三次挥手了?
从图中可以看到主动关闭方的状态是 FIN_WAIT_1 到 FIN_WAIT_2 然后再到 TIME_WAIT,而被动关闭方是 CLOSE_WAIT 到 LAST_ACK。
图解三次握手和四次挥手的过程
三次握手建立连接阐述
第一次握手:客户端要和服务端进行通信,首先要告知服务端一声,遂发出一个SYN=1
的连接请求信号,”服务端哥哥,我想给你说说话”。
第二次握手:当服务端接收到客户端的连接请求,此时要给客户端一个确认信息,”我知道了(ACK
),我这边已经准备好了,你现在能连吗(SYN
)”。
第三次握手:当客户端收到了服务端的确认连接信息后,要礼貌的告知一下服务端,“好的,咱们开始联通吧(ACK
)”。
到此整个建立连接的过程已经结束,接下来就是双方你一句我一句甚至同时交流传递信息的过程了。
四次挥手断开连接阐述
第一次挥手:双方交流的差不多了,此时客户端也已经结尾了,接下来要断开通信连接,所以告诉服务端“我说完了(FIN
)”,此时自身形成等待结束连接的状态。
第二次挥手:服务端知道客户端已经没话说了,服务端此时还有两句心里话要给客户端说,“我知道你说完了(ACK
),我再给你说两句,&*……%¥”。
第三次挥手:此时客户端洗耳恭听继续处于等待结束的状态,服务器端也说完了,自身此时处于等待关闭连接的状态,并对告诉客户端,“我说完了,咱们断了吧(FIN
)”。
第四次挥手:客户端收知道服务端也说完了,也要告诉服务端一声(ACK
),因为连接和断开要双方都按下关闭操作才能断开,客户端同时又为自己定义一个定时器,因为不知道刚才说的这句话能不能准确到达服务端(网络不稳定或者其他因素引起的网络原因),默认时间定为两个通信的最大时间之和,超出这个时间就默认服务器端已经接收到了自己的确认信息,此时客户端就关闭自身连接,服务器端一旦接收到客户端发来的确定通知就立刻关闭服务器端的连接。
到此为止双方整个通信过程就此终结。这里要声明一下:断开链接不一定就是客户端,谁都可以先发起断开指令,另外客户端和服务端是没有固定标准的,谁先发起请求谁就是客户端。
问题
1.为什么断开链接的时候客户端设置的定时器时间等待要2MSL(两个通信报文的最大时间)?
这个问题也很好理解,当客户端最终告诉服务器端断开确认的时候,他不知道自己的发出的指令是否能准确的一次性被服务器接收。假如服务器没有接收到(这已经耗费了一个报文的最大通信时间了),服务器端将会重新发起一个结束通话的指令(FIN)到客户端,客户端又接收到了服务器发来的结束通信指令将继续给服务器进行一个确认,有人会说那要是客户端发出的确认信息服务端没收到,而服务端重发的断开指令客户端也没收到怎么办,说实话我也无奈,遇到这种情况咱们干脆认为网确实不行了。
2.为什么建立连接要三次握手而断开连接要四次挥手?
说起这个,打一个比喻,目前祖国正在高速发展高铁,建立连接的过程正如上海到北京打通一条高铁线,TCP通信过程是一个全双工模式,即在这条高铁线上要有两个轨道,即能从上海发车到北京又能从北京发车到上海,甚至两边可以同时发车。所以断开连接前提就是要保证两条轨道都没有车,然后双方才能各自发起断开动作。
3.为什么要有 TIME_WAIT?
断开连接发起方在接受到接受方的 FIN 并回复 ACK 之后并没有直接进入 CLOSED 状态,而是进行了一波等待,等待时间为 2MSL。
MSL 是 Maximum Segment Lifetime,即报文最长生存时间,RFC 793 定义的 MSL 时间是 2 分钟,Linux 实际实现是 30s,那么 2MSL 是一分钟。
那么为什么要等 2MSL 呢?
就是怕被动关闭方没有收到最后的 ACK,如果被动方由于网络原因没有到,那么它会再次发送 FIN, 此时如果主动关闭方已经 CLOSED 那就傻了,因此等一会儿。
假设立马断开连接,但是又重用了这个连接,就是五元组完全一致,并且序号还在合适的范围内,虽然概率很低但理论上也有可能,那么新的连接会被已关闭连接链路上的一些残留数据干扰,因此给予一定的时间来处理一些残留数据。
等待 2MSL 会产生什么问题?
如果服务器主动关闭大量的连接,那么会出现大量的资源占用,需要等到 2MSL 才会释放资源。
如果是客户端主动关闭大量的连接,那么在 2MSL 里面那些端口都是被占用的,端口只有 65535 个,如果端口耗尽了就无法发起送的连接了,不过我觉得这个概率很低,这么多端口你这是要建立多少个连接?
如何解决 2MSL 产生的问题?
快速回收,即不等 2MSL 就回收, Linux 的参数是 tcp_tw_recycle,还有 tcp_timestamps 不过默认是打开的。
其实上面我们已经分析过为什么需要等 2MSL,所以如果等待时间果断就是出现上面说的那些问题。
所以不建议开启,而且 Linux 4.12 版本后已经咔擦了这个参数了
调小 MSL 的时间,不过也不太安全,要么调整 tcp_max_tw_buckets 控制 TIME_WAIT 的数量,不过默认值已经很大了 180000,这玩意应该是用来对抗 DDos 攻击的。