Linux-TCP协议(传输层协议)-TCP协议特点-网络抓包-TCP可靠传输机制-TIME_WAIT及解决方案

Posted 天津 唐秙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux-TCP协议(传输层协议)-TCP协议特点-网络抓包-TCP可靠传输机制-TIME_WAIT及解决方案相关的知识,希望对你有一定的参考价值。

1. TCP协议

1.1 tcp协议特点

  • 面向连接
  • 可靠传输
  • 面向字节流

1.2 面向连接

三次握手

在这里插入图片描述
1.数据包名称
2.连接双方的状态
3.包序管理

四次挥手
在这里插入图片描述
MSL:报文最大生存时间
2MSL = 丢失的ACK的MSL + 重传FIN的MSL
本质上就是为了让主动断开连接方能够接收到被动断开连接方式重传的FIN报文

1.3 抓取网络数据包

1.抓取网络数据包
  window:wireshark
  linux:tcpdump
  万能公式:tcpdump -i any port [端口] -s 0 -w 123.dat
  注意:需要使用root
2.分析tcp网络数据包
3.分析tcp包序号

为什么tcp需要包序号?
  为了可靠传输,客户端维护了一套序号,服务端也维护了一套序号,client–>server消耗的是客户端维护的序号,服务端告诉客户端自己收到数据的时候,是确定客户端的序号,server–>client消耗的是服务端维护的序号,客户端告诉服务端自己收到数据的时候,是确认服务端维护的序号。
在这里插入图片描述
  1.纯Ack数据包不消耗序号
  2.tcp的数据也是消耗序号的,每一个字节消耗一个序号
  3.确认序号 = 消息发送方的序号 + 数据的长度
  4.确认序号的作用:告知消息的发送方,期待下次发送数据从哪一个序号开始发送
  5.tcp三次握手当中,协议双方的起始序号,起始序号并不一定从0序号开始,可以从任意位置开始,只要双方协商好就行

1.4 tcp协议格式

在这里插入图片描述
  32位序号:该条tcp携带的起始序号
  32位确认序号:期望对方发送数据的时候从哪一个序号开始进行发送
  四位首部长度:0xF–>15
  指的是tcp头部的长度,首部长度 = DEC(4位长度)*4 单位:字节
  URG:紧急标志位
  ACK:确认标志位
  PSH:发送数据标志位
  RST:重置连接标志位
  SYN:发起连接标志位
  FIN:断开连接标志位
  16位窗口大小:告知消息发送方,自己对消息的接收能力是多少,这个值是一个动态变化的
  16位检验和:检验数据在传输过程中是否失真
  16位紧急指针:配合URG标志位发送带外数据
  MSS:最大的报文段长度
  1.在三次握手当中,双方协商MSS的大小,取两者的最小值
  2.为什么协商最大报文段长度?
    本质原因:防止报文过大,在网络传输的过程中数据丢失,导致重传
  3.MSS的大小会受到数据链路层MTU的影响
  MTU:最大传输单元,是网卡在传输数据帧的时候的一个限制值,这个限制值是取决于网络传输设备的电气特性

命令: ifconfig
在这里插入图片描述
  MSS + tcpheader + ipheader <= MTU

1.5 tcp可靠传输机制

  1.保证数据可靠有序到达对端
  2.提高效率

1.5.1 确认应答机制

  1.保证了数据可靠有序的到达对端
在这里插入图片描述
  tcp将每个字节的数据都进行了编号,即为序列号,每一个Ack都带有对应的确认序列号,意思是告诉发送者,我们已经收到了哪些数据,下一次你从哪里发

1.5.2 超时重传机制

在这里插入图片描述
  主机A发送数据给主机B之后,可能因为网络拥堵等原因,数据无法到达主机B,如果主机A在特定的时间间隔内没有收到B发来的确认应答,就会进行重发,当然主机A未收到主机B发来的消息也有可能是因为Ack丢失了,形成如下结果
在这里插入图片描述
  因此,主机B可能会收到很多个重复的数据,那么tcp协议可以通过序列号识别出那些重复的包,很容易的做到去重的效果。

