其他分享
首页 > 其他分享> > TCP、UDP、TCP三次握手与四次挥手、TCP如何保证可靠传输、TCP异常分析、拆包和粘包等

TCP、UDP、TCP三次握手与四次挥手、TCP如何保证可靠传输、TCP异常分析、拆包和粘包等

作者:互联网

4、OSI模型

4.1、OSI七层模型

在这里插入图片描述

在这里插入图片描述

4.2、七层模型功能

​ 物理层:利用传输介质为数据链路层提供物理连接,实现比特流的透明传输,如网线;网卡标准。

​ 数据链路层:接收来自物理层的位流形式的数据,并封装成帧,传送到上一层,定义数据的基本格式,如何传输,如何标识,MAC

​ 网络层:将网络地址翻译成对应的物理地址,并通过路由选择算法为分组通过通信子网选择最适当的路径,如不同设备的数据转发。

​ 传输层:端到端传输数据的基本功能;如 TCP、UDP。

​ 会话层:负责在网络中的两节点之间建立、维持和终止通信;如不同软件数据分发给不同软件。

​ 表示层:处理用户信息的表示问题,数据的编码,压缩和解压缩,数据的加密和解密。

​ 应用层:为用户的应用进程提供网络通信服务以及各种应用软件,包括 Web 应用。

说明

​ 在四层,既传输层数据被称作(Segments);

​ 三层网络层数据被称做(Packages);

​ 二层数据链路层时数据被称为(Frames);

​ 一层物理层时数据被称为比特流(Bits)。

总结

​ 网络七层模型是一个标准,而非实现,网络四层模型是一个实现的应用模型,网络四层模型由七层模型简化合并而来。

4.3、网络模型相关协议

1、应用层协议
FTP :定义了文件传输协议,21 (20传输,21连接)
Telnet:它是一种用于远程登陆的端口,23
SMTP:定义了简单邮件传送协议,服务器开放 ,25
POP3:它是和SMTP对应,POP3用于接收邮件,110
HTTP:超文本传输协议,80
HTTPS:超文本安全协议,443
DNS:用于域名解析服务,53 (服务器传输TCP,客户端查询服务器UDP)
SNMP:简单网络管理协议,161
TFTP(Trival File Transfer Protocal):简单文件传输协议,69
2、传输层协议
TCP:
UDP:
3、网络层协议
ARP:地址解析协议 ,根据IP地址获取物理MAC地址
IP:TCP/IP协议簇中的核心协议,也是TCP/IP的载体,IP提供不可靠的,无连接的数据传送服务
ICMP:网络控制报文协议,确认IP包是否成功达到目标地址,通知IP包丢失的原因(目标不可达、原点抑制、重定向、改变路由、时间戳)
IGMP:组管理协议,让一个物理网络上的所有系统知道主机当前所在的多播组
DHCP:动态主机配置协议,自动的给子网内新增主机结点分配IP地址,避免手动管理IP
OSPF:开放式最短路径优先,
BGP:边界网关协议,用来连接Internet上独立系统的路由选择协议
4、数据链路层协议
ARP :地址解析协议 ,根据IP地址获取物理MAC地址
RARP:反向地址转换协议,根据物理MAC地址获取IP地址
PPP:点对点协议,主要是用来通过拨号或专线方式建立点对点连接发送数据,使其成为各种主

5、TCP

5.1、TCP/UDP基本认识

TCP是面向连接的(一对一)、可靠的、基于字节流的传输层通信协议。

TCP连接:用于保证可靠性和流量控制维护的某些状态信息,包括Socket(IP+port)、序列号(解决乱序)和窗口大小(流量控制

UDP是不可靠、基于报文的、可实现多个连接的传输层通信协议。

5.2、TCP/UDP报头格式

1、TCP报头格式

在这里插入图片描述

​ **Seq序列号:**在建⽴连接时由计算机⽣成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送⼀次数据,就「累加」⼀次该「数据字节数」的⼤⼩。⽤来解决⽹络包乱序问题

​ **Ack确认应答号:**指下⼀次「期望」收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。⽤来解决不丢包的问题

窗口大小:(window size)滑动窗口,用于告知对方(发送方)本方的缓冲还能接收多少字节数据,主要进行流量控制。

校验和:接收端用CRC检验整个报文段有无损坏。

标志位

2、UDP报头格式

在这里插入图片描述

⽬标和源端⼝:主要是告诉 UDP 协议应该把报⽂发给哪个进程。

包⻓度:该字段保存了 UDP ⾸部的⻓度跟数据的⻓度之和。

校验和:校验和是为了提供可靠的 UDP ⾸部和数据⽽设计。

3、IP报头格式

5.3、TCP和UDP区别

1)连接

​ TCP 是⾯向连接的传输层协议,传输数据前先要建⽴连接。

​ UDP 是不需要连接,即刻传输数据。

2)服务对象

​ TCP 是⼀对⼀的两点服务,即⼀条连接只有两个端点。

