运输层服务
- 运输层协议为运行在不同主机上的进程之间提供逻辑通信,即从应用程序角度看两个主机好像直连一样,实际可能相隔万里
- 运输层协议是在端系统上实现的,而不是路由器,为什么这么强调,因为运输层会将应用报文划分为较小的块然后加上一个运输层首部来生成运输层报文段,再传给网络层,网络层封装网络层分组(数据报)再发送,而网络路由器仅作用于网络层字段,不会检查运输层报文段的。
运输层和网络层的关系
- 网络层为主机之间的逻辑通信,运输层为运行在主机上的进程之间提供逻辑通信。
- 举例:两个家庭写信联系,两个家庭各有一个孩子A和B进行收集家里人的信件发送或收到信件分发给家里人,这里家庭就是端系统,家里人就是进程,A和B是运输层协议,而负责送信的邮政服务就是网络层协议。
- 按照上述例子补充几点:
- 例子里A和B都不会参与运输的工作,对应到运输层协议也只工作在端系统中。
- 如果A和B有事不在,别的孩子C和D来接替工作,但是C和D太小事情处理的不够细致,会出现信件丢失错发等情况,这在计算机网络也有对应,运输层协议其实有多种,每种协议能提供不同的服务模型。
- 如果邮政服务送的慢,A和B也没有办法,这说明运输协议提供的服务其实受制于底层网络层协议的服务模型。网络层协议要是不能给运输层报文段提供时延、带宽保证,运输层协议就也没法提供。
- 并不是网络层不能提供的服务运输层也一定不能提供。比如网络层协议是不可靠的,会让分组丢失、冗余等等,但是运输层可以为程序提供可靠传输服务。
运输层概述
- 讲运输层前先灌输点网络层知识:网络层协议是IP协议,IP的服务模型是尽力而为交付服务,意思是尽可能地再通信主机之间交付报文段,但是不做任何保证,不确认报文段有没有正确交付、按序交付、完整性等等,所以是不可靠服务。
- 之所以先灌输了点IP的知识是因为运输层的UDP和TCP实际就是将两个端系统间IP的交付服务扩展承运行在端系统上进程间的交付。这种主机间扩展到进程间的特点叫做运输层的多路复用和多路分解。
多路复用和多路分解
- 运输层从下面的网络层接收报文段,然后将报文段中的数据交给主机上允许的进程。
- 举例:假设现在在下载Web页面,运行一个FTP会话和两个Telnet会话,这样就有四个进程(一个FTP进程、两个Telnet进程、一个HTTP进程),当运输层从网络层接收数据时它会将接收到的数据对应的传到四个进程的四个套接字上。
- 报文段是怎么定位到自己该去的套接字呢:每个报文段都有几个字段,接收端运输层会检查这些字段,标识出接收套接字,进而准确定位。将运输层报文段中的数据交付到正确的套接字上这个工作叫多路分解。
- 源主机从不同套接字接收数据块,并给每个数据块封装首部信息来生成报文段,然后传递到网络层,这个工作叫多路复用。
- 其实可以用寄快递的方式记住:从代收点取得多个快递,然后根据姓名分发给家人的操作交分解;从家人处收集快递给寄件员叫多路复用。
- 分解/复用原理:1. 套接字有唯一标识符;2. 报文段有特殊字段(源端口号和目的端口号)来指示要去的套接字。
- 主机上每个套接字能分配一个端口号,报文段到达主机时运输层检查报文段中的目的端口号,再定向到对应套接字。报文段中的数据再通过套接字进入对应进程。
- 无连接的多路复用和多路分解
UDP套接字是由一个二元组来标识的,该二元组包含一个目的IP地址和目的端口号,因此如果两个UDP报文段有不同的源IP地址/源端口号,但是有相同的目的IP地址和目的端口号,那也会传到同一个进程上去。这就是无连接的。多提一句:源端口号的作用是比如A发送报文段到B,则B再发给A的时候会从源端口号取值。 - 面向连接的多路复用和多路分解
TCP套接字和UDP套接字不一样,TCP套接字是由四元组组成的,在UDP的基础上多了源IP地址和源端口号,所以不会出现源IP源端口不同但目的IP目的端口相同的情况时传到同一进程的情况,除非TCP报文段携带了初始创建连接的请求。
服务器主机支持很多并行的TCP套接字,每个套接字和一个进程相连,当一个TCP报文段到达主机时所有四个字段都被用来将报文段定向到套接字。
无连接的运输:UDP
- UDP只做了运输层能做的最少工作——复用/分解功能和少量的差错检测,除此以外没有对IP增加别的东西。UDP从应用进程得到数据附加上用于多路复用/分解的源和目的端口号字段和两个其他的小字段,然后将形成的报文段交给网络层。
- 因为使用UDP在发送报文段前,发送方和接收方的运输层不会进行握手,所以UDP被称为无连接的。
- DNS采用UDP的例子:查询服务器中DNS程序构造一个DNS查询报文并交给UDP,进行部分添加后发给网络层,网络层将UDP报文段封装进IP数据报,然后发给一个名字服务器,之后查询主机中的DNS程序等待对该查询的响应,如果没有收到响应则要么向另一个名字服务器发送查询要么通知调用的程序响应失败。
- TCP比UDP可靠的多,为什么不用TCP呢:
- TCP有拥塞控制机制,如果发送方接收方直接存在链路堵塞,会遏制发送方,而有些应用要求实时性,且能容忍部分数据丢失,所以TCP并不合适。
- TCP在传输前要建立三次握手,UDP不会,所以UDP避免了建立连接时的时延。这就是DNS使用UDP的主要原因,HTTP使用TCP是因为对文本数据的可靠性有更高要求。
- TCP报文段光首部就有20个字节,UDP只要8个。
- UDP也是可以实现可靠数据传输的,通过在应用程序本身中建立可靠性机制(确认和重传机制)来实现。
UDP报文段
- 应用层数据占用了UDP报文段的数据字段,比如DNS应用就是查询/响应报文,流式音频就是音频数据。
- UDP首部有四个字段,每个字段2字节。因为数据字段的长度在一个UDP段中不同于另一个段中,所以需要一个明确的长度。接收方使用检验和来检查报文段是否出现错误。
UDP检验和
- 报文段的传输过程中比特可能发生改变,比如链路中的噪声干扰或者存储在路由器中时发生问题,发送方的UDP对报文段中的所有16比特字的和进行反码运算,求和时遇到溢出问题要回卷(溢出的最高位1再和最高位1和低16位做加法运算)。
- 检验方式:三个16比特0110011001100000、0101010101010101、1000111100001100相加为10100101011000001,一共17位,溢出一位,回卷将最高位的1再和后16位运算得0100101011000010,反码后得到检验和1011010100111101,在接收端将再算一遍三个16比特位,然后和报文段中的检验和相加,如果不是全为1就说明出错。
- UDP只提供差错检测,并不提供恢复功能,有的UDP对出错的措施是丢弃受损报文段,有的是将受损报文段交给程序并提出警告。
可靠数据传输原理
- 首先认识服务模型,分组到达信道的接收端时会调用rdt_rcv()函数接收。当rdt协议(可靠数据传输协议)想要高层交付数据时,将通过调用deliver_data()完成。
- 本节仅考虑单向数据传输(数据传输从发送端到接收端)
完全可靠信道的可靠数据传输rdt1.0
现在是最简单的情况,底层信道是完全可靠的,称为rdt1.0。
- 发送方和接收方都有各自的FSM(自动状态机),箭头指示了从一个状态到另一个状态的变迁,引起变迁的事件在横线上方,事件发生后采取的动作在下方。左上角的虚线箭头表示FSM的初始状态。rdt的发送端通过rdt_send接受来自高层的数据,然后通过make_pkt产生一个包含该数据的分组再udt_send发送到信道中。接收端上rdt通过rcv接收分组,通过extract从分组中取出数据再将数据deliver到高层。
- 之所以会说这个模型最简单是因为信道完全可靠,不需要提供任何反馈信息给发送方。
具有比特差错信道的可靠数据传输rdt2.0
- 像我们平时说话一样,听清了就来点反馈让说话人继续,没听清就会让说话人再说一遍。听清楚表达的反馈叫肯定确认,没听清让重复一遍叫否定确认。计网中基于这种重传机制的可靠数据传输协议叫自动重传请求协议(ARQ)。
- ARQ还需要三种协议功能来处理比特差错情况:
- 差错检测。一种机制能判断何时出现了比特差错。
- 接收方反馈。就是上面说的肯定确认和否定确认。
- 重传。否定确认后重传该分组。
- 发送端有两个状态,左边的再等待来自上层传下来的数据,当rdt_send事件出现时同样产生一个包含数据的分组,但是这里多了checksum检验和。注意这里用udt_send发送出去,然后切换到等待回应的状态。如果回复是NAK就重传,否则回到左边继续等待上层传数据。
- 注意一点:两个状态不能同时共存,所以在等待ack和nak消息的时候,不能接收来自上层的消息,并且只有收到ack后才能再发送数据。由于这样的原因所以rdt2.0也叫停等协议。
- rdt2.0看似可行,但别忘了,ACK和NAK也会受损!看看有没有方法解决这个问题,比如增加足够多的检验和比特,使发送方不仅能检测差错,还能直接恢复差错,对会产生差错但不丢失分组的信道这可以解决问题。还有就是如果收到了模糊的ack或nak,直接不管三七二十一重传分组,但这又会使分组冗余,让接收方不知道这个分组是新的还是重传的。
- 解决这个问题就是再数据分组中添加一个字段,让发送方对数据分组编号,将发送数据分组的序号放在该字段。接收方只要检查序号就能确定收到的分组是否是一次重传。像停等协议这种一比特序号就够了。由此引入rdt2.1版本:
- 当接收到失序分组时接收方会发送一个肯定确认,如果受到受损分组接收方将发送一个否定确认。但如果不发送NAK而是对上一个分组又发送了个ACK,也能起到同样的效果。同一个分组两个ACK叫冗余ACK,说明接收方没有正确接收。
- rdt2.1和rdt2.2之间的变化是接收方必须包括由一个ACK报文所确认的分组序号(make_pkt()中包含参数ACK0和ACK1实现),发送方必须检查接收到的ACK报文中被确认的分组序号(在isACK中包含参数0或1来实现)。
经具有比特差错的丢包信道的可靠数据传输rdt3.0
- 这里考虑了除比特受损外的底层信道丢包情况,所以要处理检测丢包以及发生丢包后该做些什么。
- 假设发送发传输了一个分组但丢失了,发送方可以等待足够长的事件来确定分组是否丢失,但应该等待多久呢?至少是发送方到接收方之间的往返时延+处理分组的时间。但这时间可能过于漫长,比较好的做法是定一个时间,如果超过这个时间没收到就重传。
- 重传真的很好用,不管是分组丢失还是ACK丢失还是分组/ACK传输超时了,都可以重传解决。实现重传使用了一个倒计数定时器的东西,每次发送一个分组就启动一个定时器。
- 总结一下数据传输的要点:检验和、序号、定时器、肯定否定确认。每个机制都必不可少,一同构造可靠的数据传输。
流水线可靠数据传输
- rdt3.0已经符合可靠的要求了,但是效率不够,因为他是个停等协议,要等ack之后才会发送下一个。但我们利用起等待ack这段时间发送,也就是发送方发送多个分组而无需等待确认。如下图二。
- 流水线对可靠数据传输协议可带来以下影响:
- 增加序号范围。因为每个输送中的分组必须有一个唯一的序号,而且可能有多个在传送中没有确认的报文。
- 发送方和接收方两端要能够缓存多个分组。发送方应当能缓存那些已发送但还没确认的分组,接收方缓存已经正确接收的分组。
- 序号范围要多大、对缓冲的要求取决于数据传输协议如何处理丢失、损坏以及延时过大的分组。解决流水线的差错恢复有两种办法:回退N步和选择重传。
回退N步
- 回退N步协议GBN允许发送多个分组并且不需要等待ack,但也受限于在流水线中未确认的分组数不能超过N(最大允许数)。
- 图上是GBN协议的序号范围。基序号定为最早的未确认分组,下一个序号是最早的未使用序号。整个序号分为4段,1段是已经发送并且已经确认过的分组,2是已经发送但还没确认的分组,3是还未发送的分组,4是不使用的分组。2段和3段就是长度为N的窗口(最多有N个待确认分组),随着协议运行窗口会向前移动。所以N又叫窗口长度,GBN协议也叫滑动窗口协议。
- 我们为什么要限制2段3段的长度为N,为什么不能是无限制的呢?以后就会知道流量控制是施加这个限制的主要原因。
- 然后根据将GBN加入到FSM中得到如下两图。发送方需要响应三种类型的事件。
- 上层调用rdt_send()时发送方要检查发送窗口(上图第2段)是否满,即是否有N个已发送但未被确认的分组,窗口没满就产生一个分组并发送;满了就将数据返回上层,表示已经满了。理论上如此,现实中发送方更可能缓存这些数据,或者用同步机制让上层在窗口不满时才调用rdt_send()。
- GBN协议对序号为n的分组的确认采取累积确认的方式,表明接收方已正确接收到序号为n及之前的所有分组。
- 之所以叫回退N步是因为类似停等协议,定时器会用于恢复数据或确认分组的丢失。如果超时,发送方会重传所有已发送但未确认的分组。如果收到一个ACK,但仍然存在已发送没确认的分组,则重新启动定时器。如果没有则终止定时器。
- 接收方接收到一个序号n的分组,并且上一个分组的序号是n-1(按序),则接收方会为分组n发送ack,然后打包交付给上层。其他情况则会丢弃该分组,然后为最近收到的分组重新发送ack。因为确认一次就交付一次,所以比如k交付了,说明k之前的也都交付了。所以有了累计确认这个功能。
- GBN中接收方会丢弃所有失序分组,虽然正确接收的分组也被丢弃了有点蠢,但GBN必须按序将数据交付给上层。比如期望收到n但n+1先到了,接收方缓存n+1,收到n并交付之后交付n+1。但如果n丢失了,则n和n+1则会被发送方根据GBN规则重传,反正会重传,接收方丢弃n+1就行了。这样接收方不需要缓存任何分组,并且只要维护期望的分组序号(expectedseqnum)这一个变量,简单方便。
- 演示一个GBN运作案例。窗口长度为4,发送方先发送0-3,在继续发送之前要等到至少一个分组的确认,接收到ack后就向前滑动窗口,并发送新的分组。示例中收到了0和1,于是发送4和5,但由于2丢失了,所以3、4、5都为最近的被收到的1发送ack,因此3、4、5都被识别为失序而被丢弃,发送发重传。
选择重传
- GBN协议仍然存在效率问题,单个分组的差错就会引起大量重传,信道差错率增加,流水线就会被不必要重传的分组充斥。举例来说就是写错一个字,那前后1000个字都要重写。
- 选择重传(SR)协议通过让发送方仅重传那些怀疑在接收方出错的分组来避免多余的重传。但是这种个别、按需的重传需要接收方逐个确认正确接受的分组。
- SR发送方动作:发送方会从上层接收数据,检查下一个用于该分组的序号,序号位于窗口内就将数据打包发送,否则像GBN一样要么缓存要么返回给上层;定时器还会被用来防止分组丢失,但现在是每个分组都有自己的逻辑定时器,因为发送超时后只能发送一个分组。收到ACK后如果分组序号在窗口内,则SR发送方将那个被确认的分组标记为已接收。如果该分组序号等于send_base,则窗口基序号向前移动到具有最小序号的未确认分组处。如果窗口移动了并且有序号落在窗口内的未发送分组,则发送这些分组。
- SR接收方动作:分组如果落在[rcv_base, rcv_base + N - 1]中,会返回一个ACK给发送方,如果该分组没收到过则缓存该分组,如果分组序号等于基序号(rcv_base),则该分组和以前缓存的序号连续(起始于rcv_base)的分组一同交付给上层。然后接收窗口按向前移动分组的编号向上交付这些分组,举例如下图中分组2丢失的情况;分组落在[rcv_base - N, rcv_base - 1],必须产生一个ACK,即使接收方以前确认过;其他情况忽略该分组。
- SR接收方确认一个正确接收的分组而不管是否有序,如果失序就缓存知道这个分组之前的分组都收到,然后一批分组按序交给上层。
面向连接的运输TCP
TCP连接
- 之所以说TCP是面向连接的是因为在一个程序向另一个程序发送数据前这两个进程要先相互握手(相互发送预备保温来建立确保数据传输的参数)
- TCP只在端系统中运行而不在中间的网络元素上(路由器和链路层交换机)运行,所以中间的网络元素不会维持TCP连接状态。
- TCP连接提供的是全双工服务。TCP也是点对点的连接,不存在多播的情况。
- 两个进程建立起TCP连接后就能相互发送数据了,客户进程通过套接字(进程的门)传递数据,数据通过该门就由客户中运行的TCP控制。TCP将这些数据引导到该连接的发送缓存中。然后TCP会时不时的从发送缓存取数据。
- TCP从缓存中取出并放入报文段中的数据量受限于最大报文段长度MSS,MSS通常根据最初由本地发送主机发送的最大链路层帧长度(即最大传输单元MTU)确定。MSS要保证一个TCP报文段和TCP/IP首部(40字节)合起来适配单个链路层帧。以太网和PPP链路层协议都有1500字节的MTU,所以一般MSS是1460字节。
- 注意MSS是报文段里应用层数据的最大长度,而不是包括TCP首部的报文段的最大长度。