学习TCP——三次挥手、四次握手

接下来将从几个方面深入学习TCP:

1.TCP基本认识
在这里插入图片描述
2.TCP连接建立
在这里插入图片描述
3.TCP连接断开
在这里插入图片描述
4.socket编程
在这里插入图片描述

正文

01 TCP 基本认识

TCP头部格式

在这里插入图片描述

  • 序列号:在建立连接时,由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次,就累加一次该数据字节的大小。 (用来解决网络包乱序问题
    问题:初始序列号为什么要随机初始化呢?
    答:保证网络安全 ,如果初始序列号不是以随机的方式产生,那么攻击者则很容易获取到你与其他主机之间通信的初始序列号,并且伪造出序列号进行攻击。(常见的网络攻击手段

  • 确认应答号: 指的是 下一次期望收到的数据的序列号 ,发送端在收到这个确认应答之后可以认为在这个序号以前的数据都已经被正常接收。(用来解决不丢包的问题

  • 控制位:

    • ACK [应答报文]: 该位为 1 时,[确认应答] 的字段变为有效,TCP规定除了最开始建立连接时的 SYN 包之外,此位必须设置为 1。
    • RST [复位报文]:该位为 1 时,表示TCP的连接中出现异常必须强制断开连接。
    • SYN [同步序号报文]: 该位为 1 时,表示希望建立连接,并在其[序列号]的字段进行序列号初始值的设定。
    • FIN [结束报文]:该位为 1 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信主机双方就可以相互交换 FIN 位置为 1 的TCP 段

为什么需要TCP协议?TCP协议工作在哪一层?

IP 层是 【不可靠的】,它不保证网络包的按序交付、也不保证网络包中数据的完整性。
在这里插入图片描述
如图可看见 OSI七层模型和 TCP/IP 分层模型的关系
IP协议位于:网络层
TCP协议位于: 传输层

如果需要保障传输网络数据包的可靠性,那么就需要上层传输层的TCP协议来负责。
因为 TCP 是一个工作在 传输层可靠数据传输的服务,它能确保接收端接收的网络数据包是 无损坏 、无间隔、非冗余和按序的

什么是TCP?

TCP 是 面向连接的,可靠的,基于字节流的传输层通信协议。

  • 面向连接:一定是【一对一】才能连接,不像UDP协议 可以一个主机像多个主机发送消息,不可一对多。
  • 可靠的: 无论网络链路中出现了怎么的链路变化,TCP都可以保证报文一定能够达到接收端。
  • 字节流: 消息是【没有边界的】,所以无论我们消息有多大都可以进行传输。并且消息是【有序的】,当【前一个消息】没有接收到的时候,即使此时接收到了【后面的消息字节】,那么也不能扔给应用层去处理,并同时会对【重复的报文】自动丢弃。

什么是TCP连接?

连接: 用于保证 可靠性和流量控制维护的某些状态信息,这些信息的组合,包括Socket、序列号和窗口大小被称为连接。
所以,建立一个TCP连接是需要 客户端和服务器端 达成这三个信息的共识。

  • Socket: 由IP地址 和 端口号 组成
  • 序列号: 用来解决乱序问题
  • 窗口大小:用来做流量控制

如何唯一确定一个TCP连接

TCP四元组 可以确定唯一的 确定一个连接,四元组包括:

  • 源地址
  • 源端口
  • 目的地址
  • 目的端口
    在这里插入图片描述

源地址目的地址 的字段(32位)是在 IP 头部中,作用是通过 IP协议发送报文给对方主机。

源端口目的端口 的字段(16位) 是在 TCP 头部中,作用是 告诉 TCP 协议应该把报文发给哪个进程。

有一个 IP 的服务器监听了一个端口, 它的TCP的最大连接数是多少?

服务器 通常固定在本地某个端口上监听,等待客户端的连接请求。

因此,客户端的 端口 和 IP 是可变的,其理论值计算公式如下:
在这里插入图片描述
-IPv4来说, 客户端的 IP 数最多为 2^32 , 客户端的 端口数 最多为 2^16,所以服务器单机最大 TCP 连接数 为 2^ 48

但是,服务端最大并发 TCP 连接数远不能 达到理论上限:

  • 文件描述符限制,Socket都是文件,所以首先要通过 ulimit 配置文件描述符的数目;
  • 内存限制,每个TCP连接都要占用一定内存,操作系统是有限的。

TCP 和 UDP 有什么区别呢? 分别的适用场景是?

UDP 不提供复杂的通信机制,依靠 IP协议 提供面向【无连接】的通信服务。
先来看看UDP的头部:
在这里插入图片描述

  • 源端口和目的端口: 告诉UDP应该把 报文发给哪个进程。
  • 包长度: 该字段保存了 UDP首部的 长度 和 数据的长度之和。
  • 校验和: 为了提供了可靠的 UDP 首部 和 数据而设计。

TCP 和 UDP 的区别

1.连接

  • TCP 是 面向连接的 传输层协议,传输数据前要先建立连接。
  • UDP 是 无连接的 传输层协议,不用连接,即刻传输。

2.服务对象

  • TCP 是 一对一 的两点服务,即一条连接只有两个端点。
  • UDP 是可以支持一对一、一对多、多对一、多对多的交互服务。

3.可靠性

  • TCP 是可靠交付的,保证数据可以 无差错、不重复、不丢失、按序到达
  • UDP 是 不可靠的,尽最大努力交付,不保证可靠交付数据

4.拥塞控制和流量控制

  • TCP 拥塞控制和流量控制机制,保证数据的安全性。
  • UDP 没有,即使网络非常拥塞了,也不会影响UDP数据的传输速率。

5.首部开销

  • TCP首部长度较长、会有一定的开销,首部在没有使用【选项】字段时是 20 个字节,如果使用了【选项】字段,长度会变的更长。
  • UDP 首部较短,只有8 个字节,并且是固定不变的,开销较小。

TCP 和 UDP 的 适用场景

1.由于TCP是 面向连接的,能保证数据的可靠性,所以经常用于:

  • FTP ——文件传输
  • HTTP / HTTPS

2.由于UDP 是面向无连接的,可以随时发送数据,且UDP本身处理简单又高效,经常用于:

  • 包总量较小的通信: DNS 、 SNMP 等
  • 视频、 音频 等多媒体通信
  • 广播通信

为什么TCP头部有【首部长度】字段,而 UDP头部 没有呢

原因:TCP头部长度可变, 有 可变长的【选项】字段;而UDP 头部长度是不会变的,无需多一个字段去记录UDP的首部长度。

为什么UDP 头部有【包长度】字段,而 TCP 没有呢?

先看一下:TCP如何计算负载数据长度:

在这里插入图片描述

IP总长度 和 IP 首部长度 在IP首部格式是已知的,TCP 首部长度 则是在TCP首部格式 是已知的。以此可得 TCP数据长度。
而:
为了网络设备硬件设计和处理方案,首部长度需要是 4 字节的 整数倍。
如果去掉UDP【包长度】字段的话,那么UDP首部长度就不是 4 字节的整数倍了。

02 TCP建立连接

TCP三次握手过程 及 状态变迁

TCP 是面向连接的协议,所以在使用TCP传输数据前,必须在先建立连接,而连接就是依靠 三次握手 而进行的
在这里插入图片描述

  • 一开始,客户端 和服务器端 都处于 CLOSED 的状态。 先是服务器主动监听某个端口,处于 LISTEN 状态。
    在这里插入图片描述

  • 客户端会随机初始化序号(client_isn),将此序列号 置于 TCP的 首部的 【序列号】字段中,并且将 SYN 标志位置 置为 1,表示SYN 报文。 接着把 第一个 SYN 报文段发送给 服务端,表示向 服务端 发起连接请求, 该报文不包含 应用层数据,之后客户端处于一个 SYN_SENT 状态。
    在这里插入图片描述

  • 服务端 收到客户端的 SYN 报文之后,首先服务器也随机初始化自己的 序号(server_isn),将次序号填入TCP首部的【序号】字段中,其次把 TCP首部的【确认应号】字段填入 client_isn +1。接着 把 SYN 和 ACK 的标志位都置为1,最后把该报文段发送给 客户端,该报文也不包含 应用层的数据,之后服务器一直处于 SYN_RCVD 状态。
    在这里插入图片描述

  • 客户端收到服务器发来的报文后,还要向服务器回应最后一个 应答报文,首先将该应答报文 TCP 首部的 ACK标志置为 1, 其次 【确认应答号】字段填入 server_isn+1,最后把报文发送给 服务端,这次的报文可以携带 客户到服务器的数据,之后客户端处于一个 ESTABLISHED 状态。

从上面过程可看出: 前两次握手是不可以携带数据的,第三次握手是可以携带数据的。

一旦完成三次握手,双方都处于一个 ESTABLISHED 状态,此时连接就已经建立完成了,客户端和服务器端就可以相互发送数据了。

如何在Linux下查看 TCP状态?

TCP的 连接状态查看,在linux可以通过 netstat-napt 命令查看。
在这里插入图片描述

为什么是三次握手、而不是两次、四次?

在前面我们得知什么是TCP连接:

  • 用于保证数据报文的可靠性 和 流量状态维护的某种状态信息,这些信息的组合,包括Socket、序列号和窗口大小 称为连接。

重要的是:为什么三次握手才可以初始化 Socket、序列号和窗口大小并建立TCP连接?
三个方面分析:

  • 三次握手才可以阻止历史重复连接的初始化(主要原因)
  • 三次握手才可以同步 双方的初始化序列号
  • 三次握手才可以避免资源浪费

原因一:避免历史重复连接

  • 防止旧的重复连接初始化造成混乱。(主要原因

网络环境是错综复杂的,往往并不是我们所期望的那样,先发送的数据包,就先到达目标主机。而是可能因为网络拥堵等乱七八糟的原因,会使得旧的数据包,先到达目标主机,那么在这种情况下,TCP三次握手是如何避免的呢?
在这里插入图片描述
客户端连续多次发送SYN 建立连接的报文,在网络拥堵情况下:

  • 一个【旧的 SYN 报文】 比 【新的 SYN报文】早达到了 服务器端
  • 那么此时 服务器就会 回一个 SYN + ACK 报文给客户端
  • 客户端收到后,就会根据自己的上下文,判断这是一个 历史连接(序列号过期或者超时),那么客户端就会发送 RST 报文给服务器端,表示终止这一次连接。

如果是两次握手,就不能判断当前连接是否是历史连接,三次握手则可以在 客户端(发送方)在发送第三次报文的时候,客户端有足够的上下文来判断 当前连接是不是 历史连接:

  • 如果是历史连接(序列号过期或者超时),则第三次握手发送的是报文是 RST报文,以此终止历史连接。
  • 如果不是历史连接,则第三次发送的报文是 ACK 报文,通信双方就会成功建立连接。

原因二:同步双方的初始化序列号

TCP协议的通信双方,都必须维护一个【序列号】,序列号是可靠传输的一个关键因素,它的作用是:

  • 接收方可以去除重复的数据
  • 接收方可以根据接收的数据序列号按序接收
  • 可以标识发送出去的数据包中,哪些是已经被对方接收到的

当客户端发送携带【初始序列号】的 SYN 报文给服务器,服务器回应一个 ACK 报文,表示客户端的SYN报文已经被服务端成功接收。
当服务器发送【初始序列号】给客户端的时候,依然也要得到客户端的确认应答。
一来一回,才能确保双方的初始序列号能被可靠的同步
在这里插入图片描述
如图:四次握手也可以,但第二步和第三步其实可以优化成一步,所以成了【三次握手】

二次握手只保证了一方的初始序列号能够被对方成功接收,而不能保证双方的初始化序列号都能够被确认接收。

原因三:避免资源浪费

如果只有【两次握手】,当客户端的【SYN】请求连接在 网络中堵塞,客户端没有接收到服务器的【ACK】报文,那么就会重新发送【SYN】,由于没有第三次握手,服务器不清楚客户端到底是否接收到自己发送的建立连接的【ACK】确认信号,所以每收到一个【SYN】就只能先建立一个连接,这会造成什么情况呢?

如果客户端的【SYN】在网络中堵塞了,重复发送多次【SYN】报文,那么服务器在收到请求后就会建立多个冗余的无效连接,造成不必要的资源浪费。在这里插入图片描述
即两次握手会造成消息滞留情况下,服务器重复接收无用的连接请求【SYN】报文,而造成重复分配资源。

小结:
TCP建立连接时,通过三次握手 能防止历史连接的建立、能减少双方不必要的资源开销、能帮助双方同步初始化序列号。 序列号能保证数据包的不重复,不丢失和按序传输。

不使用【两次握手】 和 【四次握手】的原因:

  • 【两次握手】: 无法防止历史连接的建立,会造成双方发资源的浪费,也无法可靠的同步双方序列号。
  • 【四次握手】:三次握手就已经是理论上的最少可靠建立连接,所以不需要使用更多的通信次数。

三次握手中,服务器一直接收不到客户端的ACK会发生什么?

服务器会给每个待完成的半连接都设置一个定时器,如果超过时间还未收到客户端的ACK消息,则重新发送一次ACK+SYN 消息给客户端,直到重试超过一定次数后才会放弃,这个时候,服务器虚分配内核资源来维护半连接。

为什么客户端 和 服务器端的 初始序列号 ISN 是不一样的?

因为网络中的报文会延迟,会复制重发,也有可能丢失,这样会造成的不同连接之间产生互相影响,所以为了避免互相影响,客户端和服务器端的初始化序列号是随机切不同的。

初始序列号是如何随机产生的?

RFC1948 中 提出了一个初始化序列号的 ISN随机生成算法。

ISN = M + F(localhost,localport,remotehost,remoteport)

  • M:是一个计时器,这个计时器每个 4 毫秒加 1
  • F: 是一个hash 算法,根据源IP、目的IP、源端口、目的端口生成一个随机值。要保证hash算法不能轻易被外部推算得出,用md5算法会是一个好的选择。

既然IP层会分片,那为什么TCP还需要MSS呢?

我们先来认识一下:MTU 和 MSS

在这里插入图片描述

  • MTU: 一个网络包的最大长度,以太网中一般为 1500 字节;
  • MSS: 除去 IP 和 TCP 头部之后,一个网络包能容忍的 TCP数据的 最大长度

如果TCP 的整个报文(头部 + 数据)交给IP层 进行分层,会出现什么异常呢?

当IP层有一个超过 【MTU】大小的数据(TCP头部+TCP数据)要发送,那么IP层就要进行分层,把数据分成若干片,保证每一片分片都小于【MTU】。把一份IP数据报进行分片之后,由目标主机的 IP层来进行重新组装之后,再交给上一层TCP传输层。

如此看起来很棒,但是是存在隐患的,如果在此过程中,有一个IP分片丢失,那整个IP报文的所有分片都得重传。

因为IP层 本身是没有超时重传机制,它由传输层的TCP来负责 超时和重传。

当接收方发现TCP报文(头部+数据)的某一片丢失后,就不会发送 响应ACK 给对方,那么发送方的 TCP 在超时后,就会重发【整个TCP报文(头部+数据)】。

因此,可以得知由 IP层进行分片传输,是非常没有效率的。

所以,为了达到最佳的传输效率,TCP协议在建立连接时通常要协商双方的MSS值,当TCP 层发现数据超过 MSS 时,就会先进行分片,当然由它形成的 IP包的长度也就不会大于 MTU,自然也就不会IP分片了。

经过TCP 分片后,如果一个TCP分片丢失后, 进行重发也是以 MSS 为单位重发,而不是重发所有的 数据分片。大大增加了重传的效率。

三次握手哪个阶段会出现异常?

第二个阶段会出现异常,如果服务器相应的端口没有打开,会回复RST报文,此次握手失败。此外,listen创建的监听队列到达上限,也可能失败。

什么是 SYN攻击?如何避免SYN攻击?

  • SYN攻击

SYN攻击 就是攻击客户端。攻击者伪造大量的虚假IP地址,不断的向服务器发送 SYN报文,服务器每接到一个SYN报文,就会回复确认包(ACK+SYN)并等待客户端的确认回应,但是因为那些IP地址都是虚假的,不存在的,无法得到未知IP地址主机的ACK应答,当不断重发乃至超时后,这些伪造的SYN包被长时间的占据未连接队列,正常的SYN请求被丢弃,服务器资源耗尽,使得服务器不能为正常用户提供服务,严重这甚至引起网络堵塞或者系统瘫痪。

避免SYN攻击方式一:

通过修改linux内核参数,控制队列的大小,和当队列满时应做什么处理

  • 当网卡接收网络包的速度大于内核处理的速度时,会有一个队列保存这些数据包。控制该队列的最大值如以下参数:
net.core.netdev_max_backlog
  • SYN_RCVD 状态连接的最大个数:
net.ipv4.tcp_max_syn_backlog
  • 超出处理能力时,对新的SYN直接回 RST,丢弃连接:
net.ipv4.tcp_abort_on_overflow

还有:

  • SYNAttackProtect 保护机制
  • SYN cookies 技术
  • 增加最大半连接 和 缩短 超时时间

滑动窗口是什么? 为什么要滑动窗口?

在确认应答机制中,对于每一个发送的数据,服务器都会回应一个 ACK 确认应答,等收到ACK后,再次发送下一个数据。这样虽然保证了可靠性,但会导致性能很差,尤其是在数据往返时间很长的时候。
那么我们如何改变优化呢————滑动窗口

所谓的流量控制 ——就是让发送方的发送速率不要太快,让接受方来得及接收。
利用滑动窗口机制可以很方便的控制对发送方的流量控制。

窗口类型

  • 发送窗口: 发送的窗口上限值= Min ( rwnd , cwnd )
  • 拥塞窗口: 发送端的缓冲区大小
  • 接收窗口: 接收端的 缓冲区大小(接收方将此窗口值放在TCP报文首部中的窗口字段,传送给发送端)

如下图,在此次发送的数据中设置滑动窗口,窗口大小设置为4000:在这里插入图片描述
然后:
在这里插入图片描述
窗口大小为4000,也就是说1001到5000的数据,可以一次性发送,而不用等待ACK响应。当发送方发送完4000 大小的数据后,此时等待,等到ACK响应中 确认接收到 1001-2000的数据时,窗口右移。
在这里插入图片描述
依次类推,滑动窗口中:

  • 窗口左侧都是已经收到ACK应答的数据
  • 窗口中都是已经发送,但还未收到ACK确认的数据
  • 窗口右侧是还未发送的数据

滑动窗口怎么处理丢包问题

情况一:假设是发送端发送的数据丢包:
在这里插入图片描述
这里1001-2000 的数据包丢失了,但是发送端并不知道,继续发送,直到连续三次接收到服务器发来的 ACK确认(TCP协议规定,收到三条重复的ACK响应就触发重传),于是重新发送1001-2000的数据,重传完毕后,等待接收端的响应。
此时接收端的ACK响应中下一条为 6001的数据报文,也就是说2000-6000 的数据都接收到了,那么这段报文就放在接收端的接收缓冲区中。
窗口继续移动,发送端继续发送。

收到三条重复的ACK响应,即重传响应所要求的数据报文的机制就是——快重传机制

情况二:接收端的ACK响应丢包

就算连续三条的ACK响应都丢失,那么第四条ACK响应成功到达,就不会影响发送端的发送。

因为此条ACK确认的是 4001的数据包,说明前面的数据都成功接收到了。
所以、接收端的ACK响应丢包不会对发送端的发送造成大影响,后续的ACK会解决好之前的问题。

什么是拥塞控制?意义?

拥塞控制——考察当前的网络情况,动态的调整窗口大小

  • 没有发生堵塞——窗口增大
  • 发生堵塞——窗口减小
    如此往复往复,最终应该接近于接收端的窗口大小。

意义:
提高网络利用率,降低丢包率,并保证网络资源对每条数据流的公平性。

拥塞控制包括什么?

拥塞控制包括四部分:

  • 慢启动 和 拥塞避免
    刚开始开始信息时,由于不知道网络中的具体情况,为避免大量信息造成的网络拥塞情况,此时的拥塞窗口以最小值进行数据发送,并设置门限值是慢启动算法 和 拥塞避免算法的分割点。

慢启动是指:以最小的拥塞窗口按照指数方式增长,达到门限值后,就进入到拥塞避免阶段。

拥塞避免阶段,是以拥塞避免算法,以线性方式增大拥塞窗口。(递增间隔为一个往返时间RTT)

在上述过程中,无论是窗口大小指数递增还是线性递增,当发送拥塞现象,则门限值更新为当前窗口大小的一半,拥塞窗口大小变为最小值,重复上述递增过程。
在这里插入图片描述

  • 快速重传 和 快速恢复

快速重传:当发送端连续收到三个重复的ack时,表示该数据段已经丢失,需要重发

假设发送端发送的报文段分别为:M1、M2、M3、M4、M5、M6…
当接收端接收了M1,M2后,M3数据包丢失,接着对于收到的M4,M5,M6都不做任何处理,而是每次收到一个数据报就对M2重复确认,发送方收到三次连续的ACK响应,就知道M2数据包丢失,接着立即重传M2,不用等待超时时间到达。当快速重传之后,立即进入快速恢复阶段。

快速恢复:将慢启动门限值设为原来的一半,然后将拥塞窗口设置为现在的慢启动门限值,不再执行慢启动算法,而是直接进入拥塞避免阶段,使用拥塞避免算法,使得 窗口呈 线性方式递增。

【不连续的数据段会严重影响TCP的传输效率。而快速修复这种不连续,会释放掉占用的空间,加快发送方的传输效率】

03 TCP 连接断开

TCP 四次挥手过程 和状态变迁

关闭连接的过程叫做 四次挥手,由于TCP的全双工的通讯,所以在每个方向上都必须单独进行关闭,当一方完成它的数据发送任务后就会发送一个【FIN】报文来终止这个方向上的连接,收到一个【FIN】意味着 这一方向上没有数据流动。
在这里插入图片描述

  • 客户单打算关闭连接,此时会发送一个 TCP首部 FIN 标志位被 置为1 的【FIN】 报文,之后客户端就进入了一个 FIN_WAIT_1 状态。
  • 服务器在收到这个 【FIN】报文后,就向客户端发送一个 【ACK】应答报文,接着服务器进入 CLOSED_WAIT 状态。
  • 客户端在收到 服务器发来的 【ACK】应答报文后,就进入一个 FIN_WAIT_2 状态。
  • 等待服务器端 数据处理完成后,也向客户端发一个 【FIN】报文,然后服务器进入 LAST_ACK 状态。
  • 客户端在接收到 服务器发来的这个 【FIN】报文后,回一个【ACK】应答报文,自身进入 TIME_WAIT 状态。
  • 服务器端 收到了 【ACK】应答报文后,就进入了 CLOSE 状态,至此服务器已经完成连接的关闭。
  • 客户端在等待了 2MSL 时间后, 自动进入 CLOSE 状态,至此客户端也完成连接的关闭。

注意:
每一个方向上都有一个 【FIN】和一个【ACK】

主动关闭连接的,才有 TIME_WAIT 状态

为什么挥手需要四次?

  • 关闭连接时,客户端向服务器端发送【FIN】报文,仅仅是表示客户端不再发送数据了,但是还能接收数据。
  • 服务器在收到客户端发来的【FIN】后,先回复一个【ACK】确认应答。服务器这边可能还有数据需要处理和发送,等到服务器处理完成,不再发送数据时,才发送一个【FIN】报文给客户端来表示 同意现在关闭连接。

由此,可见服务器端通常需要等待完成数据的发送和处理,所以服务器端的【ACK】和【FIN】一般会分开发送,从此比三次握手导致多了一次。

那可不可以是三次挥手呢?

在某些情况下,三次握手是可以完成挥手的。当本端关闭连接后,恰好也收到 了对方发来的【FIN】报文,此时可以把自己的【FIN】 和 给对端的【ACK】应答报文 一起发送,就变成了三次挥手。

为什么建立连接时三次握手,而关闭是四次挥手呢?

  • 三次握手时: 服务器端可以 将【ACK】和【SYN】合在一起发送
  • 四次挥手时: 服务器端需要把 【ACK】和【FIN】分来来发送

原因:
【FIN】报文是由调用 COLSE 才发送的,而【ACK】是直接由内核发送。所以【ACK】和【SYN】报文在发送的时间上是分开的,不能够同步发送。
但是,三次握手时,发送【SYN】是由内核直接完成的,所以在时间上可以达到一个 同步发送的情况。

为什么需要 TIME_WAIT状态?

time_wait状态是: 主动发送方 在接收到 对方发来的FIN报文 并且将 ACK 报文发出后处于的一个状态。

  • 保证迟来的报文能被识别并丢弃
  • 保证【被动关闭连接的一方】能被正确的关闭,即保证最后的ACK 能让 被动关闭方接受到,从而帮助其关闭连接。

原因一:防止旧连接的数据包
假设time_wait 没有等待时间或者时间过短,被延迟的数据包 抵达后会发生什么呢?

在这里插入图片描述

  • 如上图,黄色框服务端再关闭连接之前 发送的 SEQ= 301 报文,在网络中被延迟了
  • 这时, 有相同端口的TCP 连接被复用后,被 延迟的 SEQ=301 抵达了客户端,那么客户端是可能正常接收这个过期的报文,这就会产生 数据错乱 等严重的问题。

所以,TCP 设计机制——2MSL 时间
等待 2MSL 时间后,足以让两个方向上的数据包都被丢弃,使得原本连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。

原因二:保证连接正确关闭
假设 TIME_WAIT 没有等待时间或者 等待时间过短,连接断开会造成什么问题呢?
在这里插入图片描述

  • 如上图,红色框框内 客户端最后一次的ACK 报文,在网络中丢失了,此时如果没有TIME_WAIT等待或者 等待时间过短,那么客户端就自动进入close 状态了,但是服务器由于没有收到最后 ACK,则一直处于一个 LAST_ACK 状态。
  • 当客户端发起新的 连接请求 SYN 报文后,服务器会发送 RST 报文给客户端,连接的过程就会被终止。

如果TIME_WAIT 等待时间足够长,那么就会遇到两种情况:

  • 服务端正常收到 四次挥手的最后一个 ACK,连接正常关闭。
  • 服务器没有收到最后一个 ACK,那么在等待 2MSL时间后,就会重发 FIN 关闭连接报文 并等待 新的 ACK报文。

为什么等待时间是 2MSL呢?

MSL: 报文最大生存时间
是任何报文在网络中存在的最长时间,超过这个时间报文 就会被丢弃。
科普小知识:
TCP报文 是基于IP协议的,IP头中有一个【TTL】字段,是IP数据报可以经过的最大路由数,每经过一个处理他的路由器,此值就会减一, 当此值为0 时, 数据报就会被丢弃,同时发送 ICMP报文通知源主机。

MSL 与 TTL的区别: MSL的单位是时间,TTL的单位是 经过路由跳数。 MSL 应该是要大于 TTL消耗到为0的时间,以确保报文被自然消亡。

TIME_WAIT 等待 2倍的 MSL: 网络中存在来自发送方的数据包,当这些数据包被接收方处理后,又会向对方发送响应,所以一来一回 需要等待2倍的时间

比如: 如果被动关闭方,没有收到断开连接的最后的那个ACK报文,就会触发超时重传,重发FIN报文,另一方接受到FIN后,会重发ACK 给被动关闭方,一来一去,2MSL。

2MSL的时间 是从客户端收到 FIN 后发送ACK开始计时的, 如果在TIME_WAIT 时间内,因为客户端的ACK 没有传输到 服务器,客户端又收到了 服务器重发的 FIN 报文,那么 2MSL时间将 重新计时。

linux系统下,2MSL 默认为 60 秒。一个为 30秒。

TIME_WAIT 过多有什么危害?

  • 内存资源占用
  • 对端口资源的占用,一个TCP连接至少消耗一个本地端口

端口资源是有限的,一般可以开启的端口为 32768~61000。如果TIME_WAIT 状态 过多,占满了所有的端口资源,则会导致无法创建新连接。

那如何优化TIME_WAIT 呢?

  • 打开 net.ipv4.tcp_tw_reuse 和 tcp_timetamps 选项;(引入时间戳)

  • net.ipv4.tcp_max_tw_buckets

  • 这个值被默认为18000, 当系统中处于TIME_WAIT 的连接一旦超过这个值,系统就重置所有TIME_WAIT 状态。(过于暴力,弊大于利,不推荐)

  • 程序中使用 SO_LONGER, 应用强制性使用 RST 关闭。(危险)

什么是 CLOSE_WAIT 状态? 有什么影响??

CLOSE_WAIT是处于被动关闭的一端在接收到 对端关闭请求(FIN报文段)并且将 ACK 发送出去后所处于的状态。 此状态表示:收到了对端关闭的请求,但是半端还没有完成工作,还未关闭。
影响:
服务器长时间处于 CLOSE_WAIT状态, 也就是意味着 服务器的代码没有调用 close,并没有发送【FIN】结束报文段,也就是说 系统分配的文件描述符并没有关闭并归还。 大量的 CLOSE_WAIT 存在的话,会造成资源的泄漏,到最后可能就没有可分配的文件描述符了,导致一些客户端无法连接。

如果已经建立了连接,客户端突然出现故障了怎么办?

TCP 有一个机制是 保活机制,定一个保活计时器

定义一个时间段,这个时间段内,如果没有任何的连接相关的活动,TCP保活机制就开始起作用。
正常情况下,服务器每收到一次客户端的请求后都会复位这个计时器,时间间隔为2小时。
但是如果两小时内,没有收到客户端的任何数据,服务器就每隔一段时间会发送一个探测报文段,该探测报文段内的数据很少,每隔75分钟就发一次,若连发十个10探测报文仍没有任何回应,那么就判定客户端出现故障,接着就关闭连接。
如果开启了TCP保活,需要考虑以下几种情况:

  • 对端程序是正常工作的: TCP保活的探测报文发送给对端后,对端正常响应,这时,TCP保活时间会被重置,等待下一个TCP保活时间的到来。
  • 对端程序崩溃并重启: 对端可以响应,但是由于没有该链接的有效信息,会产生一个RST报文,这样很快就会 发现TCP 连接已经被重启。
  • 对端程序崩溃: 没有响应,连续几次都没有,达到保活探测次数后,TCP会报告该TCP连接已经死亡。

03 Socket 编程

针对TCP应该如何 Socket编程?

在这里插入图片描述

  • 服务端 和客户端 初始化 【socket】,得到文件描述符
  • 服务端调用【bind】,将绑定在 IP 地址 和 端口
  • 服务端调用【listen】,进行监听
  • 服务端调用【accept】,等待客户端连接
  • 客户端调用【connect】,向服务器端的 地址 和 端口 发起连接请求
  • 服务端 【accept】,返回用于传输的 【socket】文件描述符
  • 客户端调用【write】写入数据;服务器端调用【read】读取数据;
  • 客户端断开连接时,会调用【close】,那么服务端【read】读取完数据后,就会读取到【EOF】,待处理完数据后,服务器端调用【close】,表示连接关闭。

注意:
服务器端调用【accept】时,连接成功后会返回一个已完成连接的【socket】,后续用来传输数据。

所以、用来监听的【socket】和真正用来传输数据的【socket】是 两个 socket,一个叫做【监听socket】,另一个叫做【已完成连接socket】。

成功建立连接之后,双方开始通过【read】和【write】函数来读写数据,就像往一个文件流里面来写东西一样。

listen中的 第二个参数是什么?(面试会重点问到)

linux内核会维护两个队列:

  • 未完成队列(SYN队列):接收到一个 SYN请求后,处于一个SYN_RCVD状态。
  • 已完成队列(Accept队列): 已完成三次握手过程,处于ESTABLISHED状态。
int listen(int socketfd,int backlog)
  • 参数一: socketfd 为 socketfd 文件描述符
  • 参数二: backlog

早期: 在linux内核中,backlog 为 SYN 队列大小,未完成的队列大小。
linux 内核2.2后: backlog 变成Accept,也就是完成建立连接的队列长度。
在一些Unix系统中,还会被表示:两个队列长度之和。

accept 发生在 三次握手的哪一步?

在这里插入图片描述

  • 客户端协议 在收到ACK后,使得应用程序从【connect】调用返回,表示客户端到服务器端的单向连接已经建立成功。
  • 当客户端的ACK应答包到达服务器端后,服务器端协议 使得【accept】堵塞调用返回
    由此可见:
    客户端 connect成功返回后是在第二次握手后
    服务器端accept 成功返回在 第三次握手后
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值