​ UDP ⽀持⼀对⼀、⼀对多、多对多的交互通信

3)可靠性

​ TCP 是可靠交付数据的,数据可以⽆差错、不丢失、不᯿复、按需到达。

​ UDP 是尽最⼤努⼒交付,不保证可靠交付数据。

4)拥塞控制、流量控制

​ TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。UDP 则没有,即使⽹络⾮常拥堵了,也不会影响 UDP 的发送速率。

5)⾸部开销

​ TCP ⾸部⻓度较⻓,会有⼀定的开销,⾸部在没有使⽤「选项」字段时是 20 个字节,如果使⽤了「选项」字段则会变⻓的。

​ UDP ⾸部只有 8 个字节,并且是固定不变的,开销较⼩。

6)传输⽅式

​ TCP 是流式传输,没有边界,但保证顺序和可靠。

​ UDP 是⼀个包⼀个包的发送,是有边界的,但可能会丢包和乱序。

7)分片不同

​ TCP 的数据⼤⼩如果⼤于 MSS ⼤⼩,则会在传输层进⾏分⽚,⽬标主机收到后,也同样在传输层组装 TCP数据包,如果中途丢失了⼀个分⽚,只需要传输丢失的这个分⽚。

​ UDP 的数据⼤⼩如果⼤于 MTU ⼤⼩,则会在 IP 层进⾏分⽚,⽬标主机收到后,在 IP 层组装完数据,接着再传给传输层,但是如果中途丢了⼀个分⽚,在实现可靠传输的 UDP 时则就需要重传所有的数据包,这样传输效率⾮常差,所以通常 UDP 的报⽂应该⼩于 MTU。

问题一:为什么 UDP 头部没有「⾸部⻓度」字段,⽽ TCP 头部有「⾸部⻓度」字段呢?

​ TCP 有可变⻓的「选项」字段,⽽ UDP 头部⻓度则是不会变化的,⽆需多⼀个字段去记录 UDP 的⾸部⻓度

问题二:为什么 UDP 头部有「包⻓度」字段,⽽ TCP 头部则没有「包⻓度」字段呢?

​ TCP数据长度 = IP总长度 - IP首部长度 - TCP首部长度,因此TCP数据长度是可以计算出来的。虽然UDP头部有包长度字段看起来有点冗余,但是它主要是为了网络硬件设备的设计和处理的方便,首部长度需要是4bity的整数倍。所以,包长度有可能是为了补全UDP首部长度是4bity的整数倍。

问题三:UDP和TCP数据包最大值的确定?

​ MTU最大传输单元,这个传输单元实际上和数据链路层有密切的关系,由于以太网传输方面的限制,每个以太网帧有最小64bytes和最大1518bytes,超过大小限制(过大或者过小的)都视为错误的数据帧,那以太网转发设备就会丢弃这些数据帧。以太网最大的数据帧是1518bytes,所以刨去以太网的帧头14bytes和帧尾4bytes,剩下1500bytes就是MTU。

5.4、TCP 和 UDP 应用场景:

由于 TCP 是⾯向连接,能保证数据的可靠性交付,因此经常⽤于:

由于 UDP ⾯向⽆连接,它可以随时发送数据,再加上UDP本身的处理既简单⼜⾼效,因此经常⽤于:

5.5、如何唯一确定一个TCP连接?

​ TCP是四元组(IP+port)

5.6、TCP最大连接数?

​ 一般服务器固定在某个本地端口上监听,等待客户端的链接请求,因此客户端IP和port可变的。

​ 理论上:max(TCP连接数) = 客户端IP * 客户端port

​ 对 IPv4,客户端的 IP 数最多为 2 的 32 次⽅,客户端的端⼝数最多为 2 的 16 次⽅,也就是服务端单机最⼤ TCP 连接数,约为 2 的 48 次⽅。当然,服务端最⼤并发 TCP 连接数远不能达到理论上限。⾸先主要是⽂件描述符限制,Socket 都是⽂件,所以⾸先要通过 ulimit 配置⽂件描述符的数⽬;另⼀个是内存限制,每个 TCP 连接都要占⽤⼀定内存,操作系统的内存是有限的

6、TCP三次握手

6.1、TCP三次握手

在这里插入图片描述

​ 三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。实质上其实就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换 TCP窗口大小 信息。

重点

