(1)在Tcp报文中需要了解的重要字段
1.序号(sequence number):Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
2.确认号(acknowledgement number):Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。
3.标志位(Flags):共6个,即URG、ACK、PSH、RST、SYN、FIN等。具体含义如下:
URG:紧急指针(urgent pointer)有效。
ACK:确认序号有效。
PSH:接收方应该尽快将这个报文交给应用层。
RST:重置连接。
SYN:发起一个新连接。
FIN:释放一个连接。
(1)三次握手过程
image.png1.首先客户端向服务器端发起一端TCP报文
标志位 :SYN 发起一个新连接
序号:seq =x
随后客户端进入SYN-SEND阶段
2.服务器端收到客户端发送的消息后 发送一段TCP报文进行响应
标志位:ACK+SYN 表示确认客户端的seq有效,服务器能够正常接收客户端的数据,并同意创建新连接
序号:序号为Seq=y
确认号:ack = x+1 表示 把收到的序号+1 作为自己的确认号ack的值 并进入 SYN-RECV状态
3.客户端收到服务端的确认消息,确认从客户端到服务器端的信息是正常的,结束SYN -SEND状态,然后给服务器返回一段TCP报文
标志位:ACK,表示“确认收到服务器端同意连接的信号”(即告诉服务器,我知道你收到我发的数据了)
序号:seq = x+1 表示 我收到的你的ack信息
确认号:表示收到的服务器的seq值 并把他+1作为值得ack值 发送时候进入确认连接状态
(2)为什么进行三次握手
我们可以从三个方面来分析需要三次握手的原因
1.三次握手才可以阻止重复历史连接的初始化(主要原因)
2.三次握手才可以同步双方的初始序列号
3.三次握手才可以避免资源浪费
原因一 :三次握手才可以阻止重复历史连接的初始化
我们来看看 RFC 793 指出的 TCP 连接使用三次握手的首要原因:
The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.
简单来说,三次握手的首要原因是为了防止旧的重复连接初始化造成混乱。网络环境是错综复杂的,往往并不是如我们期望的一样,先发送的数据包,就先到达目标主机,反而它很骚,可能会由于网络拥堵等乱七八糟的原因,会使得旧的数据包,先到达目标主机,那么这种情况下 TCP 三次握手是如何避免的呢?
下面是一个在网络阻塞的情况下的Tcp 握手
image.png
1.在网络用的情况下客户端连续发送SYN报文
2.旧的SYN报文 比新的SYN报文 先抵达服务器端
3.服务器端对旧的报文进行响应 返回 对应的SYN+ACK报文
4.客户端收到 服务器端的响应报文 与自己最新发送的报文 进行比对 发现并不是自己期望的报文 此时客户端会发送一个RST报文终止此次连接
如果是两次握手 就没有判断当前连接时候为历史连接,三次握手 客户端 就是足够的信息来判断 当前连接是历史连接还是最新连接
如果是历史连接(序列号过期或超时),则第三次握手发送的报文是 RST 报文,以此中止历史连接;
如果不是历史连接,则第三次发送的报文是 ACK 报文,通信双方就会成功建立连接;
愿意二:三次握手才可以同步双方的初始序列号
作为TCP协议的双方 ,都必须维护一[序列号],序列号是双方通信的关键因素,他的作用有
- 接收方去除重复数据
- 接收方可以根据序列号的顺序去接收
- 可以标识发出去的数据包中那些已经被接收到了
在第一握手的时候,客户端携带“初始序列号”的SYN报文,需要服务器的一个ACK报文应答报文,标识客户端的SYN已经被接收成功,当服务器发送一个“初始序列号”给客户端的时候客户端也需要给服务器端返回一个ACK表示我应答你了 ,就这样一来一回 就保证的初始序列号的可靠与同步
愿意三:避免资源浪费
如果只有两次握手,在客户的的SYN请求过程中发生网络阻塞,客户端就会一直发送SYN请求 ,此时服务器可能多次受到建立新链接的请求 ,然后建立重复连接
即两次握手会造成消息滞留情况下,服务器重复接受无用的连接请求 SYN 报文,而造成重复分配资源。
TCP四次挥手
双方都可以主动断开连接,断开连接后主机中的「资源」将被释放。
四次挥手的过程
image.png
1.客户端需要释放连接向服务器端发送一段TCP报文
标志位:FIN
序号:seq= u
客户端进入FIN-WAIT-1阶段,即半关闭状态,并且停止客户端向服务端发送数据,但是客户端仍然可以接收到服务器返回的信息
注意:这里不发送的是正常连接时传输的数据(非确认报文),而不是一切数据,所以客户端仍然能发送ACK确认报文。
2.服务器端接收的客户端传输过来的报文后,确认客户端想要释放连接,随后服务器端返回一串报文并进入CLOSE-WAIT状态
标志位:ACK
序号:seq =v
确认号:ack = u+1 表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值
随后服务器端开始准备释放服务器端到客户端方向上的连接。
客户端收到从服务器端发出的TCP报文之后,确认了服务器收到了客户端发出的释放连接请求,随后客户端结束FIN-WAIT-1阶段,进入FIN-WAIT-2阶段、
3.服务器端自送发送ACK确认报文后,经过CLOSED-WAIT阶段做好释放服务器端到客户端方向上的连接准备后,再次向客户端发出一段TCP报文,
标志位:FIN+ACK
序号:seq =w
确认号:ack = u+1
随后服务器结束CLOSE-WAIT阶段,进入LAST-ACK阶段,并且停止服务器到客户端发送数据,但是服务器仍然可以接收数据。
4.客户端收到从服务器端发出的TCP报文,确认了服务器端已做好释放连接的准备,结束FIN-WAIT-2阶段,进入TIME-WAIT阶段,并向服务器端发送一段报文,其中:
标志位:ACK
序号:seq = u+1
确认号:ack = w+1
随后客户端开始在TIME-WAIT阶段等待2MSL
服务器端收到从客户端发出的TCP报文之后结束LAST-ACK阶段,进入CLOSED阶段。由此正式确认关闭服务器端到客户端方向上的连接。
客户端等待完2MSL之后,结束TIME-WAIT阶段,进入CLOSED阶段,由此完成“四次挥手”。
为什么 TIME_WAIT 等待的时间是 2MSL?
MSL是 Maximum Segment Lifetime,报文最大生存时间,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃
TIME_WAIT等待是2倍的MSL 是因为网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方接收后又向发送方发送响应,所以一来一回是2倍的MSL。
在 Linux 系统里 2MSL 默认是 60 秒,那么一个 MSL 也就是 30 秒。Linux 系统停留在 TIME_WAIT 的时间为固定的 60 秒。
为什么需要 TIME_WAIT 状态?
主动发起关闭连接的一方,才会有 TIME-WAIT 状态。
需要TIME_WAIT状态主要有两个原因
-
防止旧连接的数据包
-
证最后的 ACK 能让被动关闭方接收,使「被动关闭连接」的一方能被正确的关闭;
原因一:防止就连接的数据包
我们假设一下没有TIME_WAIT或者TIME_WAIT很短 在被延迟的数据包抵达后会发生什么情况
image.png
即没有经过TIME_WAIT在形同TCP端口被复用后有接收到道曾经的301报文 可能会造成数据的混乱
所以,TCP 就设计出了这么一个机制,经过 2MSL 这个时间,足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。
原因二:保证连接正确关闭
TIME-WAIT 作用是等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭
假设 TIME-WAIT 没有等待时间或时间过短,断开连接会造成什么问题呢?
image.png -
如上图红色框框客户端四次挥手的最后一个 ACK 报文如果在网络中被丢失了,此时如果客户端 TIME-WAIT 过短或没有,则就直接进入了 CLOSE 状态了,那么服务端则会一直处在 LASE-ACK 状态。
-
当客户端发起建立连接的 SYN 请求报文后,服务端会发送 RST 报文给客户端,连接建立的过程就会被终止。
如果 TIME-WAIT 等待足够长的情况就会遇到两种情况: -
服务端正常收到四次挥手的最后一个 ACK 报文,则服务端正常关闭连接。服务端没有收到四次挥手的最后一个 ACK 报文时,则会重发 FIN 关闭连接报文并等待新的 ACK 报文。
-
所以客户端在 TIME-WAIT 状态等待 2MSL 时间后,就可以保证双方的连接都可以正常的关闭
TIME_WAIT 过多有什么危害
危害一共有两个
- 内存资源占用;
- 对端口资源的占用,一个 TCP 连接至少消耗一个本地端口;
客户端TIME_WAIT过多,就会导致端口资源被占用,因为端口就65536个,被占满就会导致无法创建新的连接。
如何优化 TIME_WAIT?
- 打开 net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_timestamps 选项;
- net.ipv4.tcp_max_tw_buckets
- 程序中使用 SO_LINGER ,应用强制使用 RST 关闭。
方式一:net.ipv4.tcp_tw_reuse 和 tcp_timestamps
如下的 Linux 内核参数开启后,则可以复用处于 TIME_WAIT 的 socket 为新的连接所用。有一点需要注意的是,tcp_tw_reuse 功能只能用客户端(连接发起方),因为开启了该功能,在调用 connect() 函数时,内核会随机找一个 time_wait 状态超过 1 秒的连接给新的连接复用。net.ipv4.tcp_tw_reuse = 1使用这个选项,还有一个前提,需要打开对 TCP 时间戳的支持,即net.ipv4.tcp_timestamps=1(默认即为 1)这个时间戳的字段是在 TCP 头部的「选项」里,用于记录 TCP 发送方的当前时间戳和从对端接收到的最新时间戳。由于引入了时间戳,我们在前面提到的 2MSL 问题就不复存在了,因为重复的数据包会因为时间戳过期被自然丢弃。
方式二:net.ipv4.tcp_max_tw_buckets
这个值默认为 18000,当系统中处于 TIME_WAIT 的连接一旦超过这个值时,系统就会将所有的 TIME_WAIT 连接状态重置。这个方法过于暴力,而且治标不治本,带来的问题远比解决的问题多,不推荐使用。
方式三:程序中使用 SO_LINGER我们可以通过设置 socket 选项,来设置调用 close 关闭连接行为。
struct linger so_linger;
so_linger.l_onoff = 1;
so_linger.l_linger = 0;
setsockopt(s, SOL_SOCKET, SO_LINGER, &so_linger,sizeof(so_linger));
如果l_onoff为非 0, 且l_linger值为 0,那么调用close后,会立该发送一个RST标志给对端,该 TCP 连接将跳过四次挥手,也就跳过了TIME_WAIT状态,直接关闭。
网友评论