文章目录
1. 运输层概述
前面主要介绍了应用层协议,这里着重介绍传输层相关的知识,主要介绍两种截然不同的可用传输层协议,用户数据报协议(UDP)和传输控制协议(TCP),编写网络应用程序时需要指定其中的一种。这儿特别要提到两个重要的概念。
- 多路复用:在源主机从不同的套接字中收集数据块,并将数据块封装成报文段后传输到网络层。
- 多路分解:将传输层报文段中的数据提供给正确的套接字。简单来说就是传输层会收到很多个报文段,它需要将这些报文端正确的提供给需要的套接字。
为了实现上述功能,需要满足以下要求:
- 套接字是唯一标识的,同一主机不存在相同的套接字。
- 传输层报文段中需要携带特殊字段用来指示该报文段需要交付的套接字。
一般来说,每个套接字都绑定一个端口号,当报文端到达主机时,传输层检查报文段中的目的端口号并定向到相应的套接字。下图描述了常见的多路复用和分解的过程,其中存在中两个不同的应用,DNS和HTTP,它们分别使用了UDP和TCP传输层协议。
在描述这两个协议之前,先看一些常见的网络应用以及对应的传输层协议。
应用 | 应用层协议 | 对应传输层协议 |
---|---|---|
电子邮件 | SMTP | TCP |
Web | HTTP | TCP |
文件传输 | FTP | TCP |
远程终端登陆 | Telnet | TCP |
网络管理 | SNMP | UDP |
域名解析 | DNS | UDP |
2. 用户数据报协议
UDP即用户数据报协议,其只做传输层协议能做的最少的事情,包括前面提到的复用/分解以及差错校验。UDP是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,其有以下特点,无需建立连接,不维护连接状态。
2.1 报文格式
UDP报文格式主要包括:源端口号、目的端口号、报文长度、校验和和数据几个部分。
- 源端口:端口号0-65535,1-1024保留端口号,为标准的服务端口,占16位。
- 目的端口:无须多解释,占16位。
- UDP长度:header+data 总长度,占16位。
- UDP校验和:伪头部,头部,data 三部分校验和,,占16位。
- 数据:上层应用层的数据。
这里解释一下伪头部,伪头部并非UDP报文中的有效数据,是提取了IP数据报中的源IP,目的IP信息并加上协议等字段构造的数据。伪头部在实际网络传输中,仅用作校验和计算使用,并不发送!因此称为伪头部。
2.2 校验和
校验和为运输层的传输提供了差错校验机制,用于检测报文从源端到目的端的过程中是否是否发生了变化,UDP只提供了差错检验机制,并不提供处理机制,通过下图描述校验和的原理。第一部分三行表示3个16比特的数据,第二部分表示前两个比特数据相加的结果,第三部分表示前两个比特数据相加的结果与第三个比特数据相加的结果,注意到最后一次加法有溢出,因此要被回卷,即将溢出部分与结果数据的最后一位继续相加。然后进行反码运算,即将得到的结果的1换成0,0换成1。因此得到的结果0100101011000010的反码是1011010100111101,这就是校验和。
特别注意,这种方式有时候也会存在欺骗的行为,比如说上述的三个比特数据在传输过程中,第一个比特数据和第二个比特数据的最后一个数据发生了反转,0变成1,1变成0,但是在计算校验和的时候却不会发生异常。
UDP的校验和只能检测出受损的报文,但是却没有相应的处理机制,接下来将介绍能为受损报文提供处理机制的TCP协议。
3. 传输控制协议
传输控制协议即TCP,是一种面向连接的可靠数据传输方式,在本节将首先介绍TCP的报文格式,对TCP有一个间接的印象,接着描述TCP实现面向连接的可靠数据传输方式的原理。
3.1 TCP报文
在介绍TCP报文格式之前,首先介绍一下基本概念。最大报文长度(MSS)即TCP能发送报文段数据的最大长度,MSS通常根据最初确定的由本地发送主机发送的最大链路层长度,即最大传输单元(MTU)来设置,值得注意的是MSS是指报文段里应用层数据的最大长度,并不包含TCP的首部字段。下图所示的是TCP报文相关字段以及解释。
TCP首部有两个重要的字段,序号字段和确认号字段。
- 序号是该报文段首字节的字节流编号。
例如一个3000字节的文件,假设MSS为1000,则文件被分为三个报文段,数据流的首字节流编号是0,则分配序号0,第二个报文段的数据流的首字节编号是1000,则分配序号1000,以此类推。 - 确认号是主机期望从另一个主机收到的下一个字节的序号。
接上面的例子,将设主机A向主机B请求数据,假设A已经收到了来自主机B的编号为0~467的所有字节,所以主机A等待主机B的数据流中字节468及之后的所有字节,所以A就会在发往B的报文段中的确认号字段填上468。
3.2 可靠数据传输原理
首先看下图表述的可靠数据传输的模型,从图中可以看出,传输层为上层应用从层提供了可靠的数据传输服务,但是在实现过程中是由传输层的数据传输协议控制的,因为下层的网络层并不提供可靠信道完成数据传输。而传输层想要完成可靠的数据传输则必须使用到上述报文中的相关字段,特别是序号和确认号。在具体描述之前,先介绍以下操作表示的内容:
-rdt_send: 发送方进程调用该方法确保将数据交付给接收方;
-udt_send: 表示通过不可靠信道完成数据的接收与发送;
-rdt_rcv: 表示从分组信道接收数据,这里的分组指的就是报文段;
-deliver_data: 传输层向应用层交付数据时使用。
为了更好的描述可靠数据传输原理,这里通过发送方和接收方的状态转化图来实现,将引起状态变化的事件放在横线的上方,将该时间所作的操作放在横线的下方,却少事件或对应的操作则为“ ^ ”,因此发送端和接收端的状态转化如下所示,其描述的是逐比特传输协议,即假设每次之发送一个比特。
发送端
对于发送端来说,其状态演变的过程如下,
- 处于等待应用层的调用状态时收到接收方的数据包,发送方什么也不做。
- 被应用层调用时,假设分组序号就为0,发送方调用rdt_send(data)发送数据,此刻会使用make_pkt制作一个数据包sndpkt,数据包包括序号、数据和校验和,接着使用udt_send()发送数据并开启一个定时器。
- 接着传输层会等待序号0的数据包是否到达的响应,这里存在三种情况:
- 收到接收方的数据rdt_rcv()、数据没有发生差错notcorrupt()、是对接收到序号0的数据包的反馈,此刻发送方会关掉定时器,等待上层应用的再次调用。
- 收到接收方的数据rdt_rcv(),但是数据受损corrupt()或者不是对发送的序号为0的数据包的响应isAck(),则发送方什么也不做。
- 当定时器超时时,发送方重新发送序号为0的数据包。
后续发送方会跟之前的方式一样发送序号为1的数据包。
接收端
对于发送端来说,其状态演变的过程如下,
- 对于正在等待接收序号为0的数据包的接收方来说,接收方接收到发送方发送的数据rdt_rcv()、数据没有损坏notcorrupt()、并且数据包的序号也是正确的has_seq0(),此刻接收会给提取数据包extract(),然后将数据包交给上层应用deliver(),并且制作返回给发送方确认的数据包make_pkt(),数据包包括对于序号为0的发送方的数据包的确认和校验和,然后调用udt_send()发送数据。
- 对于正在等待接收序号为0的数据包的接收方来说,接收方接收到发送方发送的数据rdt_rcv()、但是数据损坏了corrupt()或者数据包的序号不是的has_seq0(),此刻发送方制作返回给发送方确认的数据包make_pkt(),数据包包括对于上一次已经确认收到的序号为1的发送方的数据包的确认,即重复确认,以及校验和,然后调用udt_send()发送数据,发送方收到重复确认时就知道接收方没有收到该分组后面的分组。
这里说几个值得注意的地方:
- 提供了重传机制,这也是可靠传输协议的精髓所在,该机制通过通过以下几个方面触发,差错校验、接收方反馈、定时器超时。
- 引入了序号,接收方可以根据序号判断数据包是否是重传的数据包。
- 引入定时器,有效解决了分组丢失的情况。
下图描述了接收方和发送方遇到的几种情况,左边为发送方,右边为接收方。
3.3 差错恢复
本节称为差错恢复,所以首先描述为什么需要差错恢复,因为在前面的可靠数据传输原理的地方已经描述了对于差错的处理,这里为什么还要在描述呢?这是因为前面描述的是逐个分组的数据传输,试想一下,发送方逐分组发送,并且只有在收到该比特确认的时候才会发送下一个分组,这个效率是多么底下。由此引入了流水线式的传输方式,如下图所示,
要想实现该方式传播,需要提供以下保证:
- 增加序号的范围,因为每个传输的分组必须要有唯一的一个序号,而且也会存在多个未被确认的分组,也会需要需要标识;
- 发送方和接收方都要提供缓存机制,以缓存多个要发送或者要接受的分组,发送方最低需要缓存那些已经被发送发送但是没有被确认的分组,以保证分组故障时可以重传。接收需要缓存那些已经正确接收到的报文;
- 分组丢失、损坏或者超时时,需要进行重传。
关于流水线的差错恢复有以下两种方式:回退和选择重传。
3.3.1 滑动窗口协议
回退方式也称为滑动窗口协议,其允许发送方发送多个分组而不需要等待每个都得到确认,但是在整个流水线中未确认的分组数不能超过某个允许的最大值N,在介绍该方式的传输时,先通过下图了解一些基本概念,该图表示的是发送方维护的窗口。首先是基序base,其表示最早未被确认的分组,下一序号next_seq表示最小未被使用的序号,也就是下一个可以发送的序号,由此数据被分为四个部分:
- [0,base - 1]表示发送已经被确认的分组;
- [base,next_seq - 1]表示发送还没有被确认的分组;
- [next_seq,base + N - 1]表示要被发送的分组;
- [base + N,……]表示暂时不能被发送的分组。
这里将那些已经被发送的但是还没有被确认的分组的许可范围看成是一个窗口,N被称为窗口长度。随着base分组不断被确认,该窗口也在不断向前滑动,由此得到了滑动窗口协议。
在滑动窗口协议中,如果一个分组n被正确接收到,并且按序(即前一个交付的序号是 n-1 ),则回复该分组的ACK,并且将该分组提供给上层使用。在其他情况下,接收方会丢弃该分组,并为最近按序接收的分组重新发送ACK。这里每一次收到正确的分组都会立即交付,这也是累计确认的一种表现。
该方式差错恢复中,只有发送方维护一个滑动窗口,接收方并没有,因此当接收方接收到一个不是按序的分组时就会丢弃该分组,因为其没有缓存的能力,并且会向发送方发送一个期望收到的序号以及上一次已经确认收到的序号的发送方的数据包的确认ACK。
这里举一个简单的例子说明一下,对于一个长度为4的滑动窗口,假设分组序号是0 - n,则发送方首先会发送序号0 - 3的分组,因为窗口长度为4, 当收到分组0的ACK时,窗口则向前滑动,此刻窗口覆盖的序号为[1,4],则发送方可以发送序号为4的分组。若此刻序号为1的分组丢失,当接收方接收到序号为2的分组时,发现不是按序到达的则丢弃,并向发送方发送序号为0的分组的确认ACK,以及期待下一个分组序号的1的Seq。当接收方接收到序号为3的分组时,也是做相同的处理。一段时间后,发送方的维护序号为1的分组的定时器会超时,会触发序号为1的分组重传,并且接着重新发送序号为2、3、4的分组,发送此刻若接收方接收到序号为1的分组,发现是按序的,则回复对应的ACK和下一个期望的序号。
3.3.2 选择重传
前面介绍了滑动窗口协议,但是这存在一个问题,就是许多已经被接收方接收到的分组被丢弃了,这就导致了一定的性能问题。而选择重传只是让发送方重传那些它认为接收方接收出错的分组,从而避免不必要的重传。而这个则需要接收方也维护一个缓存,即窗口,去保留这些已经接收到的分组。发送方和接收方看到的序号空间如下图所示。
发送方的事件与动作
- 从上层收到数据。从上层收到数据后,发送方查看下一个可用分组的序号。如果序号位于发送方的窗口内,则将数据打包发送;否则类似滑动窗口协议,要么将数据缓存,要么将其返回给上层以便以后传输。
- 超时。超时重传。
- 收到ACK。收到ACK,若该分组序号在窗口内,则发送方将被确认的分组标记为已接收。如果分组序号等于send_base,则窗口基序向前移动到最小序号的未被确认分组处。如果窗口移动了,并且有序号落在窗口并且没有发送,则放松这些分组。
接收方的事件与动作
- 序号在[rcv_base,rcv_base + N -1 ]内的分组被正确接收。在此情况下,收到的分组落在接收方的窗口内,一个选择ACK被回送给发送方。如果该分组以前没有收到过,则缓存该分组。如果该分组的序号等于接收窗口的基序,则该分组以及以前缓存的序号连续的分组交付给上层。然后,接收窗口向前移动分组的编号向上交付这些分组。
- 序号在[rcv_base - N,rcv_base - 1]内的分组被正确接收。在此情况下,必须产生一个ACK,即使该分组是接收方以前确认过的分组。
- 其他情况,丢弃分组。
下图表示的是一个选择重传的示例,注意重传分组2收到时必须回应ACK2,不然发送方的窗口不能继续向前滑动。
3.5 TCP 原理解析
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP旨在适应支持多网络应用的分层协议层次结构。 连接到不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。
3.5.1 TCP 数据传输
1. 报文重传分析
- 情况一超时时间内ACK丢失,如下图左边的图所示,序号为92的分组将会重传,接收会再次返回ACK。
- 情况二超时时间内,没有接收到ACK,下一次超时时间内收到确认号,如下图中间的图所示,首先在第一个超时时间内没有收到序号为92的分组,而在第二个超时时间内收到该ACK。对于分组序号为92的分组仍然会被重传,但是响应ACK为120,以表明序号为120之前的分组都已经接收到了。
- 情况二超时时间内,没有接收到序号为92的分组的ACK,但是收到了序号为100的分组的ACK,这表明序号100分组之前的分组都已经被接收,所以即使没有接收到序号为92的分组的ACK,该分组也不会重传。
2. 超时间隔加倍
TCP重传的具有最小序号的还未被确认的分组。只是每次TCP重传都会将超时时间间隔设为原来的**两倍。**不用担心超时时间会被无限放大,TCP对于超时时间有相关的处理,这里不再赘述。
3. 快速重传
依赖超时重传的方法往往超时周期相对较长,分组丢失容易增加端到端的延迟。因此TCP引入了冗余ACK机制,冗余ACK就是再次确认某个报文的ACK,而发送方已经收到了对该报文的确认。
当发送方经常一个接一个地发送大量的分组,如果一个分组丢失,就有可能引起许多一个接一个的冗余ACK。如果TCP发送方收到对相同数据的3个冗余ACK,它把这当作一种指示,说明跟在这个已被确认过3此的分组之后的分组已经丢失。一旦收到3个冗余ACK,TCP就执行快速重传,即在该报文的定时器过期之前重传丢失的分组。
4. 使用滑动窗口协议还是选择重传
TCP使用的是那种方式呢?TCP确认是累积的,正确接收但失序的报文段是不会被接收方逐个确认的。因此,TCP发送方仅需维持已发送过但未被确认的字节的最小序号和下一个要发送的字节的序号。这点与滑动窗口协议相似。
需要注意的是,TCP的接收方会将失序到达的分组缓存起来,这点与滑动窗口协议存在差异。
另外假设发送方发送一组分组1,2,……,N,并且所有的分组都按序无差错地到达接收方时会发生的情况。假设分组n < N丢失,但是其余N - 1个确认报文在分别超时以前到达发送端,这时滑动窗口协议不仅会重传分组n,还有重传n之后的分组。但是TCP将重传最多一个分组,即分组n。此外,如果对分组n + 1的确认报文在分组n超时之前到达,TCP则不会重传分组n。
3.5.2 TCP 三次握手与四次挥手
三次握手
三次握手就是TCP建立连接的过程,主要如下图所示,
- 第一次握手:客户端TCP向服务端发送一个特殊的TCP报文段,其中SYN字段被置为1,另外ia客户端随即选择一个初始序号client_isn,然后发送给服务端。
- 第二次握手:服务端收到SYN = 1的报文,知道这是一个请求连接的报文,所以服务端会为该连接分配缓存和变量,并且回复报文段,会置SYN字段为1,确认号被设置为client_isn+1,最后服务端选择自己的初始序号,该报文被称为SYNACK报文段。
- 第三次握手:客户端在收到SYNACK报文段之后,也要为该连接分配缓存和变量。并向服务器发送另外一个报文段,这个报文段允许携带数据,因为已经建立连接,SYN会被置为0。
四次挥手
四次挥手就是TCP释放连接的过程,主要如下图所示,
由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭,下图描述的即是如此。
- 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
- 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
- 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
- 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
3.5.3 TCP 流量控制与拥塞控制
因为流量控制与拥塞控制存在相似之处,因此本文放在一起描述。
流量控制
TCP提供了一个重要服务,即流量控制,其目的是消除发送方使接收方缓存溢出的可能性。
前面已经提到了窗口的概念,而流量控制则是通过维护发送方的窗口大小来实现的。首先需要明白,TCP是全双工通信的,在连接的两端都各自维护一个接收窗口。接收窗口用于给发送方一个指示,该接收方还有多少可用的缓存空间。
定义以下变量:
- LastByteRead:从缓存读出的数据流的最后一个字节的编号;
- LastByteRecv:接收窗口中的数据流的最后一个字节的编号。
- LastByteSent:发送窗口中目前发送的数据流的最后一个字节的编号;
- LastByteAcked:发送窗口中发送的数据流中收到确认的最后一个字节的编号;
不允许分配的缓存溢出,则,
当接收方窗口大小为0时,发送方会继续发送只有一个字节的报文段,这些报文段会被接收方确认,当接收方缓存被清空时,会在确认报文里包含一个非0的rwnd值。所以并不是接收方窗口大小为0时,发送方就不再发送数据了。通过这种方式,TCP完成发送方与接收方的流量控制
拥塞控制
TCP另一个关键部分就是拥塞控制机制,所采用的方法就是让每一个发送方根据感知到的网络拥塞程度来限制其向网络发送流量的速率。运行在发送方的TCP拥塞控制机制跟踪一个额外的变量,即拥塞窗口,用cwnd表示,它对一个TCP发送方能向网络发送数据的速率进行了限制,在一个发送方中未被确认的数据量不会超过cwnd与rwnd的最小值,即
发送方感知丢失的报文段来感知网络是否拥塞,对于给定的报文段,超时或者三个冗余的ACK都会被隐含的解释为丢包,以此控制拥塞窗口达到控制发送速率的目的。拥塞控制主要包括以下几个步骤。
1. 慢开始
慢开始的含义就是讲窗口先设置为1,每个传输轮次大小增长一倍,直到拥塞窗口大于慢开始的门限(ssthresh),这时候慢开始阶段结束。在该阶段,当发生超时丢包时,TCP会将阈值ssthresh设置为拥塞窗口cwnd的一半,并将拥塞窗口cwnd设置为1,重新进行慢开始;当cwnd大于等于ssthresh时候,进入拥塞避免状态。
2.拥塞避免
慢开始结束后,接下来就是拥塞避免,这个阶段拥塞窗口在每个传输轮次数量加1,直到触发了网络拥塞,窗口大小和门限都变为拥塞时最大的值得一半,然后重新开始慢开始阶段。若发生超时的丢包则与慢开始相同的处理。
3. 快恢复
快恢复就是再发生拥塞和重传时,窗口经历了拥塞避免阶段,然后进入快恢复阶段,和拥塞避免一样都是每次加一,这样能提高恢复速度,但是老版本(Tahoe)中需要重新经历慢开始。
下面的状态转化图详细的描述了三者之间的协作:
最后通过一个案例结束本节的描述:
下图表述了拥塞窗口的演化情况,初始阈值ssthresh = 8 MSS,cwnd = 1 MSS。
- 在前8个传输回合属于慢启动状态,拥塞窗口在慢启动状态以指数速度快速爬升,在第四轮到达阈值,进入拥塞避免状态。
- 拥塞避免状态以线性速度快速爬升,到第8轮传输后出现了3个冗余ACK。此时发生丢包事件,cwnd = 12 MSS, ssthresh设置为 cwnd / 2 = 6 MSS.
- 根据TCP Reno下的拥塞处理策略,cwnd 将被设置为9 MSS,然后线性增长;
根据TCP Tahoe下的拥塞处理策略,cwnd 将被设置为1 MSS,然后从慢启动再次开始传输。
扩展
扩展一、TCP与UDP的区别
- TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接;
- TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付;
- TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的;
- UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低;
- 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信;
- TCP首部开销20字节;UDP的首部开销小,只有8个字节;
- TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道;
扩展二、流量控制与拥塞避免的区别
-
流量控制是为了解决发送方和接收方速度不同而导致的数据丢失问题,当发送方发送的太快,接收方来不及接受就会导致数据丢失,流量控制用滑动窗口的形式解决问题。
-
拥塞控制是为了解决过多的数据注入到网络,导致网络过载。如果发送方发送的数据大量的数据注入到网络,不加限制,网络直接的体会就是变卡,拥塞控制的用的是拥塞窗口解决该问题。