TCP三次握手详解,滑动窗口,拥塞窗口,网络包路由过程,全连接队列,半连接队列

众所周知,网络分层有传统的OSI七层模型和后来的基于TCP/IP的四层模型:
在这里插入图片描述

那么在一次网络的传输过程中具体的流程是怎么样的,我们先从一个数据包的传输说起(以TCP为例):

  1. TCP协议根据上层应用提供的信息生成TCP报文
    TCP包头

  2. TCP报文在交由下面的IP层(网络层)进行处理,委托IP模块将TCP报文封装成网络包发送给对应的接收端:
    IP协议里需要有源IP和目标IP,这个在整个的传输过程中都是不变的
    对于源IP来说,就是发送者的IP,但是对于一台服务器来说,可能存在多个网卡,即存在多个IP,那么应该用哪一个呢?
    这时候就需要用到路由表,在linux中可以通过route -n来获取,然后会将目标IP地址和路由表中每条记录的子网
    掩码进行与运算,得到与目标IP在同一个网段的路由(从这里看,通过与子网掩码进行与运算,能够判断IP是否属于同一个网段
    这样我们就能够知道从哪个网卡将我们的数据发送出去
    这样我们就将IP报文生成
    IP包头

  3. 生成了IP报文之后接下来还需要在IP报文中增加MAC地址信息,进行实际的两端出书,那么如何获取呢 ?
    发送方的MAC地址比较简单,在上面的步骤我们确定好了哪块网卡发送数据实际上就能够获取到对应网卡的MAC地址
    但是对于原目标MAC地址则比较麻烦,只要我们填写上MAC地址,那么以太网就会将数据发送到对应的MAC地址的机器上
    我们需要确定需要发送给哪个MAC地址?根据上一步的了解,我们通过路由表知道需要将数据发往哪里,通过和路由表
    中的条目匹配,我们知道发送给路由表中哪个IP,那么我们如何知道这个IP对应的MAC地址呢?
    这个时候通过ARP协议能够帮我们知道一个IP地址对应的MAC地址
    ARP协议会在以太网中通过广播形式对以太网中所有设备发出请求询问,这个IP是谁的,收到ARP的请求后,设备判断
    这个IP和自己是否在同一个网段,如果在同一个网段,则给出响应并返回自己的MAC地址
    通过ARP,我们能够获取到目标MAC地址,
    mac包头

  4. 上述IP网络包生产之后接下来需要通过网卡将数据从本机发送出去,网卡会将IP网络包的二进制数据转换成
    电信号。网卡从IP模块接收到数据之后会将数据复制到网卡的缓存区中,接着会在其开头加上报头和起始 帧分界符,在末尾加上用于检测错误的帧校验序列FCS。

  5. 交换机。 交换机的作用是将网络包原样转发到目标设备,交换机工作在MAC层,是二层网络设备,和网卡不同,交换机的端口不具有 MAC 地址
    交换机将包存入缓冲区后,接下来需要查询一下这个包的接收方 MAC 地址是否已经在 MAC 地址表中有记录 了。
    交换机的 MAC 地址表主要包含两个信息: 一个是设备的 MAC 地址,另一个是该设备连接在交换机的哪个端口上 。
    接下来交换机就会将数据原样发送到对应设备的对应端口上。
    如果地址表中找不到对应的MAC地址,那么可能是因为具有该地址的设备还没有向交换机发送过包,
    或者这 个设备一段时间没有工作导致地址被从地址表中删除了,交换机无法判断应该把包转发到哪个端口,只能将包转发到除了源端口之外的所有端口
    上,无论该设备连接在哪个端口上都能收到这个包。
    交换机会将接收到的电信号转换为数字信号,然后判断FCS是否正确,如果没问题,则通过上述逻辑,发送到对应的地址上

  6. 路由器。 经过交换机之后,数据包现在来到了路由器这里,路由器是基于IP协议涉及的,是三层网络设备,路由器每个端口 都具有MAC地址和IP地址,而交换机是基于以太网设计的,交换机的端口不具有MAC地址
    路由器接收到数据后会将电信号转换为数字信号,然后会丢掉包中的MAC头部MAC 头部的作用就是将包送达路由器,其中的接收方 MAC 地址就是路由器端口的 MAC 地址。因此, 当包到达路由器之后,MAC 头部的任务就完成了,于是 MAC 头部就会被丢弃。
    然后查询路由器中的路由地址进行发送操作,首先,我们需要根据路由表的网关列判断对方的地址。
    如果网关是一个 IP 地址,则这个IP 地址就是我们要转发到的目标地址,还未抵达终点,还需继续 需要路由器转发。
    如果网关为空,则 IP 头部中的接收方 IP 地址就是要转发到的目标地址,也是就终于找到 IP 包头 里的目标地址了,说明已抵达终点。
    然后通过ARP协议获取到需要发送的MAC地址,然后将包中的目标MAC地址设置为获取到的MAC地址

网络包完成后,接下来会将其转换成电信号并通过端口发送出去。这一步的工作过程和计算机也是相同
的。
发送出去的网络包会通过交换机到达下一个路由器。由于接收方 MAC 地址就是下一个路由器的地址, 所以交换机会根据这一地址将包传输到下一个路由器。
接下来,下一个路由器会将包转发给再下一个路由器,经过层层转发之后,网络包就到达了最终的目的
地。

在网络包传输的过程中,源 IP 和目标 IP 始终是不会变的,一直变化的是 MAC 地 址,因为需要 MAC 地址在以太网内进行两个设备之间的包传输

这里介绍几个概念:

MTU(Maximum Transmission Unit,MTU),最大传输单元

以太网和802.3对数据帧的长度都有一个限制,其最大值分别是1500和1492个字节。链路层的这个特性称作MTU。如果IP层有数据包需要传送,但是这个数据包的大小比MTU还大,则在IP层会进行分片传输,让传输的数据包小于MTU,然后在目的端的IP层进行组装。
在IP层,即使丢失一个分片数据都要重传整个数据包,因为IP层没有超时重传机制,需要由上层TCP层来实现

MSS(Maxitum Segment Size)最大分段大小

MSS是TCP每次传送数据段的最大大小,TCP在进行数据包的传输的时候,会按照MSS来切分数据包,一般MSS<MTU,这样到达IP层的时候一般不需要IP层在进行切分。

在这里插入图片描述

TCP三次握手连接

在这里插入图片描述

由于TCP是面向连接的,因此在使用TCP之前必须先建立TCP的连接,而TCP建立连接则是通过三次握手来建立连接
在这里插入图片描述

  • 一开始,客户端和服务端的状态都是CLOSED,服务端开启TCP服务,绑定端口并监听该端口,处于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 状态。

三次握手过程中第三次是可以携带数据的

一旦完成三次握手,双方都处于ESTABLISHED状态,此时连接已经建立完成,客户端和服务端就可以相关发送数据
TCP建立需要三次握手的主要原因如下:

  1. 阻止重复历史连接的初始化(主要原因)
  2. 同步双方的初始序列号
  3. 避免资浪费
  • 阻止重复历史连接。我们知道网络中的数据传输是不固定的,有的包先发但是可能会比后发的数据包晚到,有的包后发,但是会比先发的数据包早到。三次握手条件下,在网络不好的情况下,旧的SYN报文比新的SYN报文先到,此时服务端会返回一个SYN+ACK报文给客户端,客户端收到报文后根据上下文判断这是一个历史连接(通过序列号或者超时),客户端会发送RST给服务端终止这次连接,如果是两次握手,则无法判断

  • 同步双方初始序列号。 TCP传输过程中,客户端和服务端都必须维护一个自身的序列号,序列号是TCP可靠传输的一个保证,序列号的作用如下:

  1. 接收方可以根据序列号去除重复数据
  2. 接收方可以根据序列号顺序接收
  3. 发送方可以根据返回的ACK中判断哪些数据是对方已经收到的
  • 避免资源浪费。如果只有两次握手,那么客户端发送SYN后,服务端返回ACK,但是由于网络堵塞,客户端没有收到,这时候客户端只能再发SYN,而服务端不知道客户端是否收到自己发送的ACK,只能没收到一个SYN就建立一个连接,这会造成服务端有多个连接,造成冗余

TCP四次挥手断开连接

在这里插入图片描述

  1. 客户端准备关闭连接,发送FIN报文,将TCP首部FIN标志位设置为1,然后客户端进入FIN_WAIT_1状态
  2. 服务端收到客户端的FIN报文之后,向客户端返回ACK报文,服务端进入CLOSED_WAIT状态
  3. 客户端收到服务端的ACK报文之后进入FIN_WAIT_2状态
  4. 服务端将所有的数据发送完成之后,也向客户端发送FIN报文,服务端进入LAST_ACK状态
  5. 客户端收到服务端的FIN报文之后,返回ACK给服务端,客户端进入TIME_WAIT状态
  6. 服务端收到ACK报文后,进入CLOSED状态,到这里服务端完成了连接的关闭
  7. 客户端经过2MSL时间之后,进入CLOSED状态,到这里客户端完成了连接的关闭

TCP可靠性传输

TCP在建立连接的时候,有一个重要的功能就是同步双方的序列号,而TCP称为可靠性传输,主要通过如下几种手段:

  • 消息序列机制
  • 数据接收确认机制
  • 超时重传机制
  • 选择确认机制

消息序列机制

TCP会对传输的每个消息以字节为单位进行编号,确认每个字节的数据都能有序传送和接收

数据接收确认机制

在TCP中当目标主机接收到数据后,会返回一个确认应答消息给到源主机,表示已经收到消息,但是大家需要注意的是,网络环境错综复杂是不稳定的,如果在传输的过程中,数据丢失了,应该怎么处理?
为此TCP针对数据包丢失的情况,采用了重传机制来解决,常见的重传机制有如下几种:

  • 超时重传
  • 快速重传
  • SACK
  • D-SACK
超时重传

超时重传的原理就是源主机发送一个数据包之后会设定一个定时器,到达指定的时间(RTT)后没有收到目标主机返回的确认应该报文ACK之后,重新发送该数据。
那么这个时间多长呢?
RTT (Round-Trip Time) 表示一个数据包在网络中从一端到另一端需要用的时间超时重传时间是以 RTO (Retransmission Timeout )来表示,RTO是通过算法计算而来,是一个动态变化的值,具体大家可以百度看下Linux采用的算法

快速重传

快速重传不是通过时间来判断,而是通过数据,通过一幅图大家就明白了:
在这里插入图片描述

  1. 源主机发送报文Seq=1的时候,目标主机返回ACK=2
  2. 源主机发送Seq=2,但是由于未知原因,该报文丢失了,目标主机未收到
  3. 源主机发送Seq=3,目标主机收到,但是期望的是Seq=2,目标主机还是返回ACK=2
  4. 源主机发送Seq=4,目标主机收到,还是返回ACK=2
  5. 源主机发送Seq=5,目标主机收到,还是返回ACK=2
  6. 源主机收到了三个ACK=2,知道目标主机Seq=2尚未收到,在定时器过期之前,重传Seq=2
  7. 目标主机收到了Seq=2,也收到了Seq=3,Seq=4,Seq=5,这时候目标主机返回ACK=6

但是上述也有问题,就是这个重传是重传Seq=2还是重传Seq=2,Seq=3,Seq=4,Seq=5呢?因为这时候源主机并不知道ACK=2到底是哪个数据包的确认信息,因此有了SACK算法
在发送数据的时候,会在TCP头部选项里增加SACK信息,这样在给源主机返回ACK信息时,源主机通过SACK信息,就能够知道哪些数据接收到了,哪些数据没有接收到,然后只重传丢失部分的数据

Duplicate SACK又称 D-SACK主要使用了SACK来告诉源主机有哪些数据被重复发送了。

TCP滑动窗口

我们知道,在网络中都是一个网络包一个网络包的传输,如果每次发送一个数据包就返回一个ACK应答,这种效率会特别低。TCP中引入了窗口的概念,在一个窗口中,发送端可以不等接收端相应继续发送数据,知道发送窗口大小为0,必须等待接收端相应才能继续发送。TCP中窗口的实现实际上是开辟了一段缓存空间,将发送的数据缓存在这,如果收到确认应答则会将该数据从缓存中移除。TCP的滑动窗口是以字节为单位的

接收端在返回相应给发送端的时候会告诉发送端自己还有多大的缓冲区能够接收数据,发送端可以根据接收端的处理能力来发送数据。
比如现在发手段A收到接收端(B)返回一个确认信息:确认号为101(这个是下次发送的起始序号),窗口大小为500(这是滑动窗口可发送大小为500),这时候表明,A中101序号之前的数据B都已经收到,这时候A中101序号之前的数据可以从缓存中移除,同时A可以发送101到600序号之间的数据而不用等待B的确认。这时候A发送了从101-200序号的数据,并未收到B的确认,这时候滑动窗口大小为400,如果B返回确认号151,窗口大小还是500,表示B收到了101-150之间的数据,后面的数据未收到,这时候A的窗口可以继续增加50.

TCP流量控制,拥塞控制

TCP流量控制: 发送方不能无脑的往接收方发送数据,需要考虑接收方是否能够处理过来,如果一直发送但是接收方处理不过来,则会导致重发,导致网络流量的无端浪费,基于此,TCP提供了流量控制,让发送方根据接收方的接收能力来控制发送的数据量,流量控制通过上面的滑动窗口实现。

拥塞控制 在网络出现拥堵时,如果发送方继续发送大量数据,会导致数据的延时、丢失等,这时候TCP又会重传,而重传会加重网络的负担,导致更大的延时、丢包,如果一直进行下去则会不断恶性循环,因此TCP中设置了拥塞控制,避免发送发的数据填满整个网络空间。实际上除了上面说的滑动窗口外,还有一个拥塞窗口(cwnd),拥塞控制提供了几种算法:

  • 慢开始
  • 拥塞避免
  • 快重传
  • 快恢复

一般发送方的窗口的上限值为:Min(接收窗口,拥塞窗口)

TCP全连接队列、半连接队列

在这里插入图片描述

当我们在进行TCP编程的时候,服务端会开启通过bind -> listen来监听对应端口的连接,当客户端第一次请求连接发送SYN信号后,服务端收到该请求且验证请求有效,则会将这个连接放入到半连接队列
当三次握手成功之后,将半连接队列中连接放入到全连接队列中。
服务端程序调用read系统命令,从全连接队列中取走已经建立好的连接并获得这些连接的句柄,后续的发送接收消息,服务端通过持有的连接的句柄进行操作。
不管是全连接队列还是半连接队队列都有最大的长度限制,超过限制,TCP会直接丢弃或者返回RST

参数名称 说明
tcp_syncookies 当TCP半连接队列SYN队列满之后,开启tcp_syncookies,连接校验通过会直接进入到accept队列
tcp_abort_on_overflow 当TCP全连接队列满之后的策略,0:如果全连接队列满了,服务器端会丢掉客户单发送的ACK,1服务端会发送一个RST给客户端,表示废弃这次连接
TCP全连接队列大小设置 取值是 somaxconnbacklog之间的最小值,cat /proc/sys/net/core/somaxconn,backlog在TCP程序中指定
tcp_syn_retries 当客户端发送SYN包之后等待服务端返回SYN_ACK,如果客户端很长时间没有收到SYN+ACK报文,会重发SYN包,重发的次数由该参数控制,cat /proc/sys/net/ipv4/tcp_syn_retries
tcp_fastopn TCP Fastopen,可以减少三次握手的时延,当第一次握手成功后,后续再次握手客户端会携带有第一次握手中服务端返回的cookie,后续不用在进行三次握手,直接连接,0 关闭 1 作为客户端使用 Fast Open 功能 2 作为服务端使用 Fast Open 功能 3 无论作为客户端还是服务器,都可以使用 Fast Open 功能 。 cat /proc/sys/net/ipv4/tcp_fastopen
tcp_orphan_retries TCP四次挥手中,当主动方第一次发哦送FIN报文后出与FIN_WAIT1状态,如果没有收到ACK时,会重发FIN报文,重发次数由该参数控制,默认0表示8次,cat /proc/sys/net/ipv4/tcp_orphan_retries
tcp_max_orphans 系统中孤儿连接的最大数量,当连接一直处于FIN_WAIT1状态的时候,为孤儿连接,如果大于改参数,新增的孤儿连接不会走四次挥手,直接发送RST,cat /proc/sys/net/ipv4/tcp_max_orphans
tcp_fin_timeout 控制连接处于FIN_WAIT2的时长,默认是60S,cat /proc/sys/net/ipv4/tcp_fin_timeout
tcp_max_tw_buckets 当处于TIME_WAIT状态的连接超过该参数时,新关闭的连接不在经历TIME_WAIT,直接关闭,cat /proc/sys/net/ipv4/tcp_max_tw_buckets
tcp_tw_reuse,tcp_timestamps 仅适用于客户端,复用处于TIME_WAIT的连接,前提是打开tcp_timestamps
tcp_wmem TCP发送缓冲区大小,单位是字节echo "4096 16384 4194304" > /proc/sys/net/ipv4/tcp_wmem ,第一个表示动态范围的最小值,4K,二个是初始默认值,86K,第三个是动态范围的最大值,4M
tcp_rmem TCP接收缓冲区大小,单位是字节echo "4096 87380 4194304" > /proc/sys/net/ipv4/tcp_rmem 第一个表示动态范围的最小值,4K,二个是初始默认值,86K,第三个是动态范围的最大值,4M
tcp_moderate_rcvbuf 开启接收缓冲区自动调节,发送缓冲区是自动调节的echo 1 > /proc/sys/net/ipv4/tcp_moderate_rcvbuf
tcp_mem TCP内存大小,单位是页,默认一页是4KB,echo "382806 510409 765612" > /proc/sys/net/ipv4/tcp_mem ,,第一个表示TCP内存小于该值时不需要自动调节,在第一个和第二个值之间时,调节接收缓冲区大小,大于第三个值时,不在为TCP分配新内存,此时无法建立新的连接.千万不要在 socket 上直接设置 SO_SNDBUF 或者 SO_RCVBUF,这样会关闭缓冲区的动态调整功能。
tcp_window_scaling 不限制默认TCP窗口64KB大小,开启滑动窗口因子,echo “1” > /proc/sys/net/ipv4/tcp_window_scaling

おすすめ

転載: blog.csdn.net/LeoHan163/article/details/117449698