【网络原理】简述TCP/IP网络模型传输层协议——UDP/TCP

在TCP/IP网络模型中,应用层会将数据交给传输层,传输层是给应用层提供网络支持的,在传输层中有两个重要的传输层协议:TCP和UDP

UDP协议

UDP协议是无连接,面向数据报,不可靠传输,全双工的一种协议

UDP只是简单的传输数据,但并不能够保证数据能够成功的抵达接收方,但是相较于TCP,UDP的效率更高,实时传输性更好

报头结构

UDP协议会在应用层传输过来的数据前加上一个UDP协议报头(如图)

 在UDP报头中包含四个信息分别是源端口,目的端口,UDP长度和,UDP校验和,这里的每个信息都是两个字节的长度,所以总共加起来UDP报头是八个字节

源端口和目的端口

源端口表示数据是从哪里传输出去的,目的端口是表示数据要传输到哪里去

UDP校验和

校验和的作用就是来检验传输的数据是否正确的。为什么会出现校验和?原因是我们传输数据的时候本质上是通过物理层的光信号和电信号进行传输,当光信号或者电信号受到物理环境的影响的时候,很有可能传输的数据也因此收到影响(比如电信号传输的数据是1111,收到物理环境的影响1111变成了0111),这种情况是客观存在的,那么为了让这种情况所受到的影响降到最低我们引入了校验和来检查传输的数据是否正确

校验和的具体工作流程就是发送方通过数学公式将数据内容进行计算最后得到一个只有几个字节长度的校验和,通过UDP报头传输给接受方,接收方也通过数学公式来计算的得到数据的校验和,如果两个校验和一致那么传输的数据就是正确的数据,如果校验和不一致那么就是有错误的数据

那么有没有一种可能得到错误数据的校验和同传输过来的校验和是一致的?答案是有可能的,但是在工程上这种可能出现的概率极小可以忽略不计 

在工程中知名的几种计算校验和的方式 CRC MD5 SHA1

CRC计算校验和的方法是将数据里面的字节循环往上,如果累加溢出了那么高位就不要了,这样计算校验和的方式优点是好算,但是如果溢出以后再次修改数据内容那么就有可能出现校验和不变内容改变的情况

MD5的计算校验和方式是通过数学公式将数据计算成一个固定长度字符串,无论数据有多长计算出来的校验和都是固定长度的,同时MD5计算的校验和冲突概率很小因为MD5是根据内容进行计算的,如果内容发生改变了那么校验和也会发生改变,同时MD5计算出来的校验和是不可逆的,无法逆推回原来的数据内容

UDP长度

UDP长度是表示数据报的大小是多大,16位UDP长度表示的范围是0到65535,换算一下就是0到64kb,也就是说使用UDP协议传输数据报,这一个数据报最大只能是64kb。

那么问题来了——如果想要发送的数据超过了64kb并且想要使用UDP协议进行传输应该怎么办?答案是手动拆包,多次传送,在应用层里面就应该将想要传输的数据进行手动拆包,将数据拆成多个包然后再将数据传输到传输层使用UDP协议进行传输数据。同时还有另一种解决方案就是不使用UDP协议而是转用TCP协议


TCP协议

TCP是一种面向字节流,有链接,可靠输出,全双工的传输层协议,相比较于UDP,TCP多了很多特性所以能够保证数据成功的传输到接收方

确认应答机制(ACK)

确认应答机制可以说是实现TCP实现可靠传输的最核心的机制,可靠传输并不是说100%将数据传输到接收方上,而是尽力而为将数据尽可能的传输过去,并且当数据没有传输过去的时候传送方可以知道

关于确认应答机制这里我举一个例子

假设传输方给接收方传了个信息叫做:“今晚一起吃个饭吗?”,那么接收方回复一条: “好的没问题”,这个时候接收方回复的“好的没问题”就相当于是TCP中确认应答(ACK),通过ACK,传输方收到确认应答(ACK)就可以清楚的知道自己的数据有没有成功的传输给接收方

当我们的情况变得复杂一点,传输方连续发送了两条消息,由于客观因素的存在,很有可能会出现“后发先至”的情况,那么收到的数据也就会出现顺序上出现错误的情况

 

因为在网络中每个数据报所通过的传输路线是不一样的,可能数据1的传输路线传输速率更快,而数据2的传输速率更慢,所以就会出现后发先至的情况,由于后发先至这种情况是无法避免的情况,所以我们就应该来考虑如何将这种负面影响降到最低。TCP中通过给传输报文和ACK进行编号的方式来解决这个问题。