TCP连接:用于保证可靠性和流量控制维护的某些状态信息,包括Socket(IP+port)、序列号(解决乱序)和窗口大小(流量控制

6.2、为什么要三次握手才能初始化Socket、序列号和窗口大小并建立TCP连接?

三次握手的原因(三方面):

1、防止旧的重复连接初始化造成混乱

​ 客户端通过上下文比较,发现自己期望收到的Ack num 应该是100 + 1 ,而不是 90 + 1,所以就会向服务器发起RST报文终止连接。

​ 客户端连续发送多次 SYN 建立连接的报⽂,在⽹络拥堵情况下:

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

2、同步双方初始序列号

TCP 协议的通信双⽅, 都必须维护⼀个「序列号」, 序列号是可靠传输的⼀个关键因素,它的作⽤:

:四次握⼿其实也能够可靠的同步双⽅的初始化序号,但由于第⼆步和第三步可以优化成⼀步,所以就成了「三次握⼿」。⽽两次握⼿只保证了⼀⽅的初始序列号能被对⽅成功接收,没办法保证双⽅的初始序列号都能被确认接收。

3、避免资源浪费

​ 如果只有「两次握⼿」,当客户端的 SYN 请求连接在⽹络中阻塞,客户端没有接收到 ACK 报⽂,就会重新发送 SYN ,由于没有第三次握⼿,服务器不清楚客户端是否收到了⾃⼰发送的建⽴连接的 ACK 确认信号,所以每收到⼀个 SYN 就只能先主动建⽴⼀个连接,这会造成什么情况呢?如果客户端的 SYN 阻塞了,重复发送多次 SYN 报⽂,那么服务器在收到请求后就会建⽴多个冗余的⽆效链接,造成不必要的资源浪费。即两次握⼿会造成消息滞留情况下,服务器重复接受⽆⽤的连接请求 SYN 报⽂,⽽造成重复分配资源。

6.3、总结为什么握手是三次?不是两次或者四次?

​ TCP 建⽴连接时,通过三次握⼿能防⽌历史连接的建⽴,能减少双⽅不必要的资源开销,能帮助双⽅同步初始化序列号。序列号能够保证数据包不重复、不丢弃和按序传输。

​ 不使⽤「两次握⼿」和「四次握⼿」的原因:

6.4、为什么第三次握手是可以携带数据的,前两次握手是不可以携带数据的?

​ 其实第三次握手的时候,是可以携带数据的。但是,第一次、第二次握手不可以携带数据

​ 为什么这样呢?大家可以想一个问题,假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据。因为攻击者根本就不理服务器的接收、发送能力是否正常,然后疯狂着重复发 SYN 报文的话,这会让服务器花费很多时间、内存空间来接收这些报文。

​ 也就是说,**第一次握手不可以放数据,其中一个简单的原因就是会让服务器更加容易受到攻击了。而对于第三次的话,此时客户端已经处于ESTABLISHED状态。对于客户端来说,他已经建立起连接了,**并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据也没啥毛病。

6.5、如何在Linux系统中查看TCP状态?

​ TCP 的连接状态查看,在 Linux 可以通过 netstat -napt 命令查看。

在这里插入图片描述

6.6、为什么客户端和服务端的初始序列号ISN是不同的?

​ 如果⼀个已经失效的连接被重⽤了,但是该旧连接的历史报⽂还残留在⽹络中,如果序列号相同,那么就⽆法分辨出该报⽂是不是历史报⽂,如果历史报⽂被新的连接接收了,则会产⽣数据错乱。所以,每次建⽴连接前重新初始化⼀个序列号主要是为了通信双⽅能够根据序号将不属于本连接的报⽂段丢弃。另⼀⽅⾯是为了安全性,防⽌⿊客伪造的相同序列号的 TCP 报⽂被对⽅接收。

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

I S N = M + F ( l o c a l h o s t , l o c a l p o r t , r e m o t e h o s t , r e m o t e p o r t ) ISN = M + F(localhost,localport,remotehost,remoteport) ISN=M+F(localhost,localport,remotehost,remoteport)

​ M是一个计数器,F是一个hash算法,根据四元组生成一个随意数值,一般使用MD5算法。

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

在这里插入图片描述

​ MTU :⼀个⽹络包的最⼤⻓度,以太⽹中⼀般为 1500 字节;

​ MSS :除去 IP 和 TCP 头部之后,⼀个⽹络包所能容纳的 TCP 数据的最⼤⻓度;

​ 当 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 为单位,⽽不⽤重传所有的分⽚,⼤⼤增加了重传的效率。

总结IP按MTU分⽚,如果某⼀⽚丢失则需要所有分⽚都重传;(2)IP没有重传机制,所以需要等TCP发送⽅超时才能重传;

问题⼀MSS跟IP的MTU分⽚相⽐,只是多了⼀步协商MSS值的过程,⽽IP的MTU可以看作是默认协商好就是1500字节,所以为什么协商后的MSS可以做到丢失后只发丢失的这⼀⽚来提⾼效率,⽽默认协商好1500字节的IP分⽚就需要所有⽚都重传呢?
问题⼆TCP MSS分⽚如果丢失了⼀⽚,是不是也需要发送⽅等待超时再重传?如果不是,MSS的协商如何能在超时前就直到丢了分⽚从⽽提⾼效率的呢?

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

服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,所以服务器容易受到SYN洪泛攻击。SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server则回复确认包,并等待Client确认,由于源地址不存在,因此Server需要不断重发直至超时,这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。SYN 攻击是一种典型的 DoS/DDoS 攻击。

​ 检测 SYN 攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击。

netstat -n -p TCP | grep SYN_RECV

常见的防御 SYN 攻击的方法有如下几种:

其中⼀种解决⽅式是通过修改 Linux 内核参数,控制队列⼤⼩和当队列满时应做什么处理。
当⽹卡接收数据包的速度⼤于内核处理的速度时,会有⼀个队列保存这些数据包。控制该队列的最⼤值如下参数:

net.core.netdev_max_backlog

SYN_RCVD 状态连接的最⼤个数:

net.ipv4.tcp_max_syn_backlog

超出处理能时,对新的 SYN 直接回报 RST,丢弃连接:

net.ipv4.tcp_abort_on_overflow

当SYN队列占满,重新启动cookies

net.ipv4.tcp_syncookies = 1

7、TCP四次挥手

在这里插入图片描述

7.1、为什么挥手需要四次?

另一种回答:

​ 因为当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端,“你发的FIN报文我收到了”。只有等到我服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四次挥手。

7.2、为什么Time_wait等待时间是2MSL?

​ MSL 是 Maximum Segment Lifetime,报⽂最⼤⽣存时间,它是任何报⽂在⽹络上存在的最⻓时间,超过这个时间报⽂将被丢弃。因为 TCP 报⽂基于是 IP 协议的,⽽ IP 头中有⼀个 TTL 字段,是 IP 数据报可以经过的最⼤路由数,每经过⼀个处理他的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报⽂通知源主机。

​ MSL 与 TTL 的区别: MSL 的单位是时间,⽽ TTL 是经过路由跳数。所以 MSL 应该要⼤于等于 TTL 消耗为 0 的时间,以确保报⽂已被⾃然消亡。

​ 2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK没有传输到服务端,客户端⼜接收到了服务端᯿发的 FIN 报⽂,那么 2MSL 时间将重新计时。 在 Linux 系统⾥ 2MSL 默认是 60 秒,那么⼀个 MSL 也就是 30 秒。Linux 系统停留在 TIME_WAIT 的时间为固定的 60 秒。

#define TCP_TIMEWAIT_LEN (60*HZ) //最长等待时间

7.3、为什么需要Time_wait状态?

1、防止旧连接的数据包

​ 如果发生网络延迟或者存在服务器关闭之前的报文,那么如果有相同端口的TCP连接被复用,该延迟的报文抵达客户端,客户端仍然能够正常接收这个过期的报文,就会导致最后接收到的数据错乱。

2、保证连接正确关闭

​ 客户端四次挥⼿的最后⼀个 ACK 报⽂如果在⽹络中被丢失了,此时如果客户端 TIME-WAIT 过短或没有,则就直接进⼊了 CLOSED 状态了,那么服务端则会⼀直处在 LASE_ACK 状态。当客户端发起建⽴连接的 SYN 请求报⽂后,服务端会发送 RST 报⽂给客户端,连接建⽴的过程就会被终⽌。

7.4、Time_wait过多怎么处理?

过多的Time_wait状态主要危害有两种:

端口数量是有限的,一般为32768~61000(max=65535),如果端口资源占用过多的话,会导致无法创建新的连接。

net.ipv4.ip_local_port_range
问题一如果客户端第四次挥⼿ack丢失,服务端超时重发的fin报⽂也丢失,客户端timewait时间超过了2msl,这个时候会发⽣什么?认为连接已经关闭吗?
问题二如果是服务提供方发起的 close ,然后引起过多的 time_wait 状态的 tcp 链接,time_wait 会影响服务端的端⼝吗?
问题三:服务端设置 SO_REUSEADDR 选项,这样服务器程序在重启后,可以⽴刻使⽤。这⾥设置SO_REUSEADDR 是不是就等价于对这个 socket 设置了内核中的net.ipv4.tcp_tw_reuse=1 这个选项?

tcp_tw_reuse 是为了缩短 time_wait 的时间,避免出现大量的 time_wait 连接⽽占⽤系统资源,解决的是 accept后的问题。

SO_REUSEADDR 是为了解决 time_wait 状态带来的端⼝占⽤问题,以及⽀持同⼀个 port 对应多个 ip,解决的是bind 时的问题。

7.5、如何优化Time_wait?

打开 net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_timestamps 选项;
net.ipv4.tcp_max_tw_buckets
程序中使⽤ SO_LINGER ,应⽤强制使⽤ RST 关闭

1)net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_timestamps