RTO:超时重传机制
  这个特定的时间间隔又叫超时的时间,随着网络环境的不同,这个时间是有差异的,如果超时时间设置的太长会影响整体的重传效率,如果时间设置的太短,就有可能频繁的发送重复的包,tcp为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间,Linux、BSD、Unix和Windows超时以500ms作为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。
  RTO = RTT(上次)* i +RTT(上上次)*(1 - i)
在这里插入图片描述

1.5.3 拥塞控制

  慢启动/快恢复/快重传/快恢复/ 机制
在这里插入图片描述
  1.ssthresh的初始值是慢开始门限“阈值”
  2.慢开始:随着传输轮次的增加,拥塞窗口的大小呈指数增长
  3.拥塞避免:随着传输轮次的增加,拥塞窗口的大小呈线形增长

快恢复
在这里插入图片描述
  1.新的慢开始门限 = 拥塞是拥塞窗口大小/2
  2.拥塞窗口 = 新的慢开始门限
  3.执行拥塞避免算法

捎带应答机制
在这里插入图片描述

1.5.4 延时应答机制

在这里插入图片描述
1.通告发送方更大的窗口大小
在这里插入图片描述
  一旦接收方给发送方通告了一个0号窗口,则表示接收方的接收能力为0,而发送方收到0号窗口之后,就不会再给接收方发送数据了
  1.发送方发送窗口探测包
  2.接收方主动通告窗口大小
在这里插入图片描述

1.5.5 滑动窗口机制

  每一个发送的数据段,都要给一个ACK确认应答,收到ACK后再发送下一个数据段,但是这样就会存在性能较差,尤其是数据往返的时间比较长。
在这里插入图片描述
  灰色: 已经发送,并且已经收到了确认,这些数据就可以从发送缓冲区移除掉了
  蓝色: 已经发送没有收到应答
  绿色: 还没有发送但是可以发送
  红色: 不能够发送的数据
  蓝色框: 当前可以直接发送的数据,暂时不需要应答
在这里插入图片描述

  • 窗口大小指的是无需要等待确认应答可以继续发送数据的最大值
  • 发送窗口里面的数据时,不需要等待任何ACK,直接发送
  • 收到第一个ACK后,滑动窗口向后移动
  • 操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区,来记录当前还有哪些数据没有应答,只有确认应答后的数据,才能从缓冲区删掉。
  • 窗口越大,网络的吞吐率就越高

情况一: 数据包已经抵达,ACK被丢了
  这种情况下部分ACK丢了不要紧,因为我们可以通过后续的ACK去确认
情况二: 数据包就直接丢了
  当某一段报文段丢失之后,发送端会一直收到1001这样的ACK,如果发送端主机连续三次收到了同样一个这样的应答,对应的数据就会重新发送,如果接收端收到了1001之后返回的ACK就是7001,因为2001-7000之间的数据已经收到,这种机制称为“高速重发控制”(也叫“快重传”)

1.5.6 流量控制

  接收端处理数据的速度是有限的,如果发送端发送的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包问题,继而引起丢包重传等一系列连锁反应,因此TCP支持根据接收端的处理能力来决定发送端的发送速度,这个机制叫做“流量控制”(Flow Control)

  • 接收端将自己可以接收的缓冲区大小放入TCP首部当中的“窗口大小”字段,通过ACK端通知发送端
  • 窗口大小字段越大,说明网络的吞吐量越高
  • 接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成为一个更小的值通知给发送端
  • 发送端接收到这个窗口之后,就会减慢自己的发送速度
  • 如果接收端缓冲区满了,就会将窗口设置为0,这时发送方将不会再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端

1.6 TIME_WAIT状态

1.6.1 产生情景

  当我们启动server之后然后启动client,使用ctrl+c使server终止,这时候再运行server,就可能存在这种情况。

1.6.2 产生原因

  虽然server应用程序终止了,但是在TCP协议层的连接并没有完全断开,因此不能再次监听相同的server端口,TCP协议中规定,主动断开连接的一方要处于TIME_WAIT状态等待两个MSL的时间后才能回到CLOSED状态,当我们使用ctrl+c终止了server,所以server是主动断开连接的一方,在TIME_WAIT期间不能再次监听同样的server端口,这个时间在不同的操作系统上是不同的,MSL在RFC1122中规定为2分钟,Centos7上默认配置60s,我们可以通过