那么在TCP报头中的ACK如果标记为1就表示这是一个应答报文,如果表示为0则表示这是一个传输报文。那么传输报文是怎么进行编号的?由于TCP是面向字节流进行传输数据的,所以使用TCP协议进行数据传输那么数据都是一个字节一个字节进行传输的,假设这个数据里面有1000个字节,那么根据字节进行编号第一个字节就是1,第二个字节就是2,以此类推。由于这一千个字节都属于同一个TCP报文,所以TCP报头中只用记录第一个字节的序号,所以这个TCP报文的序号就是1。紧接着假设下一个报文的字节长度还是1000,那么根据刚才的描述,这个TCP报文的序号就是1001。所以传输报文的编号是根据报文里面的第一个字节的序号进行编号的。TCP知道了第一个字节的序号再根据报头里面的报文长度就可以很轻松的知道每一个字节的序号了。根据下面这副图,可以清楚的了解确认应答机制的具体工作流程。传输方将数据1传输给接收方,接收方接收到了数据并且传输应答报文(编号1001),这个1001的意义有两个:1.表示序号小于1001的数据我都接受到了。2.表示接收索要从序号为1001开始的字节流

小结:

TCP通过确认应答机制来保证协议中的可靠传输,通过应答报文来让发送方知道自己的报文有没有顺利的发送出去,同时引入了编号来区分不同的报文,也解决了因为客观因素所导致的数据后发先至的问题

超时重传机制

当我们传输数据的时候如果出现了丢包的情况,那么就会分为两种情况来讨论:ACK丢了/传输的数据丢了。那么在TCP协议中无论是出现哪一种情况,发送方看到的结果都是没有接收到ACK,所以这两种情况一视同仁都是坐视为丢包了

因此为了解决这个问题,TCP引入了超时重传的这个机制来解决丢包的问题。TCP设置了一个时间阈值,当发送方将数据传输出去的那一刻就开始计时等待接收ACK,如果时间超出了时间阈值,那么这个时候就被视为丢包了(不管是传输出去的数据在路上/传输的数据丢了/ACK还在路上/ACK丢了都视为丢包),此时发送方会再次发送数据给接收方。

这个时候又有新的问题出现了,是否会出现接收方接收两次相同数据的情况?
答案是不会的:TCP会根据这种特殊数据进行去重处理,TCP存在“接收缓冲区”,接收方将读取到的数据存放到socket的缓冲区里面,根据数据报头的序号TCP可以很容易识别接收到的数据是不是重复的,如果重复了就将后一份数据直接丢弃从而保证接收方读到的数据一定是唯一的,同时这个“接收缓冲区”还会将传过来的数据根据序号进行排序,从而保证接收方的数据是和发送方发送的顺序是一致的。

TCP超时重传并不是只重传一次,而是N次(N并不是一个很大的数字),当发送方重传了N次以后仍然没有收到接收方发送过来的ACK,此时就会认为是网络出现了故障,这个时候TCP就会进行重置连接的操作,如果重置连接还是失败了,那么就彻底断开连接了

小结:

TCP的可靠传输是根据确认应答机制和超时重传机制来保证的,确认应答表述的是数据顺利传输的情况,超时重传是表述数据传输失败的情况,这两者相互配合保证了TCP的可靠传输

连接管理(三次握手/四次挥手)

连接管理:连接就是通信双方各自记录对方的信息,彼此之间相互认同。而管理则是描述了连接如何创建和断开。

三次握手

实际上建立连接的过程是通信双方各自发送一个“建立连接”的申请并且回复一个应答报文,我们将这里面的每次进行信息交互的过程称为“握手”,我们通过下面这张图可以发现实际上四次握手可以合并成三次握手,将接收方发送应答报文和建立连接申请合二为一,将两次握手变成一次握手

从四次握手变成三次握手

为什么一定要进行合并?因为封装分用两次的成本比分装分用一次的成本要更高,使用四次握手也就意味着要进行更多次的通信次数,也就意味着要消耗更多的网络资源

那么能不能将三次握手变成两次握手?答案也是不行的

假如使用两次握手,那么发送方可以清楚的知道自己已经建立连接完成了,但是接收方并不清楚自己是否建立好了连接,因为接收方没有收到来自发送方的ACK

三次握手除了建立连接以外还有一个重要的作用就是检查通信双方的发送和接受功能是否正常

