计算机网络 --第三章

一、概述

运输层协议是在端系统中而不是在网络路由器中实现的, 路由只实现到网络层
应用层报文传到运输层后,经过运输层加工后,称为 报文段(segment)。

运输层会 应用层 报文划分为较小的块,并为每块加上一个运输层首部来创建运输层 报文段(Segment)。然后再继续向下传递,给网络层,网络层再进行封装,等等,最终,发送出去。

路由仅作用于该数据包的网络层字段,即 不检查封装在数据包的 运输层报文段的字段。

1、运输层和网络层的关系
运输层在网络层之上。 运输层为运行在不同主机上的进程之间提供了逻辑通信,而网络层则提供了主机之间逻辑通信

解释下逻辑通信,逻辑通信就是,比如运输层,它不管有没有底下的网络层,不管底下层的协议怎么实现的,反正他做出个Segment,传给网络层,对面目标的网络层就能收到,就好像两个运输层是在进行直接的通信一样,这就是逻辑通信。

如上面那话说的,网络层提供主机之间的逻辑通信,意思即是,网络层服务的是所有进程,同个主机上所有进程的报文发给网络层,它都送到目标机上去。

而运输层提供进程间逻辑通信,是说,两个主机间通信,从网络层发来好多报文,这些报文是给不同进程的,运输层要将这些报文发给不同的进程。对于某个进程来说,它不管下面还有哪些协议,协议怎么实现的,它只认识运输层,只知道把报文给他就能送到目标的对应进程上。

对于上述问题,书里给的例子挺形象的,P125。

上层的运输层能提供的服务,是受到底下网络层协议限制的,但是运输层还是能提供些底层没有的服务,比如:
  • 网络 层协议会使分组丢失、混乱和重复,但是运输层还是能提供可靠的传输服务。
  • 网络层不能保证运输层报文段的机密性,但是运输层还是可以提供机密传输的服务
2、运输层概述
在此书中,将TCP和UDP的分组通称为报文段(Segment),这句话可不是在重复,是有的地方把TCP的分组称为报文段,UDP分组称为数据段。。。。。。

网络层协议 IP,全称 网际协议,提供主机之间逻辑通信,其服务模型是 尽力而为交付服务(best-effort delivery service),不保证报文段一定能到。

特别是: IP不保证报文的交付、不保证报文段按序交付、不保证报文段中数据的完整性

所以IP为不可靠服务(unreliable service)。

收集各进程报文,发送给网络层过程叫做运输层的 多路复用(transport-layer multiplexing),从网络层拿到解析出报文段后,分派给不同进程的过程,称为 多路分解(demultiplexing)。

TCP 提供一些特殊服务:
  •      提供可靠数 据传输(reliable data transfer);
  •      拥塞控 制;

二、多路复用与多路分解

将运输层报文段中的数据 交付到正确的 套接字的工作称为 多路分解(demultiplexing)。

从源主机的 不同套接字收集数据块,并为每个数据块封装上首部信息(将在多路分解时使用)从而生成报文段,从而将报文段传递到网络层的工作称为 多路复用(multiplexing)。

可以看到多路分解和多路复用都是和套接字相关的,于是多路复用的要求有:
  1. 套接字有唯一标识符
  2. 每个报文段有特殊字段,来指示该报文段所要交付的套接字。
这些特殊字段是 源端口号字段 (source port number field)和 目的端口号字段 (destination port number field)。
运输层 报文段(segment),大概 样貌
注意:这个图不全奥,Other header fields都没写是什么,就先给看一个大概。

0 ~ 1023 范围的端口号称为 周知端口号(well-known port number),是受到 严格限制的。

多路分解基本过程
  1.      主机每个套接字,都被分配一个端口号
  2.      有报文到达,主机运输层检查报文段中的目的端口号,并将报文送入该套接字
  3.      报文中数据,通过套接字进入所连接的进程

                                                       无连接的多路复用和多路分解



注意:一个 UPD套接字是由一个包含 目的IP地址目的端口号的二元组来全面标识的。
注意:也可以看出,UDP没有对IP增加任何别的东西,完全不管IP方面内容。

上图中,从A到B,报文段包要包括B的目标端口号,但是包含源端口号干什么呢?主要是,报文段(segment)发送到B之后,B要对该报文段进行提取,提取出源端口号,这样B就知道响应报文该发给谁了。

                                   面向连接的多路复用和多路分解

注意,与无连接差别在: TCP套接字是由一个四元祖( 源IP地址、源端口号、目的IP地址、目的端口号)来标识的。
即,两个不同源IP地址,或者不同端口号的到达的TCP报文段,将被定向到两个不同的套接字,除非TCP携带了初始创建连接的请求。
此图可以看到,同一台主机C上,不同套接字发送出来的TCP报文段,通过TCP中的源端口号进行区别,所以到了目标主机B中,多路分解后,进入两个不同的套接字。
而两台不同的主机,A和C,因为在不同主机上,两个套接字,可以使用相同的端口号(26145),来发送TCP报文段,进入目标B,通过源IP地址来区分来自两个不同套接字的TCP报文段,来进行多路分解,之后也会进入不同的套接字。

                                              Web服务器与TCP

当今高性能的Web服务器通常能使用一个进程,但为每个新的客户机连接,创建一个具有新连接套接字的新线程(第二章的一个作业)。

三、无连接运输:UDP