命令: cat /proc/sys/net/ipv4/tcp_fin_timeout
功能: 查看msl的值
在这里插入图片描述
在这里插入图片描述
  1.time_wait状态存在与主动断开连接方。
  2.如果服务端为主动断开方,此时一定在四次挥手的过程当中拥有time_wait状态,即使当服务端进程已经结束了,但是服务端之前使用tcp协议针对连接还是time_wait状态,换句话说,服务端之前绑定的端口还没有被网络协议栈的tcp协议释放掉,导致服务端无法快速重启。

问题: TIME_WAIT的时间为啥是2MSL?
  MSL是TCP报文最大生存时间,当TIME_WAIT时间为2MSL的时候就能保证两个传输方向上的尚未接收或者迟到的报文段都已经消失,否则可能会收到迟到的数据,并且假设最后一个ACK丢失,那么服务器可以再发送一个FIN,这时客户端进程虽然不在,但TCP连接还在,仍然可以重发LAST_ACK。

1.6.3 解决TIME_WAIT状态引起的bind失败的方法

存在的问题
  在server的TCP连接没有完全断开之前不允许重新监听,某些情况可能不合理,比如当服务器需要处理非常大量的客户端连接,每秒都有很大数量的客户端请求,每个连接生存时间可能很短,这时候如果由服务器端主动关闭连接,就会产生大量的TIME_WAIT问题,每个连接都会存在五元组信息,但是服务器的ip、端口和协议是固定的,如果新来的客户端连接的ip和端口和TIME_WAIT占用的链接重复了,就会出现问题。
解决方案
  使用setsockpt()设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但ip地址不同的多个socket描述符
setsockopt函数,让端口重用
在这里插入图片描述
listenfd:侦听套接字
  SOL_SLCKET:套接字选项
  SO_REUSEADDR:重用端口
  只有当服务端状态是TIME_WAIT的时候才可以重用
  opt->1
  opt的大小

int opt = 1;
setsockopt(listemfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

  1.如果服务端存在大量的close_wait状态的连接,应该如何做?
  close_wait状态一定是存在被动断开连接方的,服务端没有发送FIN报文给主动断开连接方,一定是当前服务端代码没有调用到close函数,代码执行阻塞了。
  解决方案: 排查阻塞点,重启服务端进程

1.7 连接管理机制

  正常情况下,TCP要经过三次握手建立连接,四次挥手断开连接

1.7.1 服务器状态转化

  • [CLOSED->LISTEN]服务器端调用listen后进入LISTEN状态,等待客户端连接
  • [LISTEM->STN_RCVD]一旦监听到连接请求(同步报文段),就将该连接放入内核等待队列中,并向客户端发送SYN确认报文
  • [SYN_RCVD->ESTABLISHED]服务端一旦收到客户端的确认报文,就进入ESTABLISHED状态,可以进行读写数据了
  • [ESTABLISHED->CLOSE_ACK]进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据),担当服务器真正调用close关闭连接时,会向客户端发送FIN,此时服务器进入LAST_ACK状态,等待最后一个ACK到来(这个ACK时客户端确认收到了FIN)
  • [LAST_ACK->CLOSED]服务器收到了对FIN的ACK,彻底关闭连接

1.7.2 客户端状态转化

  • [CLOSED->SYN_SENT]客户端调用connect,发送同步报文段
  • [SYN_SENT->ESTABLISHED]connect调用成功,则进入ESTABLISHED状态,开始读写数据
  • [ESTABLISHED->FIN_WAIT_1]客户端主动调用close,向服务器发送结束报文段,同时进入FIN_WAIT_1
  • [FIN_WAIT_1->FIN_WAIT_2]客户端收到服务器对结束报文段,进入TIME_WAIT,并发出LAST_ACK
  • [TIME_WAIT->CLOSED]客户端要等待一个2MSL(Max Segment Life,报文最大生存时间)的时间,才会进入CLOSED状态

以上是关于Linux-TCP协议(传输层协议)-TCP协议特点-网络抓包-TCP可靠传输机制-TIME_WAIT及解决方案的主要内容,如果未能解决你的问题,请参考以下文章

信我,面试TCP/IP经典问题就这些!

传输层TCP协议连接的建立和断开

传输层协议

传输层协议介绍

TCP/IP

可靠的传输层协议——TCP协议