​ 作用:可以复⽤处于 TIME_WAITsocket 为新的连接所⽤。但需要注意,tcp_tw_reuse 功能只能⽤客户端(连接发起⽅),因为开启了该功能,在调⽤ **connect()**函数时,内核会随机找⼀个 time_wait 状态超过 1 秒的连接给新的连接复⽤。(但需要对TCP 时间戳的⽀持)

net.ipv4.tcp_timestamps=1(默认即为 1)

2)net.ipv4.tcp_max_tw_buckets

​ 这个值默认为 18000,当系统中处于 TIME_WAIT 的连接⼀旦超过这个值时,系统就会将后⾯的 TIME_WAIT 连接状态重置(但引入的问题会更多)

3)程序中使⽤ SO_LINGER,应⽤强制使⽤ RST 关闭

​ 相当于通过设置socket选项,来设置调用close关闭连接的行为。调用close之后,会立刻发送一个RST标志给对端,直接跳过四次挥手,跳过time_wait,直接关闭。

7.6、如果已经建立了连接,但是client突然发生故障怎么办?

​ TCP有一个保活机制(keep_alive):

​ 原理:定义⼀个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作⽤,每隔⼀个时间间隔,发送⼀个探测报⽂,该探测报⽂包含的数据⾮常少,如果连续⼏个探测报⽂都没有得到响应,则认为当前的TCP 连接已经死亡,系统内核将错误信息通知给上层应⽤程序。