第一次交互让接收方清楚自己(接收方)的接收能力没有问题,同时发送方的发送能力没有问题(但此时发送方并不知道)。第二次交换让发送方知道了自己的接收能力和发送能力没有问题,同时接收方的发送能力也没有问题(但此时接收方不知道)。第三次交换让接收方知道了自己的发送能力也没有问题。我画了一个图可以很清楚的表示这上面的关系。

实际上下面这张图描述了具体的三次握手的过程

ACK:表示的是应答报文

SYN(同步报文段)表示的是客户端主动给服务器发起建立连接的请求

Listen表示的是服务端的状态:服务端已经准备好了随时可以连接

Established表示的是:通信的一方已经建立好了连接,接下来就可以准备通信了

四次挥手

刚才讲的是通信双方怎么建立连接,接下来要谈的是通信双方断开连接的过程

四次挥手是发送方和接收方一共进行四次交互,发送方和接收方各自发送一次断开连接的申请和应答报文。但是和三次握手不同,四次挥手接收方发送的断开连接的申请以及ACK并不能合并成同一条报文发送给发送方。原因在于三次握手是在操作系统内核里面完成的,应用层根本感知不到并且也干预不了,那么服务器的操作系统内核接收到SYN以后会立刻发送ACK和SYN给发送方,时间上是一致的。但四次挥手并不是这样的。FIN表示的是断开连接的申请,发送方发送的FIN是通过调用Socket中的close方法才会触发FIN,接收方收到FIN以后操作系统内核会立刻回复一个ACK,但是接收方的FIN也是根据对应的close方法来进行调用的,所以说接收方发送的FIN和ACK之间会有一个时间间隔,这个时间间隔不足以让FIN和ACK合并成一个报文发送给发送方。这就是为什么要进行四次挥手。

如果FIN和ACK的时间间隔比较短的话,那么就有可能将两个报文合并成一个报文。但如果在调用close之前接收方在做别的工作那么就不能合并成一个报文了。比如这样

四次挥手在TCP中具体的过程如下 

 ​​

这里有两个重要的状态一个是CLOSE-WAIT和TIME-WAIT

CLOSE-WAIT出现在被动申请断开连接的一方,表示的含义是等待调用close来关闭socket,FIN-WAIT1是表示此时主动断开连接的一方等待被动方发送ACK,当主动方收到ACK以后就进入到FIN2-WAIT的状态等待被动方的FIN,TIME-WAIT出现在主动申请断开连接的一方,当主动断开连接方的状态是TIME-WAIT的时候就表示四次挥手已经结束了

但是此时并不着急断开连接,为什么?因为客户端的ACK有可能丢包了!

这种情况下,站在服务器的角度来看不知道自己的FIN丢包了还是客户端的ACK丢包了,所以无论是哪一个情况都是视为服务器的FIN丢包了,统一进行重传操作。既然FIN丢包了,那么就要进行重传操作,那么客户端就要对传过来的FIN进行响应,对服务器发送ACK。假如客户端在进入到TIME-WAIT以后直接断开连接那么也就不能对服务器发送ACK了。这也就是为什么TIME-WAIT要在保留一段时间,就是为了能够处理ACK丢包的情况。

客户端进入TIME-WAIT状态后再等待一段时间过后如果没有收到重传的FIN,那么也就认为服务器收到ACK,此时断开连接。

那么问题来了进入TIME-WAIT状态后要等待多久?答案是2MSL!客户端在等待2MSL以后还没有收到服务器发送过来的FIN,那么就认为此时可以断开连接了

MSL指的是互联网上两个节点进行传输时所需要的最大时间,通常情况下MSL = 60s,但无论MSL定义成多少,都不能避免特殊情况的发生,所以MSL其实是一个“经验值”,在一般情况下数据的传输都不会超过这个值的大小

小结:

1.通过三次握手可以让通信双方各自建立起对对方的“认同”(建立好连接)

2.通过三次握手可以检查通信双方的接受能力和发送能力是否正常

3.在三次握手过程中通信双方需要协商一些重要的参数(TCP中有些参数需要通信双方相互同步完成,此时刚好有这样的交互过程来完成同步)

4.四次挥手也是有可能变成三次的,但通常情况下都是四次挥手

5.进入TIME-WAIT状态后并不会立刻断开连接,而是等待一会看看服务器会不会发送FIN过来,如果超过2MSL还没有收到FIN,那么此时断开连接

滑动窗口

围绕着TCP的可靠传输这个特点上面谈到的确认应答,超时重传,连接管理这三个机制。

TCP引入了可靠性但是牺牲了传输效率