更加适合 UDP的场合,或说 UDP相对于TCP的 优点
  • 应用层能更好地控制要发送的数据和发送时间
               UDP有更快的发送时间,是因为:
                    1、 没有创建连接的开销
                    2、TCP有拥塞控制,链路非常拥塞的时候,会节制报文段发送。且TCP为了保证一定能发送成功,他是不管交付需要多长时间的,失败一次,就重                              
                         来一次。
  • 无需连接建立
               如前面说的,建立连接会引入延时。
  • 无连接状态
               维护连接状态需要更多的资源,比如缓存等,UDP不需要维护连接状态,所以可以支持更多客户机
  • 分组首部开销小
               TCP首部有20字节,UDP仅8字节。

注意:UDP应用也是可以完成可靠数据传输的,只是要自己建立一些可靠性机制(如增加确认和重传机制)。

1、UDP报文段结构

结构如下:
Source Port 和 Dest Port前面介绍过了。
Length          :是包括首部行在内的 UDP报文段长度,以字节为单位。
Checksum     :是 校验和位,校验和其实还会用到IP首部的一些字段。

2、UDP校验和

即上面Checksum首部该填什么呢?是把一个UDP报文段中的所有16bit字相加求反,遇到溢出要回卷。
示例如下:
     假如有如下三个16 bit word
     
主要注意下最后一个,最后本来加起来是 10100101011000001 ,即出现溢出,且末尾为01的,但是正因为溢出,出现回卷,溢出加到尾巴上,变成10。

这块讲了一个 端到端原则(end-end princeple),说为什么在UDP实现差错检验。我没看懂,贴出原文:

UDP实现差错检验非常有用,但是 它不提供差错恢复,发现错误了有的UDP实现是直接丢弃受损报文段,有的是将受损报文段交给应用层,并给出警告。


四、可靠数据传输的原理

可靠数据传输协议(reliable data transfer protocol)责任就是,像个管子一样,给它投个数据,那管子另一头就一定能收到该数据,即不会损坏、丢失、甚至收到数据的顺序都能保证和发送时顺序一致。

该协议难点在于:其调用的下层协议是不可靠的,即下层无法给它提供可靠传输服务,得全凭自己干。

书里这要自己一步步实现个可靠数据传输协议,从而完成教授原理的目的。这里仅考虑 单向数据传输(unidirectional data transfer)的情况,但实际网络都是 双向数据传输(bidirectional data transfer)的,不过与单向的类似。
看Key,看Key!!!!!
左图是讲解可靠数据传输协议的模型,便于理解,右边就是作者准备真正实现的目标模型。

本图主要看右边的!

注意:我为啥强调看Key呢,Key里用的Packet,这右边这图是个抽象,不针对任何实际的运输层还是什么层,就是单纯在讲,我Data怎么通过个可靠数据传输协议,被包装成Packet,让可靠数据传输协议,调用一个不可靠的通道的接口,然后能让数据安全传输过去。

图上 rdt_send()udt_send() 是我可靠数据传输协议, 发送部分的两个 接口。而 deliver_data()rdt_rcv()是可靠数据传输协议, 接收部分的两个 接口

这里引入几个函数仅作为说明之用。

1、构造可靠数据传输协议

下面开始一步步要构造个协议了。建议直接看图,都肯定能看懂的,看书时候也是, 直接看图。文字太容易晕了。。。。。

rdt 1.0
先考虑 最简单情况,rdt 1.0的下层协议能提供可靠的数据传输,即调用接口udt_send()函数,就能保证分组一定能达到目标。目标直接调用rdt_rcv()就能接受分组。
横线上面是一个事件,横线下面是发生事件后,采取的动作。
解释一下,sending side即发送方,receiving side即接收方,都是 有限状态机(finite-state machine, FSM)。
那个大球,就是状态,箭头就是从一个状态转到另一个状态,虚线箭头指示出状态机的初始状态。
目前协议非常简单,所以只有一个状态,wait for call from above 这里above就是指上一层协议,再次强调,这里讲抽象,不具体到上一层是什么层(网络层 还是运输层什么的)。
make_pkt(data),就是制作出一个分组。
需要注意的是:看一下rdt_send() 和 deliver_data()的参数都是data,udt_send()和rdt_rcv()函数参数都是Packet。

rdt 2.0
分组发给下一层后,目标收到时, 收到的分组是损坏的怎么办?

引入 肯定确认(positive acknowledgement),和 否定确认(negative acknowledgment)。
此时基于重传机制的可靠数据传输协议,称为 自动重传请求协议(Automatic Repeat reQuest, ARQ, protocol)。

三种协议处理比特差错
  •      差错检测                       比如校验和
  •      接收方反馈                    ACK和NAK
  •      重传
先看 发送方
解释右边状态,接收反馈回来的分组rdt_rct(rcvpkt),并查看返回的是不是NAK,isNAK(rcvpkt);如果是NAK,就是没有收到刚发的分组,那么重发udt_send(sndpkt)。sndpkt还是前面那个分组,注意。
如果收到的是ACK,那就传对了,什么也不做,回复到左边的状态。那个像A一样的符号是什么也不做。

接收方:
解释:接收分组,rdt_rcv(rcvpkt),并查看是不是损坏了corrupt(rcvpkt),如果损坏了,就做一个NAK分组,发回去作为答复。
sndpkt = make_pkt(NAK)
udt_send(sndpkt)

如果收到的分组好的呢,没有损坏,notcorrupt(rcvpkt),那么就取出分组里面的数据extract(rcvpkt, data)。然后发送给上面一层deliver_data(data)
最后,做出个ACK分组,作为答复,回复给发送方。
sndpkt = make_pkt(ACK)
udt_send(sndpkt)

