Long

欢迎来到Long的博客站点

TCP三次握手和四次挥手

前言

对于TCP协议相关的说明,这里就不赘述了,想要了解的可以去看看《TCP/IP详解卷一:协议》这本书,里面关于tcp的讲解非常深刻,这里就说说tcp三次握手和四次挥手,后面有机会再聊一聊tcp/ip协议。

TCP三次握手

TCP的三次握手相信学习过网络的人再清楚不过了,但是可能对于其中稍微深层次的知识记忆不怎么深刻;直接利用tcpdump来抓取三次握手的数据报文。这里我telnet连接到我租用的阿里服务器,其实不用,可以直接本地连接本地的telnet服务,实现三次握手抓包。

xinlongdeng@ubuntu:~/GitHub_program/Tinyhttpd/simpleclient$ sudo tcpdump -i ens33 -nt '(src 192.168.92.138 and dst 112.124.12.155) or (src 112.124.12.155 and dst 192.168.92.138)'
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
IP 192.168.92.138.45196 > 112.124.12.155.80: Flags [S], seq 1082808877, win 64240, options [mss 1460,sackOK,TS val 1382094075 ecr 0,nop,wscale 7], length 0
IP 112.124.12.155.80 > 192.168.92.138.45196: Flags [S.], seq 1996443845, ack 1082808878, win 64240, options [mss 1460], length 0
IP 192.168.92.138.45196 > 112.124.12.155.80: Flags [.], ack 1, win 64240, length 0

从抓取的报文可以看出来,tcp建立连接就是总共三次报文的交换,其实表面上看去好像是建立连接,其实不然,本质上是主要通过tcp通信的双方是在相互交换tcp控制信息,以及确认对方是否能正确收到自己发送的报文。这里已经体现了为什么tcp需要三次握手,后面会详细讲解。

在第一个报文段中,ip:192.168.92.138 port:45196 发送给ip:112.124.12.155 port:80。这是第一次握手过程,Flags中的S表示的是SYN标志,表示这是一个连接报文段;

序号为 1082808877, 这里序号是为了后面进行实际数据传输过程中的报文序号,便于tcp对报文进行重组,上交给上层协议,这个数字是随机生成的,为了安全;