滑动窗口这个机制主要是为了提高TCP的传输效率,本质上是为了降低等待ACK所需要的时间

具体的操作:批量发送,批量等待

原本的TCP协议中,发送方每次发送完数据之后都要等待接收方的ACK,等ACK到了以后再发送下一条消息,但是在滑动窗口中是批量发送一批数据,同时用同一份时间等待ACK,把不需要等待的就能够直接发送的数据最大的量叫做“窗口大小”,像这个图里面的窗口大小就是 4

在滑动窗口里面在发送完这个批数据之后,发送方就要等待ACK了,当接收到一条ACK以后又继续发送一条数据(注意!不是一批数据!),这样需要等待的传过来的ACK始终是四条

举个例子:这里主机A批量发送了1001~5000的数据给主机B,当主机B发送给主机A的ACK是2001那么就说明2001以前的数据已经处理完了,需要2001以后的数据,这个时候主机A又会将5001~6000的数据发送给主机B,以此类推。

那么问题来了:假如丢包了怎么办?

这里又分为两种情况:ACK丢了和数据丢了

1.ACK丢了:

 一条ACK丢了都不用做任何处理!原因很简单,在前面的确认应答机制里面提到过,给ACK进行编号的意义在于——1.小于这个序号的数据我都已经接收到了 2.索要这个序号开始的数据。那么假如序号为1001的ACK丢包了但是主机A接收到了序号为2001的序号,那么就说明2001之前的序号已经接收到了,主机B向主机A索要序号为2001开始的数据。所以在滑动窗口中一条ACK丢了不用做任何处理。

快速重传

2.数据丢了:

假如说数据丢了,那么主机B返回的ACK就会索要丢掉的数据。

以这个图为例子,假如说数据1001~2000在传输的过程中丢包了,那么接下来不管接收到了编码是几,主机B在给主机A返回ACK的编码都是1001,那么也就意味着说主机B会一直向主机A索要1001~2000的数据。直到主机A向主机B发送了1001~2000的数据以后,主机B才会发送“正常”的ACK。这里不用担心数据会出现混乱的情况,我们前面也提到过TCP中的socket里面存在一个接收缓冲区(类似于阻塞队列),在这个缓冲区里面会根据序号重新进行排列和去重

我们将这样的机制称为“快速重传”,当传输的数据是非常密集的,按照滑动窗口这样的方法进行传输的话,进行重传操作就是快速重传。但是如果这里的数据是非常稀疏的,还是根据之前的“一应一答”的方式进行传输,那么重传操作还是根据超时重传机制来进行的

流量控制

滑动窗口如果窗口越大那么传输的数据就越多,那么传输效率也就越高

那窗口设置的越大越好吗?答案肯定是NO!

为什么?1.滑动窗口如果越来越大,那么可靠传输不能够保证

2.窗口越大传输所需要的消耗网络资源也就越多

3.数据一股脑地传输,接收方很有可能处理不过来,此时传了也是白传

那么应该怎么样来设计窗口的大小呢?答案是根据接收方的处理能力来计算发送方的滑动窗口大小具体方法是:根据接收缓冲区剩余大小来动态规划滑动窗口的大小是多少

接收方通过TCP报头中的窗口大小告诉发送方自己能够接收的数据大小是多少。在TCP报头中有专门的一项16位窗口大小,那么是否就以为着窗口大小只能是64kb呢?并不是,在TCP报头中还存在着“选项”这个选项,在选项里面我们引入窗口大小拓展因子就可以让窗口大小变得更大。比如在拓展因子写入2,那么窗口大小就从64<<2变成256kb大小了。

以这副图为例,应答报文每次会索要数据序号的同时更新窗口大小是多少,当窗口大小为0的时候,发送方就不会发送数据了,当接收方迟迟没有收到窗口大小更新的通知就会发送一个窗口探测报文,这个报文里面没有任何的业务数据目的只是为了触发ACK从而探测窗口大小

拥塞控制

可以说流量控制和拥塞控制共同控制了窗口大小是多少,流量控制是考虑接收方的处理数据能力是多少,而拥塞控制则是考虑在中间节点处理数据的能力

我们对接收方处理数据的能力是可以通过计算接收缓冲区的剩余大小进行量化的,但是我们对中间节点的处理数据能力很难进行量化,原因在于主机AB都处于共享的网络环境,因此有可能主机AB之间的通信会因为其他主机之间的通信而变得缓慢,如果主机AB之间发送一个很大的数据进行通信,那么很有可能就让整个网络环境瘫痪,所以这个时候就引入了拥塞控制机制。