类似rdt 2.0的协议,称为 停等(stop-and-wait)协议

rdt 2.1
如果接收方回复的ACK或者NAK分组出现受损怎么办?
更麻烦的是,怎么纠正受损?

采用 冗余分组(duplicate packet),即ACK或者NAK受损,就请求重传,但为了让接收者分清,下一个收到的是重传,还是新分组,发送方给每个发送的分组弄个 序号(sequence number),一起发送。

发送方:

解释:用条丑陋的线把图劈开,上下两部分其实质是一样的,与rdt 2.0的发送方图基本是一致的,就是加了个序号。把rdt 2.0发送方图复制两份,粘一起,就是这图。

唯一问题就是不是很清晰。。。。。。

接收方:
解释:与rdt 2.0接收方,本质上一样。

rdt 2.2
在rdt 2.1基础上,做小小改进,当接收方收到的分组有问题时,不专门做出个NAK分组作为答复了,而是直接回复上一次成功传送时,发回去的答复。即上一次的ACK分组,作为答复。这样能省下来一些时间,而且图也稍稍简单一点点。

发送方:
解释:不清晰是最大的问题。。
仅在画圈地方与 rdt 2.1 有区别

接收方:
解释:这里与rdt 2.1接收方不一样的地方就多一点点点点。。。。
     少了两个环。仔细看看吧。

rdt 3.0

之前都是接收方发现分组受损,进行一些处理,如果接收方压根没收到分组怎么办呢?

定时重传就来了,但是因为网络最大延时很难估算,且如果每次都以最大延时时间来等待,耗时太久。于是采用这么个机制:
      发送一个分组后,定时器打开,到一定时间后,不管接收方是否真正收到分组,都重新传一份该分组。

这就引入了 冗余数据分组(duplicate data packet), 注意:之前的 冗余分组(duplicate packet)冗余的是NAK 和 ACK两个答复分组,并不是数据,所以这个叫冗余数据分组。

发送方需要个倒数计时器(countdown timer),在一个给定的时间过期后,可中断发送方。
     发送方要做到:
  1. 每次发送一个分组时,启动一个定时器
  2. 响应定时器中断,采用适当的动作
  3. 能够中止定时器
因为分组序号在0和1之间交替,所以rdt 3.0 有时也成为 比特交替协议(alternating-bit protocol)。

发送方:
解释:
注意画圈位置是关键,这里是发送放,所以收到的rcvpkt分组都是接收方发回来的答复分组。收到答复分组后,如果该分组损坏,即无法辨别答复的是ACK还是NAK,那么就什么也不做继续等待,由接收方来保证答复信号的重新发送(接收方也是要计时的)。
或者收到的答复信号未损坏,只是是上一次正确传送时的答复信号,那么就是说,我发送方发送的分组,要么是丢了,要么是还在路途中,还未到达接收方,于是我什么也不干,继续等待。

红圈下面有个 timeout。即计时到了以后,不做任何判断,直接重发分组,并再次启动计时器。

接收方:
书上是习题,让自己画,problem 8。自己看吧。。。。

2、流水线可靠数据传输协议

从前面rdt 3.0描述也可以看出,发送方发送一个分组,要一直等待回复,会占用大量的时间。所以假若一次性发多个分组,即 流水线(pipelining)技术,会好很多。

左边是停等协议,右边是流水线协议。流水线速度快,书上有个简单 证明p145,自己去看吧。

看下图也能有个大概感觉, 为什么流水线快
这是普通停等协议情况,在一个RTT之内,只传了一个分组。而 使用了流水线之后
即,在一个RTT下,传了3个分组。

流水线技术对可靠数据传输协议带来如下 影响
  • 必须增加序号范围
  • 协议的发送方和接收方也许必须缓存多个分组
  • 所需序号范围和对缓冲的要求取决于数据传输协议处理丢失、损坏及过度延时分组的方式。

解决流水线差错恢复有两种基本方法: 回退N步(Go-Back-N)和 选择重传(Selective repeat)。

3、回退N步

退回N步协议(Go-Back-N,GBN)中,允许发送方发送多个分组(当然得有多个分组可发,只有一个分组那就不用在这讨论了)而不需要等待确认,但它受限于流水线中未确认的分组数不能超过某个最大允许数N。

看上图,最大允许数N,就是窗口大小, 基序号(base)是最早未被确认的分组序号, 下一个序号(nextseqnum)是窗口中第一个还没发送的分组序号。

于是,图中 四个部分就很明白了:最左边是已经确认发送到了的分组;最右边是窗口外面,是还不允许发送的分组;中间是窗口,包括已经发送分组,和未发送分组两个部分。

发送方收到个ACK,如果序号刚好等于base,窗口就向右移动一格,所以GBN协议常称为 滑动窗口协议(sliding-window protocol)。

有可能会奇怪, 为什么非要限制个窗口N呢?其实三个部分就够了啊,已确认接收,发送未确认接收,还有未发送,为什么再弄出来个不允许发送的第四部分?
答案:因为一个原因是为了流量控制, 第二个原因:就是拥塞控制

看一下有限状态机图, 发送方:
解释:难倒不难,就是看到小字估计你就要跳过了。。。。。
注意:那个 refuse_data()函数,包括 两种实现:
  1. 缓存当前data,等到窗口有空余时再发送;
  2. 是直接把该data打回上一层,报告给它说现在窗口满了,发不成。