窗口大小为 64240, 这是tcp的流量控制手段,因为tcp中每发送一报文,接收方都需要确认一个报文段,然后发送端才能接着发送报文,显然这样的效率非常低,同时也会造成网络中的确认报文段非常多,为了提高效率,每次发送规定一个窗口,发送端发送一个报文,窗口减小,当窗口大小为0时,将不能再发送数据,直到接收方再次发送一个报文告知其接收窗口的大小,其实该窗口就是一个滑动窗口,在窗口中数据可以发送出去,接收方有一个接收窗口,在接收到报文时,将需要发送确认报文(此时如果接收方也可能发送数据,将会把确认信息和数据同时发送出去,确认报文中是没有任何数据的,数据段的长度为0,只是标志中的ack为1而已,不会和数据相互影响),如果此时又接收到后面的报文段,将只确认后面的报文段,因为确认后面的报文,前面的报文就自然已经确认了;options选项 [mss 1460 每个报文的最大长度,单位为字节,这时因为在以太网中,MTU最大传输单元为1500字节,减掉ip固定头部20字节,tcp固定头部20字节,就是1460个字节;

sackOK 选项, 在tcp通信中,刚刚我们了解了确认机制,发送方发送一个tcp报文,接收方都要确认。同时采用滑动窗口机制来保证效率,这时如果接收方接收了1,2, 3, 5, 6报文,这些报文都没有发送确认报文,如果没有sackok,则需要重发后面的5, 6 号报文,因为发送方发现其没有接收到4号报文请求发送方发送4号报文,发送方将会发送4,5,6号报文,也就是说接收方将会重复接收到5,6号报文,还是为了效率,sacok选项就是使得tcp模块重新发送丢失的4号报文,将不会发送5,6号报文。TS val 时间戳 1382094075, 用于计算双方之间的回路时间,为tcp的流量控制提供重要信息。 ecr 0 回显应答时间,这里不解释。
wscale 7 窗口扩大因子,因为在tcp头部中的窗口大小最大为16位表示,最大为65535个字节,但实际中远不止这么大,为了扩大这个窗口大小,就可能需要设置这个窗口扩大因子,这时为7,就是窗口的实际大小为, 64240 * 2^7.
length 0 报文长度为0,因为同步报文段中没有实际的数据,长度为0.

接着看应答报文,IP 112.124.12.155.80 > 192.168.92.138.45196: Flags [S.], seq 1996443845, ack 1082808878, win 64240, options [mss 1460], length 0,这里面的信息就不在解释了,和上面的一样,就是Flags中多了一个 . 号,表示这是一个应答报文和连接报文,表示服务器同意建立连接,同时ack号为1082808878 = 1082808877 + 1,符合tcp协议的要求。ack号就是接收的报文的序号+报文中数据的长度;但同步报文段比较特殊,因为其没有数据,但也要占用一个序号值,所以这里的确认号为 1082808878。

最后一次握手过程,IP 192.168.92.138.45196 > 112.124.12.155.80: Flags [.], ack 1, win 64240, length 0,由客户端发送应答报文给服务器,告诉服务器其发送的报文客户端已经收到了。

通过一张图来表示:
《TCP三次握手和四次挥手》

四次挥手

直接看四次挥手的报文段

IP 192.168.92.138.45196 > 112.124.12.155.80: Flags [F.], seq 1, ack 1, win 64240, length 0
IP 112.124.12.155.80 > 192.168.92.138.45196: Flags [.], ack 2, win 64239, length 0
IP 112.124.12.155.80 > 192.168.92.138.45196: Flags [FP.], seq 1, ack 2, win 64239, length 0
IP 192.168.92.138.45196 > 112.124.12.155.80: Flags [.], ack 2, win 64240, length 0

从上面四个报文段中可以看出来,tcp的确通过四次交互发送报文来完成断开操作。Flags中的F表示的就是断开连接报文,其余的字段这里不解释了,需要注意的是,在这里体现的是四次报文,但有可能只有三次报文,就是在client请求断开连接的时候,发送了FIN报文,这时候server也请求断开连接,将会同时发送FIN和ACK报文给client,这时候就只有三个报文。四次挥手过程是因为tcp是全双工通信,双方都需要断开连接。因为在一方主动断开连接,另一方可能还有一些数据在路上,此时主动断开连接的一方仍然可以接受到数据,同时给出应答。也就是说没有断开连接的一方仍然是可以发送数据给已经处于半关闭(只能接收报文和发送应答报文)的状态的一方的。

下面用一张图来表示,图中显示了双方的状态,也相当于一张状态图。

《TCP三次握手和四次挥手》

上面的就是tcp四次挥手的过程,从过程中我们可以得出,在客户端主动发送断开连接报文时,此时client将进入FIN_WAIT1状态,当server接收到该报文段并发送确认报文后,server进入CLOSE_WAIT, 从此状态可以得出,此时的server是等待关闭状态,因为client已经要关闭连接了,迟早server也要关闭连接,然后client接受到该确认报文段将进入到FIN_WAIT2状态,通过netstat命令可以查看到client的FIN_WAIT2状态和server的CLOSE_WAIT,注意此时的client是处于半关闭状态的,也就是client到server的连接已经关闭,但是server到client的连接没有关闭。当server确认关闭连接后,发送完结束报文段后进入到LAST_ACK状态,正如名字所表示的,此时server等待client发送确认报文;client接收server发送的结束报文,将处于TIME_WAIT,等待2MSL时间后处于CLOSED状态,然后client发送确认报文,server收到该确认报文后,直接关闭连接,处于CLOSED,内核将socket结构删除。

为什么需要三次握手

这个问题其实在面试中是高频考题,这里解释一下。三次握手是为了保证tcp通信双方的正常,因为tcp协议就是可靠的传输层协议。第一次握手是保证server能正确接受到client发送的报文段,clien能正确发送报文段,同时,保证server知道client能正确发送报文,第二次握手同样也是保证client能正确收到报文,server能正确发送报文,同时,使得client知道server能正确接收和发送报文,但此时server不知道client是否能正确接收报文,所以需要第三次握手,使得server知道client能正确接收报文,这就是从tcp的功能上需要三次握手,保证通信双方都能正确接收和发送数据,也就是双方能正常交流。

从另一方面来思考,如果二次握手行不行,肯定是不行的,首先如上一样,server不能保证client能正确接收到server发送的报文。同时,如果二次握手,总共发送两个报文,client发送报文1,server发送允许连接和确认报文2,如果报文段2在发送过程中丢失了,但是server认为连接已经建立,此时其等待接收数据,但是client没有接收到报文2,认为连接没有建立好,此时其重新发送建立连接请求,但是server认为已经建立好了连接,将接收的同步报文丢弃,这时client将一直请求建立连接,知道超出内核设置的次数,默认超时重连接次数为6次,可以在/proc/sys/net/ipv4/tcp_syn_retries中进行修改。6次之后将确认无法建立连接,报告应用层,连接失败。

还有一种情况就是,在client发送报文段1之后,由于网络延迟,报文段1在网络中停留时间比较长,但不会大于其最大生存时间。根据tcp超时重传策略,client重新发送同步报文段1,然后通过二次握手正确建立连接,发送完数据后断开连接,client等待2MSL时间,此时刚开始丢失的同步报文到达server端,server发送确认报文,client接收到该报文,但是显然这是client不想要的,直接丢弃。server认为有建立了一个新连接,等待接收数据,当client已经关闭的连接,server陷入等待中,发生死锁。

为什么需要四次挥手

四次挥手就是因为tcp连接是全双工通信,一方关闭连接,另一方可能暂时不关闭,可能还有数据需要发送,或者还有一些数据在路上,不能马上关闭连接,因为在路上的数据到达对方将会发送ack报文,不能保证在路上的数据能正确到达对方。一方关闭连接只表示我这边没有数据需要发送给你了,但你可能还有数据会达到我这里。

TIME_WAIT状态

在client接收到server发送的结束报文后将处于TIME_WAIT状态,这个状态有很大作用,首先当server发送结束报文,client发送确认报文,如果确认报文丢失,server将会重新发送结束报文,client需要再次发送确认报文,TIME_WAIT状态就能处理这种情况;如果没有TIME_WAIT状态,client接受的server发送的结束报文直接关闭,确认报文丢失,server将重新发送,此时client已经关闭了连接,client内核已经把刚刚的socket结构删除,client将直接回复一个复位报文,显然这不是server想要的,将会丢弃,再次发送结束报文。直到发送的次数到达内核规定的次数,报告应用层,断开连接失败。

还有一个原因就是如果client没有TIME_WAIT状态。这时又有一个进程和server建立连接,并且使用的端口和之前的进程一样,这时网络中属于之前旧进程的数据到达该新进程,显然这不是新进程想要的,但是仍然会接收,因为此时其使用的socket和旧进程相同。内核判断这些数据就是新进程的。造成数据传输错误。

等待2MSL就是保证网络中的数据能到达client,并且能被处理到,因为网络中的数据最大生存时间不会超过2MSL,其为MSL。我们需要知道的时,在server发送结束报文段后,处于网络中的迟到的的报文此时已经正确到达了client,也就是说网络中的数据client是早就接收到了,这些数据client是不需要的。所以等待2MSL时间,这些报文将会全部丢弃掉。这时建立的和原来相似的连接完全能保证不会接收到就旧连接的数据。这就是为什么需要等待2MSL时间。

点赞
  1. 匿名说道:

    加油 :idea:

  2. 匿名说道:

    :razz:

  3. 匿名说道:

    期待更新啊 :idea:

发表评论

电子邮件地址不会被公开。