拥塞控制本质上是使用实验的方法来进行测试中间节点的处理数据能力大小

首先TCP开始进行通信,第0轮窗口大小是1,收到确认应答报文以后,第二轮将窗口大小扩大一倍变成2,然后再次收到确认应答报文以后再将窗口大小扩大一倍,重复这样的操作直到触发超时重发机制。

这个时候会设置一个“慢启动阈值”,大小为触发超时重发机制时拥塞窗口大小的一半。这个慢启动阈值是用来限制拥塞窗口大小增长的斜率而存在的:由于拥塞窗口大小是从0开始指数增长的,而指数增长越到后面斜率越高也就意味着每通信一次发送的数据越多,所以为了避免网络环境瘫痪,设置慢启动阈值来限制拥塞窗口的增长速度。

每当滑动窗口的大小到达阈值以后,滑动窗口就以线性增长的方式来进行扩大,直到触发超时重传机制。

由于是进行拥塞窗口进行传输,所以在出现上一个数据丢包而后面数据并没有丢包的情况后,也会有快速重传这样的机制来对这种情况进行处理。出现这种情况后,慢启动阈值要重新设置,变成原来阈值大小的一半,而拥塞窗口的大小则变成此时(阈值+3数据段)的大小,并且此时拥塞窗口的增长还是按照线性增长的方式进行增长。

最后知道了流量控制和拥塞控制的大小之后,滑动窗口的大小就取这两个值的较小值作为滑动窗口的大小进行数据的传输。

延时应答

延时应答也算是提高TCP传输效率的一种方式。传统的滑动窗口,接收方的响应都是对发送方逐条进行响应,那么每次传输方都以接收方返回的窗口大小来进行传输下一批数据,此时的窗口大小一般都比较小因为缓冲区刚刚填满,那么延时应答的具体的做法就是接收方的ACK不再是逐条响应了,而是隔一条响应一次,这样做的好处就是接收方更够在隔一条响应的这段时间里面将接收缓冲区里面的数据处理更多,从而让返回的窗口大小变得更大,传输的数据更多,网络利用率更大。

捎带应答

捎带应答就是在延时应答的基础之上再次提高TCP的传输效率,如果不启动延时应答机制那么捎带应答也就无法完成,这样的好处在于提高网络利用率并且降低计算机负载

根据应用层协议,发送数据到对端,对端处理完数据以后会返回一个“回执”,那么在TCP协议中,这个回执可以和ACK一起返回给发送方。接收方在接收到数据以后并不立刻返回ACK而是等待回执的生成,回执生成好了以后ACK和回执一起返回给发送方。

面向字节流(粘包问题的处理)

TCP协议是面向字节流的,那么传输的数据都是一个字节一个字节的,我们对服务器写代码的时候调用socketAPI时对读数据操作如果不指定分隔符是什么的话,服务器就不知道每个TCP的报文应该读到哪里为止了,以这个图为例子

如果应用程序调用read,对第一个报文来说,如果读7个字节那么刚好读到一个报文,但是如果读6个字节那么就把报文读少了,如果读8个字节那么又把报文读多了。在socketAPI里面并没有告诉指定应该读多少个字节,读多少个字节完全是程序员自己设置的事情。

解决办法也很简单,约定好每个包长度是多少或者约定好分隔符是什么!这样就能解决粘包问题了

以约定好分割符为例,这里时约定\n作为分割符(可以参考我上一篇文章有详细的讲解)

处理异常

传输过程中出现了不可抗因素导致出现异常

具体有两种情况:1.进程崩溃了 / 主机关机了 2.主机断电 / 网线断开

进程崩溃那么也就意味着文件描述符也就释放了,相当于socket的close操作,这个时候操作系统内核还是会继续进行四次挥手的操作相当于是一个正常的断开。主机关机首先要进行关闭进程的操作,所以还是会重复上面的操作

主机断电可以理解成服务器出现故障,这个时候客户端传输数据后等待ACK的返回,但是由于一直没有收到返回的ACK于是触发超时重传机制,重传了几次过后依然没有收到ACK,此时尝试TCP重置连接操作,重置连接操作显示是失败的,最后客户端单方面放弃连接

网线断开可以理解是客户端出现故障,服务器在返回ACK后等待下一批数据的传输,结果迟迟没有收到继续等待,等到一段时间过后服务器会周期性的发送一个心跳包(确认通信双方正常连接)来确认对方是否还在正常工作,最后如果客户端一直没有回应服务器断开连接。 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值