GBN 发送方响应下面 三种类型的事件:
  • 上层的调用
                     窗口没满 ,就直接响应rdt_send(),发送分组;
                     窗口满了,有 三种情况,看refuse_data()函数实现,两种,还有采用同步机制,窗口满的时候,上一层经过同步,是知道窗口满了的,就根本不
                    会调用rdt_send()函数。
  • 收到ACK
                    GBN采用 累计确认(cumulative acknowledgment),即收到序号为n的分组的ACK答复,那么就是说n之前的分组必然已经都接收到了。
  • 超时事件
                    如果超时,发送方重传所有已发送但还未确认的分组;
                    如果收到ACK,但还有已发送但未被确认的分组,则定时器被从新启动;
                    如果收到ACK,但没有已发送却未被确认的分组,那么定时器终止。
                     注意:上面最后两条留意一下。

接收方:
注意:GBN协议中,接收方丢弃所有失序分组。正确接收了也丢弃掉。
好处在于,接收方缓存简单,不需要缓存任何失序分组。

解释:看上面图中那个default动作,这个动作包含的内容就多了,发来的分组出现损坏,或者乱序,都要求发送方重新发送的,udt_send(sndpkt)意思是回复的是上一次正确发送时已经做出的ACK。

4、选择重传

GBN的问题从前面叙述也能看出,就是丢弃失序的分组。想象如果窗口很长,1000分组,我第1000发完了,然后发现第一个错了,我要从第一个开始重传。。。

选择重传(Selective repeat),让发送方仅重传那些他怀疑在接收方出错的分组,而避免不必要的重传。

发送方事件与动作:

接收方动作与事件:
注意:第二个很重要,即是说,如果一个分组,接收方已经正确接收过了,也发送过了ACK确认信息,但是各种原因之下,又收到了这个已经接收过的分组,那么接收方,一定要再次发送正确接受确认,不能只是默默将第二次收到的这个分组删掉。
这么做的原因是,接收方第一次收到分组时的ACK答复,有可能失败,从而导致了发送方的重传,致使接收方再次接收到了这个分组;如果接收方此时不吭声,那发送方还以为又没收到,就继续发送,这么一来就没完没了了。。。。

这里隐含的问题就是:对SR而言,接收方和发送方不总是能看到相同的结果!

如下图:
图中,上面的是发送方看到的序号,下面是接收方看到的序号。

可以看到,画红色括号的部分,接收方已经正确接受,但是发送方还不知道,有可能是答复分组已经丢失,这图就能体现出来发送方和接收方看到的序号的不同。

窗口大小问题:
假设,我序号只有0、1、2、3这么多能用,所以给一系列报文编号时,0 1 2 3 用完后,会重复,比如 0 1 2 3 0 1 2 ,即从第五个分组开始,编号重复了,又从0开始了,那么假设现在窗口大小为3,传输有下面情况:

图中的X表示分组丢失!

对于这两图,先不看左半部分,只看接收方,第二次发送序号为0的分组时,对于接收方来说,它无法分辨上面两图的区别,即(两图都看最下面那个receive packet with seq number 0)最后收到的那个编号为0 的分组,无法判断它是源数据中真正的第0个分组,还是第5个分组(由于序号重排而序号恰巧也为0);

这就出现混淆了。。。

所以窗口并不是能随意大的,给个结论:窗口长度必须小于或等于序号空间大小的一半。

注意:还是看本例,窗口大小为序号一半以下时,窗口假设为2(为1也行,但没意义了),那仔细想下,无论如何,在再次发送0号分组时,前一个编号恰好为0的分组一定已经收到,课后有个证明,不过我就自己在这推演了一下。。没有详细证明。

可靠传输机制及其用途总结


五、面向连接到运输:TCP

1、TCP连接
 
由于TCP协议只在端系统中运行,而不在中间的网络元素中运行,所以中间要素不会维持TCP连接状态。事实上,中间路由器对TCP连接完全不知情,他们看到的是数据报,而不是连接。

TCP连接提供的是 全双工服务(full-duplex service),TCP连接也总是 点对点(point-to-point)的,即在单个发送方与单个接收方之间的连接。即TCP连接无法一对多传输。

三次握手(three-way handshake):
  1.      客户机首先发送一个特殊的TCP报文段;
  2.      服务器用另一个特殊的TCP报文段来响应;
  3.      客户机再用第三个特殊报文段作为响应。
注意:前两次报文段不承载“有效载荷”,即不包含应用层数据,而第三次握手,即是给服务器做回答,有承载着有效载荷。

注意:上面有个 发送缓存(send buffer),TCP先将数据引到发送缓存,该缓存是三次握手初期设置的缓存之一。 但是,TCP没明确规定什么什么时候TCP该将发送缓存内数据真正的发送出去。
接收的时候,也是有一个 接收缓存(receive buffer),数据先进入接收缓存之后,才通过套接字的。

TCP可从缓存中取出并放入报文段(segment)中的数据量受限于 最大报文段长(maximum segment size, MSS)。MSS通常根据最初确定的最大链路层帧长度来设置,本地发送主机发送长度是这样的帧,即所谓 最大传输单元(maximum transmission unit, MTU)。

注意:MSS是运输层一个报文段内应用层数据的最大长度,而不包括TCP首部。

MSS和MTU关系简单说就是:MTU因为是最下链路层上,一个帧的长度,那MSS肯定得受它限制,MSS长度,加上TCP首部,再加IP首部,等之后,不能大于MTU。

