TCP协议详解(图解三次握手四次挥手)

Posted It‘s so simple

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TCP协议详解(图解三次握手四次挥手)相关的知识,希望对你有一定的参考价值。

目录


1. 三次握手

1.1 图解三次握手

TCP协议是面向连接、可靠传输、面向字节流的。

那么TCP是如何保持可靠且有序的连接的呢?答案是三次握手+四次挥手。

那么TCP的三次握手到底是如何进行的呢?我们首先从三次握手中的数据包名称和连接双方的状态开始看。


如图所示:
客户端在发送SYN数据包之前的状态是SYN_SENT,服务器在接收SYN数据包之前的状态是SYN_RECV状态,当客户端收到来自于服务端的SYN和ACK数据包之后,客户端的状态就会变为ESTABLISHED,表示客户端和服务端单向的连接已建立,而当服务端收到来自客户端的ACK数据包时,服务端的状态就会变为ESTABLISHED,表示服务端与客户端的单向连接已建立,至此,服务器和客户端的双向连接已完全建立,三次握手结束。

问题:为什么要三次握手?两次握手为什么不行?四次呢?

首先因为TCP是有连接,可靠有序、面向字节流的协议,为了保证通信双方是有连接的,因此我们需要进行握手来保证连接,那为什么是三次呢?因为两次握手只能保证连接是单向的,即只建立了从服务端到客户端的单向连接,并不能保证客户端到服务端的有效连接,因此,两次不行,四次会多余,三次握手刚刚好。简而言之,三次握手是为了保证客户端和服务端之间的连接是双工的。

1.2 包序管理

在三次握手中,其实还牵扯到一个包序管理的问题。

1.2.1 如何抓包

在Windows操作系统环境下,我们可以使用wireshark软件来对当前的网络数据进行抓包并进行分析。

在Linux操作系统环境下,我们可以使用tcpdump来进行抓包,具体的语句如下:

tcpdump -i any port [端口号] -s 0 -w xxx.dat

该命令是对当前某个端口进行抓包,并将结果放在xxx.dat中,需要注意的是该命令只能抓取数据,但是不能对其进行分析(并且要在root用户进行抓包),分析我们就在win下的wireshark进行。
该命名既能抓TCP的包,也能抓UDP的,总而言之,所有的网络数据包都可以使用tcpdump这个工具去抓!!

注:抓包的时候,要先进行抓包的命令,然后再启动(为了防止错过三次握手)。

1.2.2 抓包并分析

我们现在在Linux中随便开启一个服务端和客户端,使用tcpdump来对其进行抓包分析。
代码可看我之前写的这篇文章.

在root用户下,运行上面的命令,先进行抓包,在开启客户端和服务端

然后将该123.dat在wireshark中进行分析。

1.2.3 分析TCP包的序号

问题:为什么TCP需要包序号呢?

解答:本质上是为了可靠传输(即数据发送过去后,能收到回复应答的消息),说白了就是服务端和客户端各自维护了一套序号。

  • Client -> Server:消耗了客户端维护的序号,当服务端告诉客户端自己收到数据的时候,是确认客户端所维护的序号。即就是说服务端告诉客户端下一次要发送的序号是我期望收到的自己维护的序号
  • Server->Client:消耗了服务端维护的序号,当客户端告诉服务器自己收到的数据的时候,是确认服务器所维护的序号。

我们继续来分析上面所抓到的包,这次我们来看看他每次序号中的值是如何变的。


用图来表示,如下:

总结一下就是:(这里的序号指的是seq数据包中所带的值)

  • TCP连接双方各自维护一套自己的序号
  • 在三次握手期间连接双方协商了各自维护序号的起始位置
  • 双方在发送各自数据的时候,消耗的是各自维护的序号
  • 双方在回复应答的时候,是期望对方下一次发送数据的时候的序号 。
  • 纯ACK数据包是不消耗序号的

2. 四次挥手

对于四次挥手而言,我们也需要从双方连接的状态和数据包名称来进行分析。

MSL:报文最大生存时间(默认是60ms)。

那这里为什么要让主动断开连接方等待2MSL呢?

解答:当主动断开连接方的状态为TIME_WAIT时,如果回复给被动断开连接方的ACK数据包丢失掉,那么在过了1个MSL后,由于没有接收到ACK数据包的被动断开连接方会再次重传一个FIN数据包,让主动断开连接方重新发送ACK数据包。

总结一下:
① 2MSL:丢失的ACK的MSL + 超时重传的FIN的MSL。

本质上就是为了让主动断开连接方能够接收被动断开连接方重传的FIN报文,这也是可靠的一种体现。

② TIME_WAIT状态存在于主动断开连接方。

如果服务端位于主动连接方,此时一定在四次挥手的过程中拥有TIME_WAIT状态,即使当服务端进程已经结束了,但是服务端之前使用TCP协议针对的连接还是TIME_WAIT状态,换句话说,服务端之前绑定的端口还没有被网络协议栈的TCP协议释放掉,导致服务端无法快速重启。

解决方案:

使用setsockopt函数,让端口重用。

int setsockopt(int sockfd, int level, int optname, 
			   const void *optval, socklen_t optlen);
  • sockfd:使用对应的 listen_fd,即侦听套接字。
  • level:SOL_SOCKET
  • optname:SO_REUSEADDR(重用端口)
  • opt:1

问题来了,如果四次挥手过程中服务端存在大量close_wait状态的连接,应该如何做?

  • close_wait状态一定是存在被动断开连接方的。
  • 服务端没有发送FIN报文给主动断开连接方,一定是当前服务端代码没有调用到close函数,代码执行阻塞了。

解决方案:排查阻塞点,重启服务端进程。

以上是关于TCP协议详解(图解三次握手四次挥手)的主要内容,如果未能解决你的问题,请参考以下文章

wireshark抓包直观图解 TCP三次握手/四次挥手详解

三次握手,四次挥手(详解)

wireshark抓包图解 TCP三次握手/四次挥手详解

TCP的三次握手和四次挥手

tcp协议中的三次握手和四次挥手(图解)

TCP和UDP的区别(三次握手四次挥手全过程图解)