​ 在 Linux 内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔,以下都为默认值:

net.ipv4.tcp_keepalive_time=7200 //~2小时,两小时内如果没有任何连接相关的活动,则会启动保活机制
net.ipv4.tcp_keepalive_intvl=75  //每次检测间隔75s
net.ipv4.tcp_keepalive_probes=9  //检测响应次数最大9次,如果检测9次无响应,则认为对方不可达,从而中断本次连接

**注意**开启TCP保活机制,需要注意一下情况:

7.7、服务器出现大量close_wait的连接的原因是什么?有什么解决方法?

close_wait状态是在TCP四次挥手的时候收到FIN但是没有发送自己的FIN时出现的,服务器出现大量close_wait状态的原因有两种:

处理方法:

8、TCP如何保证可靠传输?

校验和、确认机制(确认应答+序列号)、重传机制、连接管理(挥手和握手)、流量控制(滑动窗口)、拥塞控制

8.1、TCP可靠的原因?

​ 每个TCP的socket在内核中都有一个发送缓冲区接收缓冲区,TCP协议要求在对端接收到TCP数据报之后,对其序号进行ACK,只有当接收到一个TCP数据报的ACK之后,才可以把这个TCP数据报从socket的发送缓冲区清除,另外,TCP还有一个流量控制功能,TCP的socket接收缓冲区接收到网络上来的数据缓存后,如果应用程序一直没有读取,那接收缓冲区满了之后,就会通知对端TCP协议中的窗口关闭,就是滑动窗口实现流量控制,保证TCP的接收缓冲区不会溢出,因为对方不允许发送超过所通知窗口大小的数据,要是无视窗口大小而发送了超出窗口大小的数据,则接受发TCP将丢弃它。综上来说,TCP有三次握手、四次挥手,除此之外还有超时重传机制,对于每份报文也存在校验和,保证每份报文可靠性。

9、TCP重传、滑动窗口、拥塞控制、流量控制

9.1、重传机制(丢包)

​ 重传机制主要有四种:超时重传、快速重传、SACK、D-SACK

1、超时重传

在发送数据时,设定⼀个定时器,当超过指定的时间后,没有收到对⽅的 ACK确认应答报⽂,就会重发该数据。

问题一:超时重传、RTO与RTT区别是什么?
问题二如果RTO较长或较短时,会发生什么事情呢?
问题三:如何计算RTO?

在这里插入图片描述

​ 其中 SRTT 是计算平滑的RTT , DevRTR 是计算平滑的RTT 与 最新 RTT 的差距。

​ 在 Linux 下,α = 0.125β = 0.25μ = 1∂ = 4(实验结论)

2、快速重传

​ 不以时间为驱动,⽽是以数据驱动重传(3次同样的ACK触发快速重传机制)

​ 但是,快速重传机制只解决了超时时间的问题,但是重传过程中存在一个问题,是重传之前的一个,还是重传所有?(SACK解决该问题)。

3、SACK

​ TCP头部[选项]字段里加SACK,就可以将缓存的地图发送给发送方,这样就可以知道哪些数据收到了,哪些数据没有收到,因此只需要重新传丢包的数据。

net.ipv4.tcp_sack
4、D-SACK

​ Duplicate SACK ,使用了SACK来告诉发送方有哪些数据被重复接收了。

​ D-SACK优势:

net.ipv4.tcp_dsack

9.2、滑动窗口

​ 引言:TCP每发送一次数据,就需要应答一次,然后再发送下一个,这样就存在一个问题,如果应答时间较长的话,就会导致网络的吞吐量较低。所以为了解决数据包的往返时间越长,通信效率越低的问题,引入窗口

​ 窗口的实现实际上是操作系统开辟的一个缓存空间,发送方主机在等待确认应答返回之前,必须在缓冲区中保留已经发送的数据。如果已经确认应答,则可以删除缓存中的数据。

​ TCP头部有一个window字段,该字段主要是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。

​ 窗口大小:指不需要等待确认应答,而可以继续发送数据的最大值。