TCP连接的组成包括:一台主机上的缓存、变量和与一个进程连接的套接字,以及另一台主机上的一套缓存、变量和与一个进程连接的套接字。(看图)
在下面的路由、交换机什么的可没有给连接留有任何缓存和变量。

2、TCP报文段结构

刚说了,MSS限制了TCP报文段中,应用层数据的长度,所以当TCP发送大文件时,一般将该文件划分为许多小块,每个小块都不大于MSS。
TCP首部一般是20字节,比UDP首部多12字节。

与UDP相同的地方是:
  1.      TCP首部也有源端口号和目的端口号;
  2.      TCP首部也有checksum,即校验和字段。
比UDP多的地方是:
    1、      32bit的 序号字段(sequence number field)和32bit的 确认号字段(acknowledgment number field)。

    2、     16bit 接收窗口(receive window);用于流量控制。

    3、     4bit 首部长度字段(header length field);有这项是因为,看上面的Options字段,即选项字段,因为有它,TCP首部是可变的,只不过通常它都空,所以说TCP首部一般为20字节。

    4、     可选与变长的 选项字段(options field),用于发送方和接收方协商最大报文段长度(MSS),或高速网络环境下用作窗口调节因子时使用。

    5、     6bit 标识字段(flag field)。RST SYN FIN用于连接建立和拆除,PSH被设置时,指示接收方立即将数据交给上层;URG是指示发送紧急数据,和 紧急数据指针(urgent data pointer field)配合使用。

注意:但实际中PSH URG和紧急数据指针并没使用,这仅介绍!!!

序号和确认号
看文字极容易晕,直接看图。

序号:一个文件,500,000字节,TCP给每个字节都排序,如上图所示,然后TCP报文段中那个序号域,填写的内容就是,该报文段中应用层数据的第一个字节的序号。还是如上图所示,第一个TCP数据段的序号就是0,第二个TCP数据段序号就是1000。

确认号是什么:A B两主机,A向B请求数据,A发个TCP数据段确认号就是A期望从B获得的下一字节的序号。即A已经收到了从B主机来的0~535个字节,那么A再给B发报文请求时,确认段就写536。

TCP是提供 累积确认(cumulative acknowledgment)的。

TCP的 累积确认,实现起来有 两种情况
  1. 如果收到乱序的数据段,就直接丢弃;
  2. 如果收到乱序数据段,先将其缓存,之后等缺少的数据段补上后,再从缓冲取出。实践中采用这个。
注意:一条TCP连接,双方均可随机的选择初始序号,即不一定向上面图那样非从0开始编序号。
原因是:如果每次都从0开始编序号,前一个连接断开了,但是还有数据段在网路上走,未到达目标机,这时各种原因之下,该目标机又和之前主机建立了连接,那等这些数据段到了以后,序号又相同,会出现混淆。

3、往返时延的估计与超时

超时时间不能乱规定,要大于往返时间RTT,但是也不能太大,太大如果出现丢包,会浪费很多时间。

估计往返时延

大多数TCP实现,仅在某个时刻做一次SampleRTT测量,而不是为每个发送的报文段测量一个SampleRTT,且决不为已重传的报文段计算SampleRTT,而仅为传输一次的报文段测量SampleRTT。

每获得一个SampleRTT新值,就要计算一个均值, EstimatedRTT,公式:
EstimatedRTT = (1 - α) x EstimatedRTT + α x SampleRTT。
即,EstimatedRTT是SampleRTT得出的一个加权平均值。新样本权值大于老样本权值。因为越新的样本越能反应当前网络状况。统计学上,将这种平均称为 指数加权移动平均(Exponential Weighted Moving Average, EWMA)

除了估算RTT外,测量RTT变化也是有意义的,于是RTT偏差 DevRTT出现了。用于估算SampleRTT一般会偏离EstimatedRTT的程度。
DevRTT是一个SampleRTT与EstimatedRTT之间差值的EWMA。如果SampleRTT值波动较小,那么DevRTT的值就小,反之则大。

设置和管理重传超时间隔

超时间隔设置,要大于等于EstimatedRTT,否则会出现不必要重传。但也不能大太多,不然万一报文段真丢失了,又不能很快的重传。

要将超时间隔设为EstimatedRTT加上一定余量。SampleRTT波动大的时候,余量大;SampleRTT小的时候,余量小。DevRTT这时候用处就来了。


4、可靠数据传输

前面介绍可靠数据传输时候说,每个分组发送的时候都启动一个定时器,但是定时器开销大,所以 实际中仅使用单一的重传定时器,即,即便一次发多个报文段,计时器就一个。

为了下面说明,先定义两变量:
TCP发送方 主要响应三个事件
     1、从上层应用程序接收到数据
     2、定时器超时
     3、收到ACK报文
注意:第三个,收到ACK后,如果有报文还未被确认收到,那么重新设置定时器。

一些有趣情况




加倍超时间隔

每次TCP重传都会将下一次的超时间隔设为先前值的两倍,而不是从EstimatedRTT和DevRTT推出。

这么做也提供了一个有限形式的拥塞控制。定时器到期,可能就是网络太堵了,如果按相同时间间隔不断重传,可能使得拥塞更严重,排队时间更长。

快速重传

产生TCP ACK的建议:

前面也说了,如图情况下,发了好几份报文段,但中间有一个没接到,那ACK答复报文中,确认号就一直是100,一直重复发送相同报文段,这个就叫做冗余ACK(duplicate ACK)。

如果TCP连续接到三个相同数据的冗余ACK报文段,那TCP就立即执行重传,即在定时器到之前重传。所以前面ACK事件代码段可写为:

退回N步还是选择重传

TCP提供累积确认,但是与GBN又有很大区别,如果一次传100个报文段,且第50个报文段出错了,GBN机制下会重传50到100所有报文段,而TCP不会,如果后面都正确传输了的话,TCP就只重传第50个报文段。

TCP这机制叫做 选择确认(Selective acknowledgment),还不是 选择重传(Selective Repeat), 注意啊。
这种机制就是允许TCP接收方有选择地确认失序报文段,而不是累积地确认最后一个正确接收的有序的报文段。
TCP差错恢复机制,算是GBN和选择重传协议的混合体。

5、流量控制

因为接收方主机设置了接收缓存,缓存大小都有限,如果接收速度,大于应用程序从缓存读取数据速度,那会造成接收缓存溢出。

于是, 流量控制服务(flow-control service)来了,以消除发送方使接收方缓存溢出的可能性。其就是个速度匹配服务,让发送速率与接收方应用程序读取速率相匹配。

TCP发送方因为IP网络的拥塞而被遏制,这种发送方控制叫 拥塞控制(congestion control)。

注意:流量控制和拥塞控制可不是一个东西。

发送方维护一个 接收窗口(receive window)的变量来提供流量控制。看TCP结构那节。

TCP连接建立时,发送方和接收方,都分配(因为全双工)一个接收缓存,接收窗口大小计算出来的剩余接收缓存大小,然后每个TCP报文段都携带这个信息,发送给发送方,从而让发送方控制发送速率,达到流量控制效果。

两个变量:
     LastByteRead 主机B应用程序,从缓冲读出的最后一个字节的编号
     LastByteRcvd  从网上到达并进入主机B接收缓存中的数据流最后一个字节编号

     LastByteRcvd - LastByteRead 得到的就是已经使用的缓存,该值必然 <= RcvBuffer 即接收缓存。

     接收窗口大小为:
     

发送端还有两个变量:
     LastByteSent 和 LastByteAcked    就不说意思了啊,明了的
     LastByteSent - LastByteAcked 结果就是主机A发送到连接,但还未确认收到的数据量。
     未确认的数据量,必须保证在接收窗口以内,从而保证主机A发送数据不会使主机B溢出。
     

接收窗口和接收缓存关系示意图:

有一个 大问题
     如果接收窗口满了,主机A知道了,于是不给主机B发送有用数据,一段时间之后,主机B清空了他的缓冲,但是主机B并不会发送消息给A告知A说我已经有缓存了。。。因为 注意!:TCP仅在有数据或确认信息要发送时,才会发送报文段给另一个主机。

解决办法
     B接收缓存满了以后,通知A,之后A持续给B发送数据段只有一个字节的报文段,发过去一个报文段,B要回复一下确认收到,然后A就可以从这个持续发回来的确认报文段中,得到B缓存状况,一旦发现B清理了缓存,A就继续发送有用数据给B。
     
     注意啊:并不是说A发现B缓存满了以后,给B发送一个1字节内容的报文段,去通知B清理缓存,A是不能管B的事情的。

注意:UDP不提供流量控制,UDP中,如果读缓存太慢,缓存溢出了,那后来的报文段都直接丢弃。

6、TCP连接管理

客户机向服务器请求建立 TCP连接过程
       第一步:客户机端TCP首先向服务器端的TCP发送一个特殊的TCP报文段。标志位SYN被置1,称为SYN报文段。客户机还会选择一个起始序号(client_isn),并把它放到TCP SYN的序号字段中,为了避免攻击,适当的随机化client_isn有好处。
      第二步:客户机的TCP SYN到达服务器后,服务器分配 TCP缓存和变量,并给客户机回复允许连接报文段。其SYN还是1,首部的确认号字段被置为client_isn + 1,服务器要选择自己的初始序号server_isn,并放入报文段序号字段。该报文被称为SYNACK报文段(SYNACK segment)。
      第三步:客户机收到SYNACK 报文段后,客户机也要配置缓存和变量,并再发个报文给服务器,确认段为server_isn + 1。SYN置0。此回复报文已经可以包含应用程序传来的数据。
     
图仔细看:SYN号,然后各个阶段seq和ack号。

TCP连接 结束过程
      第一步:客户及应用程序发出关闭连接命令。于是客户机TCP发送个特殊报文给服务器,该报文FIN置 1 
      第二步:服务器收到特殊报文,回应一下说收到报文了;然后回收缓存等资源,之后给客户机发送个FIN置1的特殊报文。
      第三步:客户机收到服务器发来的FIN报文段,回复一下,然后等一段时间后,结束。

FIN_WAIT下面讲。

客户机TCP经历的典型TCP状态序列:
注意:看那个TIME_WAIT,一般等待时间是30秒,一分钟或两分钟。干嘛等这么长时间呢????
原因在于:服务器给客户机发送的那个FIN报文段,要保证客户机真正收到了,那么如果这个FIN报文段或者客户机对该保温的答复ACK,在网络中丢失,那么服务器就不知道客户机是否收到了这个FIN报文段,所以隔一段时间后,会重新发送FIN报文段;但如果服务器正确收到了最后的ACK报文段,它什么也不再回复。这也导致了,客户机在这要等这么久。就是说,客户机等待,就是再等看服务器会不会再次发送FIN,等了半天没消息了,就说明正确了。

服务器TCP经历的典型TCP状态序列:
注意:两图都只给了正常状态,没设计不正常情况。