​ 窗口的大小一般都是由接收方的窗口大小来决定。

问题一:程序是如何表示发送方的四个部分呢?

​ TCP 滑动窗⼝⽅案使⽤三个指针来跟踪在四个传输类别中的每⼀个类别中的字节。其中两个指针是绝对指针(指特定的序列号),⼀个是相对指针(需要做偏移)。

发送窗口

接收窗口

9.3、流量控制

​ 为了解决网络浪费的情况,引入流量控制,让发送方根据接收方的实际接收能力控制发送的数据量。

1、操作系统缓存区与滑动窗口的关系

​ 当服务端系统资源⾮常紧张的时候,操⼼系统可能会直接减少了接收缓冲区⼤⼩,这时应⽤程序⼜⽆法及时读取缓存数据,那么这时候就有严重的事情发⽣了,会出现数据包丢失的现象。

2、窗口关闭

​ 窗口关闭:如果窗⼝⼤⼩为 0 时,就会阻⽌发送⽅给接收⽅传递数据,直到窗⼝变为⾮ 0 为⽌,这就是窗⼝关闭。

问题一:窗口关闭存在的问题?
问题二:如何解决窗口关闭时,潜在的死锁现象?
3、糊涂窗口综合征

​ 如果接收⽅太忙了,来不及取⾛接收窗⼝⾥的数据,那么就会导致发送⽅的发送窗⼝越来越⼩。到最后,如果接收⽅腾出⼏个字节并告诉发送⽅现在有⼏个字节的窗⼝,⽽发送⽅会义⽆反顾地发送这⼏个字节,这就是糊涂窗⼝综合症。(一般针对小窗口问题)

问题一:怎么让接收方不通告小窗口呢?
问题二:怎么让发送方避免发送小数据呢?

只要没满⾜上⾯条件中的⼀条,发送⽅⼀直在囤积数据,直到满⾜上⾯的发送条件。

TCP_NODELAY //TCP通过设置该选项来关闭Nagle算法,一般默认打开,但是对于交互性比较强的程序,如telnet或ssh则需要关闭
setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&value, sizeof(int));

9.4、拥塞控制

​ 拥塞控制目的是避免「发送⽅」的数据填满整个⽹络

​ 拥塞窗口:cwnd是发送方维护的一个状态变量,会根据网络的拥塞程度实现动态变化。

​ 发送窗⼝ swnd 近似等于接收窗⼝ rwnd,引入拥塞窗口之后,此时发发送窗口swnd = min(cwnd, rwnd)。

​ 拥塞窗口cwnd变化规则:如果未曾出现网络拥塞,则cwnd就会增大;一旦出现网络拥塞,cwnd就会减小。此时的网络拥塞的判断是,是否发生了超时重传,一旦发生了超时重传,即只要发送方没有在规定的时间内收到接收方ACK应答报文,则认为网络拥塞。

总述拥塞控制在刚开始传输就发送大量的数据,网络可能在一开始就很拥堵,持续发送就会越来越堵,拥堵的加剧会产生大量的丢包,大量的超时重传,严重影响传输。所以TCP引入a)慢启动机制,在开始发送数据的时候,先发送少量的数据探明当前网络的状况,再决定多大的速度进行传输,这时有种拥塞窗口的概念,发送刚开始定义拥塞窗口位1,每次收到ACK应答,拥塞窗口增加,发送数据前,将拥塞窗口与接收端反馈的窗口大小比对,取较小的值做为实际发送的窗口。慢启动只是说一开始发送的少,但是拥塞窗口的增加是指数级别的,为了控制拥塞窗口的增长,b)拥塞避免,设置拥塞窗口阙值,当拥塞窗口的大小超过阙值时,将不会按照原来的指数增长而是线性的增长。在慢启动开始的时候,慢启动的阙值等于窗口的最大值,一旦造成网络拥塞,发生超时重传,就会到d) 快重传接收方在收到一个失序的报文段就发出重复确认,发送方只要一连收到三个重复确认就应当立即重传对方未接受到的报文段,而不必继续等待设置重传计时器时间到期。e) 快恢复当收到3个重复的确认后,说明网络不那么糟糕,可以快速恢复,又进行慢启动,窗口阙值会降为发生网络拥塞时窗口大小的一半,同时拥塞窗口重置为1。

在这里插入图片描述

1、慢启动

​ 慢启动:也就是⼀点⼀点的提⾼发送数据包的数量。

​ 慢启动规则:当发送⽅每收到⼀个 ACK,拥塞窗⼝ cwnd 的⼤⼩就会加 1,但是慢启动算法其发包的个数是指数性的增长。当到达慢启动门限ssthresh状态变量时,就要开始使用拥塞避免算法。

2、拥塞避免

​ 当cwnd >= ssthresh时,就使用拥塞避免算法,⼀般来说 ssthresh 的⼤⼩是 65535 字节。

​ 拥塞避免规则:

​ 目的是将慢启动算法中的指数增长变成了线性增长,但仍然处于增长状态,只是增长的速度更缓慢一些。但,随着数据包一直增加,也会导致网路拥塞,于是就存在丢包现象,就需要对丢包的数据进行重传,触发重传机制,也就进入了「拥塞发生算法」

3、拥塞发生

​ 一般来说,TCP拥塞控制默认认为网络丢包是由于网络拥塞导致的,所以一般的TCP拥塞控制算法以丢包为网络进入拥塞状态的信号。对于丢包有两种判定方式,一种是超时重传RTO[Retransmission Timeout]超时,另一个是收到个重复确认ACK

​ 超时重传RTO是TCP协议保证数据可靠性的一个重要机制,其原理是在发送一个数据以后就开启一个计时器,在一定时间内如果没有得到发送数据报的ACK报文,那么就重新发送数据,直到发送成功为止。但是如果发送端接收到3个以上的重复ACK,TCP就意识到数据发生丢失,需要重传。这个机制不需要等到重传定时器超时,所以叫做快速重传,而快速重传后没有使用慢启动算法,而是拥塞避免算法,所以这又叫做快速恢复算法。

4、快重传、快恢复
问题一:为什么快速重传是选择三次ACK,而不是两次或者四次?
问题二:丢包原因有哪些?

10、Nagle算法&&延迟ACK

10.1、Nagle算法:

​ Nagle算法是为了减少广域网的小分组数目,从而减小网络拥塞的出现;

该算法要求一个tcp连接上最多只能有一个未被确认的未完成的小分组,在该分组ack到达之前不能发送其他的小分组,tcp需要收集这些少量的分组,并在ack到来时以一个分组的方式发送出去;其中小分组的定义是小于MSS的任何分组;

TCP_NODELAY //TCP通过设置该选项来关闭Nagle算法,一般默认打开,但是对于交互性比较强的程序,如telnet或ssh则需要关闭
setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&value, sizeof(int));

10.2、延迟ACK:

如果tcp对每个数据包都发送一个ack确认,那么只是一个单独的数据包为了发送一个ack代价比较高,所以tcp会延迟一段时间,如果这段时间内有数据发送到对端,则捎带发送ack,如果在延迟ack定时器触发时候,发现ack尚未发送,则立即单独发送;

//关闭TCP延迟ACK
//通过在socket设置TCP_QUICKACK来关闭这个算法
setsockeopt(sock_fd, IPPROTO_TCP, TCP_QUICKACK, (char*)& value, sizeof(int));

10.3、当Nagle遇上延迟ACK:

​ 试想如下典型操作,写-写-读,即通过多个写小片数据向对端发送单个逻辑的操作,两次写数据长度小于MSS,当第一次写数据到达对端后,对端延迟ack,不发送ack,而本端因为要发送的数据长度小于MSS,所以nagle算法起作用,数据并不会立即发送,而是等待对端发送的第一次数据确认ack;这样的情况下,需要等待对端超时发送ack,然后本段才能发送第二次写的数据,从而造成延迟;

10.4、关闭Nagle算法:

​ 使用TCP套接字选项TCP_NODELAY可以关闭套接字选项;

​ 如下场景考虑关闭Nagle算法:

10.5、禁止Nagle和开启Nagle算法发送数据与确认示意图:

11 、TCP异常分析

11.1、TCP第一次握手的SYN丢包了,会发生什么?

​ 客户端发起SYN包后,如果一直没有收到服务端的ACK,就会触发超时重传RTO机制。在Linux中,第一次捂手的SYN超时重传次数是由内核参数指定,tcp_syn_retires默认重传5次。因此,当客户端TCP第一次握手发生SYN包,在超过时间内没有收到服务端的ACK报文,就会超时重传SYN数据包,每次超时重传RTO是成倍增加的,直到超过SYN包重传次数,则客户端不再发送SYN包。

11.2、TCP第二次握手的SYN、ACK丢包了,会发生什么?

​ 当第二次握手的SYN和ACK丢包时,客户端会超时重发SYN包,服务端会超时重发SYN + ACK包。在Linux中,第二次捂手的SYN + ACK 超时重传次数是由内核参数指定,tcp_synack_retires默认重传5次。超过重传次数之后,服务端的TCP连接主动断开,但是客户端仍然处于established状态。

11.3、TCP第三次握手的ACK丢包了,会发生什么?

​ 由于服务端已经断开连接,此时客户端再次向服务端发送数据报文,则会一直处于超时重传状态,每重传一次,RTO翻倍增长,所以持续一段时间之后,共重传15次之后,客户端的talent才报错退出。最⼤超时重传次数是由 tcp_retries2 指定,默认值是 15 次。

11.4、如果客户端不发送数据,什么时候才会断开处于established状态的连接?

​ 核心:保活机制keeping_alive

​ 定义⼀个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作⽤,每隔⼀个时间间隔,发送⼀个「探测报⽂」,该探测报⽂包含的数据⾮常少,如果连续⼏个探测报⽂都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应⽤程序。