注意:如果客户机给服务器发的请求TCP连接报文端口不对,如客户机说我要连80端口,然后服务器80端口没开,就会给客户机回复一个标志位RST置位的重置报文段,告诉客户机没有对应套接字。这也给端口扫描提供了条件。

在UDP中,如果套接字不匹配,主机会发送特殊的ICMP数据报回复。

端口扫描工具nmap实现,就是发送TCP SYN报文段:
  •      收到目标主机回复TCP SYNACK报文段,说明目标机该端口打开。
  •      收到目标主机回复TCP RST报文段,说明目标机没有开该端口,没有应用程序运行。且源和目标之间没有放火强。
  •      如果什么也没收到,说明源到目标之间有防火墙。
nmap功能很多,还能扫目标机防火墙配置,系统等信息。

六、拥塞控制原理

丢失一般是在网络变得拥塞时, 由于路由器缓存溢出引起的。所以, 分组重传可以作为网络拥塞的一个征兆

本节主要讲, 为什么网络拥塞是坏事怎么控制拥塞

1、拥塞原因与开销

这小节就是讲随着主机增加其传输速率,使得网络变得拥塞时会发生的情况。
下面情况1 情况2 和情况3用来介绍拥塞引入的三种开销。

情况1:两个发送方和一个具有无穷大缓存的路由器
解释:上面那四个格格子,就是协议栈。

朗姆达打不出来,用L代替。Lin是应用程序平均速率,发送到连接中。主机A和B都是以Lin这个速率发送的。

因为路由缓存无穷大,所以不会出现丢包,这里也假设不执行任何差错恢复,流量控制、拥塞控制。

那么有:
吞吐量上限就是R / 2,速率再高就使得延时无限增大。

所以, 情况1的开销就是:当分组到达速率接近链路容量的时候,分组会经历巨大的延时。

情况2:两个发送方和一个具有有限缓存的路由

解释: 注意:因为此情况下,会出现重传,所以引入了从运输层向网络中发送报文的速率L'in, 它L'in = 原始数据速率 + 重传速率

情况2下的性能,在很大程度上依赖于如何进行重传。

分a b c三种情况,看下图:
先看a,是完全没有重传的时候,即发送方总是知道连接上路由缓存情况,从而只在有空缓存时,才发送数据,这时候就和前面有无限缓存的路由的情况1相同了。所以有上面图a部分。

看b:b是没有超时,它等待时间无限长,只在确认一个分组没收到的情况下,才对这个分组重传。(别考虑那么多,我这里发送方是一直发送,所以一直会有回应回来,所以如果分组丢失,我不会不知道,只要等时间足够长,总是能知道有分组丢失的,想想冗余ACK)
当L'in(原始数据传输加上重传的速率)为R / 2时候,它的图可能就是上面b的样子,即我虽然L'in是R / 2,但是实际有效的输出只有R / 3,R / 2 - R / 3那部分是重传消耗掉了。
所以, 情况2 引入的开销是:网络拥塞时,发送方要执行的重传来补偿缓存溢出丢失的分组,这就存在速率损失。

看c:c是假设等待时间不是无限长,引入超时重传,且假设我发出去的分组,其实都接收方都受到了,只是都在还没收到回应的时候,就出现超时,发送方就重发。那么对于每一个分组它实际有效发送的分组只有实际发送的一半,因为每个分组都发了两份。所以L'in为R / 2,实际 Lout只有 R / 4。

所以, 情况2 引入的另一个开销是:发送方遇到延时非常大的时候, 不必要的重传会引起连接转发 不必要的分组拷贝(因为接收方实际已经接收到了),又是个开销。

情况3:四个发送方、具有有限缓存的多台路由器和多跳路径(multihop paths)

图看着复杂,其实R3和R4不用看。

每个路由容量都是R。

解释:
不管Lin有多大,从A到C的速率最多只有R,因为受R1限制。

现在 看从A到C,和从B到D
A-C路径上要经过两个路由,R1和R2;而B-D路径上有路由R2和R3。那么在竞争R2路由时,B-D流量很可能比A-C大,因为A-C还要通过R1,要与D-B链路争带宽,而从B到R2是畅通无阻的,L'in就是L'in,没有人争的,那么如果B-D载荷越来越大,就会将A-C流量在R2上挤的趋于0,不断丢包,且一旦A-C在R2上出现了丢包,那它在上游路由占用的传输容量就全部损耗了,即白白占用了R1的容量。

所以, 情况3 引入的开销是:当一个分组沿一条路径被丢弃时,每个上游路由用于转发该分组到丢弃该分组而使用的传输的传输容量最终全部被浪费。

所以,当选择一个分组进行传输时,路由最好先考虑那些已经历过一定数量上游路由器的分组。

2、拥塞控制方法

可根据网络层是否为运输层拥塞控制提供了显示的帮助来区分 拥塞控制方法:

  • 端到端拥塞控制
               就是网络层没有为运输层拥塞控制提供显示支持,这种就是端到端拥塞控制。
  • 网络辅助拥塞控制
               就是通过网络层组件(路由器)向发送方提供关于网络中拥塞状态的显式反馈信息。

拥塞信息从网络 反馈到发送方通常有 两种方式
     1、直接反馈信息。由网络路由器直接以阻塞分组(choke packet)的形式发送消息给发送方。
     2、路由器标记或更新从发送方流向接收方的分组中的某个字段,来指示拥塞的产生。
                注意:这种方式的通知至少要经过一个完整的往返时间,即要由接收方再次发回给发送方消息后,发送方才能知道阻塞了。
3、网络辅助的拥塞控制例子:ATM ABR拥塞控制
     
这个是建议用在TCP/IP网络中,或更复杂的网络反馈中。没细看。p178


七、TCP拥塞控制

TCP的 另一个关键:拥塞控制,通过让每一个发送方根据所感知到的网络拥塞的程度,来限制其能向连接发送流量的速率。

三个问题:发送方如何限制自己流量速率?发送方如何感知到拥塞?采用什么算法改变发送速率?

注意:限制和改变可不是一个概念,限制是结果,改变是过程。

TCP拥塞控制机制让连接的每一端都记录一个额外的变量,即 拥塞窗口(congestion window)。

发送方中未被确认的数据量不会超过CongWin 和 RcvWindow中的较小值,即:
     
cwnd是拥塞窗口,rwnd是接收窗口。

注意:后续叙述,忽略rwnd约束(因为那个是流量控制上的),为了凸显拥塞控制。

因此,目前:发送方的发送速率大概是Cwnd / RTT 字节/秒 。通过调节Cwnd,发送方就能调整它向连接中发送数据的速率。

发送方的丢失事件,即要么超时,要么收到3个冗余ACK,就是 拥塞的指示

收到以前未确认的报文段的确认,( 注意:这强调是以前未确认的报文段的确认,冗余ACK可不算啊,收到冗余ACK,未超过3个时,Cwnd和Threshold都不变)是网络 一切正常的标志,并使用这个确认来增加拥塞窗口的长度,及其传输速率。

注意:以上叙述能看出,确认如果缓慢到达,那传输速率长的就很慢;如果确认到达的很快,那传输速率上涨速度就很快,所以TCP被称为是自计时(self-clocking)的。

TCP拥塞控制算法(TCP congestion control algorithm),包括三部分:
     1、加性增(additive-increase)、乘性减(multiplicative-decrease)
     2、慢启动(slow start)
     3、对超时事件做出反应。

加性增、乘性减

乘性减:每发生一次丢包事件就将当前的Cwnd值减半,但不会降到小于1个MSS。

加性增:没收到一个确认,就把Cwnd增大一点,其目标是在每个往返时延内Cwnd增加一个MSS。

TCP拥塞控制算法常常被称为 加性增、乘性减(Additive-Increase, Multiplicative-Decrease,AIMD)算法。

TCP拥塞控制协议的线性增长阶段被称为 避免拥塞(congestion avoidance),注意这个词后面要用到。

注意:加性增时,每次都加 MSS / Cwnd 乘 MSS个字节(我自己的理解),都是固定值,所以是线性的。
这段例子看书上这么说:
如果MSS 1460字节,Cwnd14600字节,RTT内发10个报文段,每到达ACK增加1/10 MSS的拥塞窗口长度,因此在收到对所有10个报文段的确认后,拥塞窗口的值将增加了MSS,与所期望的一样。

这种算法使得长寿命TCP连接的Cwnd变化呈锯齿形状:

慢启动

TCP连接开始时,Cwnd初始值为一个MSS。即速率为 MSS / RTT,而可用带宽可能大得多,如果仅通过上面的算法,线性增大的话要好久才能到个比较好的速度。

所以,慢启动来了:初始化期间,TCP发送方以慢速率(所以叫慢启动)发送,但是以指数的速度快速增加其发送速率,直到遇到一个丢包时间为止,Cwnd降为一半,然后按上面线性速度增长。

每当一个传输的报文段被确认后,Cwnd增加一个MSS。

对超时事件做出反应

超时做出的响应和对收到3个冗余ACK的响应是不一样的。

先引入个变量,阀值(Threshold),用来确定慢启动将结束并且拥塞避免将开始的窗口长度。Threshold初始化时被设置为一个很大的值(实际为65kb),以使其没有初始效应。
没发生一个丢包事件,Threshold被设置为当前的Cwnd的一半。

收到3个ACK后,TCP行为和前面描述一样,拥塞窗口减少一半,然后线性的增长。

但超时事件发生时,TCP发送方进入慢启动阶段,即将拥塞窗口设置为1MSS,然后窗口长度以指数速度增长。直到Cwnd达到超时事件前,窗口值的一半为止,即Threshold值为止。此后TCP进入拥塞避免(congestion avoidance)阶段,Cwnd线性增长,就像收到3个冗余ACK一样。

                                                               

早期TCP,不管发生那种丢包事件,都无条件将拥塞窗口减至1MSS,并进入慢启动阶段,现在这种较新版本,在收到3次冗余ACK时,取消慢启动阶段的行为称为 快速恢复(fast recovery)。

TCP Reno就是快速恢复版本的,TCP Tahoe是不带快速恢复的。

对TCP吞吐量的宏观描述

自己看吧 p184

TCP的未来

自己看吧 p 185

公平性

自己看吧 p186

当多个连接共享一个公共的瓶颈链路时,那些具有较小RTT的连接能够在链路空闲时,更快地抢到可用带宽,即较快地打开其拥塞窗口,因而将比那些具有较大RTT的连接享有更高的吞吐量。

公平性和UDP

UDP没拥塞机制,拥塞了也不减低发送速率,也不与其他连接合作。而TCP因为一旦发现拥塞就降低速率,所以会出现UDP压制TCP的情况。

公平性和并行TCP连接

当一个应用程序使用多条并行连接时,它占用了一段拥塞链路较大部分的带宽,所以也有公平性问题。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值