总结

​ 在建⽴ TCP 连接时,如果第三次握⼿的 ACK,服务端⽆法收到,则服务端就会短暂处于 SYN_RECV 状态,⽽客户端会处于 ESTABLISHED 状态。

​ 由于服务端⼀直收不到 TCP 第三次握⼿的 ACK,则会⼀直重传 SYN、ACK 包,直到重传次数超过tcp_synack_retries 值(默认值 5 次)后,服务端就会断开 TCP 连接。

​ ⽽客户端则会有两种情况:

12、拆包和粘包

12.1、TCP和UDP哪个会发生粘包?

​ 只有TCP会产生粘包,UDP不会。首先,TCP采用的SOCKET是SOCK_STREAM (流式套接字) ,UDP是SOCK_DGRAM(数据报套接字)。TCP基于字节流,虽然应用层和TCP传输层之间的数据交互是大小不等的数据块,但是TCP把这些数据块仅仅看成一串无结构的字节流,没有边界,并且TCP首部没有表示数据长度的字段。UDP是基于数据报发送,从UDP的帧结构可以看出,UDP的首部采用了16bits来指示UDP报文的长度,所以在应用层可以很好的将不同的数据报文区分开。

12.2、粘包、拆包的可能情况

​ 粘包:接收端只收到一个数据包,但是TCP是不会出现丢包,所以就会出现一个数据包中包含了发送端发送的两个数据包的信息,由于接收端不知道这两个数据包的界限,就是粘包现象。

​ 拆包和粘包:接收端接收到了两个数据包,但是这两个数据包要么是不完整的,要么就是多出来一块,就发生了拆包和粘包。

12.3、粘包、拆包发生的原因

12.4、解决办法

关键在于给每个数据包添加边界信息,一般有如下几种处理方式:

13、UDP

13.1、UDP报文格式

在这里插入图片描述

⽬标和源端⼝:主要是告诉 UDP 协议应该把报⽂发给哪个进程。

包⻓度:该字段保存了 UDP ⾸部的⻓度跟数据的⻓度之和。

校验和:校验和是为了提供可靠的 UDP ⾸部和数据⽽设计。

13.2、UDP使用connect

1、UDP可以使用connect系统调用

​ UDP中的connect与TCP中的由本质的区别,TCP调用connect会引发三次握手,client与server建立连接。UDP是面向无连接的,调用connect是把对端IP和port记录下来;

2、UDP可以多次调用connect,TCP只能调用一次

​ UDP多次调用connect主要是希望指定一个新的IP和port连接,以及断开以前的IP和port连接。指定新连接直接设施connect的第二个参数(sockaddr_in sin),断开连接,需要将connect中第二个参数中的sin_family设置成 AF_UNSPEC;

3、UDP使用connect可以提高效率

​ LINUX系统有用户空间和内核空间之分,接收数据,数据从网卡上收上来,需要先交给系统内核,然后内核再交给上层应用程序(处于用户空间)。发送数据也是一样,数据需要从用户空间拷贝到内核,内核处理完之后,再交给网卡发出去。在用户态和内核态进行切换,非常耗时,对于高性能的服务器来说,其实应该减少这种耗时,但是如果切换无法避免,就尽量减少切换时拷贝的数据,那调用connect之后的UDP,内核相当于维护了一个“连接”,就可以调用send来发送数据了,那send对比于sendto其实参数少得多,那每次调用的时候就会少拷贝一些数据到内核空间。从另一个方面来讲,sendto的参数到内核空间以后,内核需要分配内存来存储这些参数值,当数据包发送出去之后,内核还需要释放掉这块内存,下次再调用sendto的时候,内核就需要再次分配内存存放这些临时的数据,就会形成一个不断地分配和释放临时内存的过程。那connect之后可以使用send,相当于维护了这个连接,所以后面每次进行发送数据,内核就不需要再分配删除内存了。

4、UDP使用connect可以得到错误信息的提示

​ 在使用connect编写UDP SOCKET的时候,会遇到连接错误的提示(ECONNREFUSED),可以本来UDP是无连接的,报连接错误其实是ICMP带来的。当一个UDP socket去connect对端是,并没有发送任何数据包,仅仅只是在内核建立了一个映射,该映射的作用是为了把UCP和ICMP(IP协议的补充,检测网络连接)通道捆绑在一起,调用了connect之后,内核协议栈就维护了一个从源目的地的单向连接,当下层有ICMP错误信息返回时,内核就可以根据这个映射找到是哪个UDP的socket发的包失败了,进而可以把得到该错误信息了,要是没有connect,是得不到该错误信息的。

13.3、UDP使用bind

13.4、为什么UDP不可靠?如何实现UDP可靠传输?

标签:UDP,重传,ACK,TCP,发送,拆包,连接
来源: https://blog.csdn.net/loytuls/article/details/123426442