TCP/IP详解 卷1:协议 学习笔记 第二十章 TCP的成块数据流

在bsdi上运行自定义的sock程序作为服务器:
在这里插入图片描述
其中-i和-s选项指示程序作为一个从网络上读取并丢弃数据的服务器运行,且服务器端口为7777,相应的客户程序为:
在这里插入图片描述
该命令发送8个1024字节的数据,以下是这个过程的时间序列:
在这里插入图片描述
前三个报文段是连接建立过程,建立时的报文段显示了两端的MSS值,连接建立后,发送方连续发送了三个数据报文段,但报文段7中只确认了前两个数据报文段(其确认序号为2048),仅确认前两个报文段的原因为:当一个分组到达时,它首先被设备中断例程处理,然后放置到IP的输入队列中,三个报文段4、5、6依次到达并按接受顺序放到IP的输入队列,IP按同样顺序将它交给TCP,当TCP处理报文段4时,报文段4的确认需要等待以发送一个经受时延的确认,之后TCP又处理了报文段5,此时TCP有两个未完成的报文段需要确认,因此产生了报文段7,它只对两个报文段确认,此时发送了该连接产生的经受时延的确认,之后TCP处理报文段6,该连接又被标志为产生一个经受时延的确认,在报文段9到来之前,时延定时器溢出,产生一个对第三个数据报文段及进行确认的报文段。

报文段11~16说明了通常使用的隔一个报文段确认的策略(即每两个报文段发送一次确认),报文段11、12、13到达并被放入IP的接收队列,当报文段11被处理时,连接被标记为要产生一个经受时延的确认,当报文段12被处理时,它们的ACK产生(报文段14中发出)且连接的经受时延的确认标志被清除,报文段13使得连接再次被标记为产生经受时延的确认,但在时延定时器溢出前,报文段15被接收方处理完毕,因此在定时器溢出前报文段13和15的确认在报文段16中被发送。

上图中所有发送方发送的8个数据报文段全都设置了PUSH标志,这是因为每次写操作都清空了发送缓存。如果待发送数据将清空发送缓存,大多源自伯克利的实现会自动设置PUSH标志。

接收方不必确认每个收到的分组,TCP的确认是累积的,它表示接收方已经正确接收到了一直到确认序号减1的所有字节。

线路上的分组依赖于许多无法控制的因素,发送方TCP的实现、接收方TCP的实现、接收进程读取数据(依赖于操作系统的调度)、网络的动态性(如以太网的冲突和退避等),没有一种单一的、正确的方法交换给定数量的数据,如果再次发送与上例相同的数据,分组交换情况也可能会不同。

以下是一个快的发送方到一个慢的接收方的时间序列:
在这里插入图片描述
发送方连续发送四个数据报文段去填充接收方的窗口,之后停下来等待ACK,接收方在报文段8中发回ACK,但通告其窗口大小为0,说明接收方已经接收到了所有数据,这些数据全部在接收方的TCP缓冲区,应用还没读取这些数据,另一个ACK(称为窗口更新)在17.4ms后发送,表明接收方现在可以接收另外的4096字节的数据,它虽然看起来像ACK,但不确认任何新数据,只是用来增加窗口的右边沿。

发送方之后在报文段10~13中再次发送了四个1024字节大小的数据,且报文段13带有PUSH和FIN标志,随后接收方发来两个ACK表示对这四个数据报的确认和窗口更新,之后接收方发出FIN。

上图中,前四个数据报文段(报文段4~7)每一个都设置了PUSH标志,这是因为它们每一个均使发送缓存为空了,随后,TCP停下来等待一个确认来移动移动窗口,在此期间,TCP又得到了应用的4096个字节,当窗口张开时,TCP知道它有4个可立即发送的报文段,因此它只设置了最后一个报文段的PUSH标志。
在这里插入图片描述
上图中,接收方通告的窗口称为提供的窗口,它覆盖了第4~9字节的区域,表明接收方已经确认了之前的数据,当前的通告窗口大小为6,窗口大小是相对于确认序号的,发送方计算它的可用发送窗口。

描述窗口左右边沿的运动的术语:
1.窗口左沿向右边靠近称为窗口合拢,发生在数据被发送后收到确认时。
2.窗口右沿向右移动时称为窗口张开,发生在另一端的接收进程读取已经确认的数据并释放了TCP的接收缓存时。
3.当右边沿向左移动时称为窗口收缩,Host requirements RFC强烈建议不要使用这种方式。
在这里插入图片描述
如果左边沿到达右边沿,称其为一个零窗口,发送方不能发送任何数据。

由接收方提供的窗口的大小通常可以由接收进程控制,这对TCP的性能有影响。

4.2 BSD默认将发送和接收缓冲区大小设为2048字节,在4.3 BSD中双方被增加为4096字节。Sun OS 4.1.3、BSD/386、SVR4仍使用4096字节的默认大小;Solaris 2.2、4.4 BSD、AIX 3.2使用更大的默认缓存大小,如8192或16384。

Socket API允许进程设置发送和接收缓存大小。接收缓存大小是该连接上能通告的最大窗口大小。有些应用通过修改socket的缓存大小来增加性能。

[Mogul 1993]显示,在以太网上两站之间进行文件传输时,默认的4096字节可能不是最理想大小,将两个缓存(发送方的发送缓存和接收方的接收缓存)增加到16384字节可以增加约40%的吞吐量。

socket API允许进程设置发送和接收缓存的大小,接收缓存的大小是该连接上能通告的最大窗口大小,一些应用通过修改发送和接收缓存大小来增加性能。
在这里插入图片描述
由于接收方开始时提供的窗口大小为6144字节,客户发送了6个1024大小的报文段,然后停止,报文段10中确认了这6个报文段,但提供的窗口大小为2048,很可能是应用只读取了2048字节的数据。报文段11和12完成了客户的数据传输,且最后一个报文段带有FIN标志。

报文段13包含与报文段10相同的确认序号,但通告了一个更大的窗口大小,之后报文段14确认了最后2048字节的数据和FIN,报文段15和16仅用于通告一个更大的窗口大小。

上图中发送方的第12个报文段的PUSH被置位,因为它是最后一个报文段。第7个报文段也设置了PUSH标志,但发送方此时知道有更多的数据需要发送,原因在于发送方的发送缓存大小只有4096字节。

当接收方通告其窗口大小为0(此时发送方将停止发送)后,当窗口再次张开时,需要发送另一个窗口非0的ACK来使发送方重新启动,但上图中,窗口的大小从来没有达到过0,然而,上图中当窗口大小增加了2048字节的时候,另一个ACK被发送以通知对方更新窗口(第15、16个报文段),这是由于许多TCP实现在窗口大小增加了两个最大报文段长度(上例中是2048字节,MSS是1024字节)或最大窗口的50%(上例中也是2048字节,最大窗口大小为4096字节)时,发送窗口更新。

发送方使用PUSH标志通知接收方将所受到的数据全部提交给接收进程,包括与PUSH一起传送的数据和接收方TCP已经为接收进程收到的其他数据。接收方接收到PUSH后立即将这些数据交给服务器进程而不等待判断是否还会有额外的数据到达。

最初的TCP规范中,一般假定编程接口允许发送进程告诉TCP何时设置PUSH标志,从而通知服务端TCP不要因等待额外数据而使已接收数据在缓存中滞留。

目前大多API没有向应用提供设置PUSH标志的方法,许多实现认为PUSH标志已经过时,一个好的TCP实现能自行决定何时设置这个标志。

如果待发送数据将清空发送缓存,则大多源于伯克利的实现能自动设置PUSH标志,这个算法对于只有在缓存被填满或收到一个PUSH标志时才向应用程序提交数据的对端TCP实现有效。

socket API不能通知TCP设置PUSH标志,也不能知道收到的数据是否设置了PUSH标志。

源于伯克利的实现一般从不将收到的数据推迟交付给应用,因此它忽略接收到的PUSH标志。

上例中,发送方一开始就向网络发送多个报文段,直至达到接收方通告的窗口大小为止,这在发送方和接收方处于同一个局域网时是可以的,但如果发送方和接收方之间存在多个路由器和速率较慢的链路时,可能出现问题,一些中间路由器必须缓存分组,并有可能耗尽存储器的空间,从而降低TCP连接的吞吐量,TCP使用慢启动算法缓慢探测可用传输资源,防止短时间内大量数据注入导致拥塞。

慢启动为发送方的TCP增加了拥塞窗口,记为cwnd,当与另一个网络的主机建立TCP连接时,拥塞窗口被初始化为1个报文段(即另一端通告的报文段大小),每收到一个ACK,拥塞窗口增加一个报文段(cwnd以字节为单位,慢启动以报文段大小为单位进行增加),这意味着当发送方收到第一个报文段的ACK时,会将拥塞窗口增加到2,之后发送两个报文段,当接收到这两个报文段的两个ACK时,拥塞窗口变为4,因此拥塞窗口实际上是指数增加的,发送方取拥塞窗口与通告窗口中的最小值作为发送上限,拥塞窗口是发送方的流量控制,而通告窗口是接收方使用的流量控制。

在某个点上达到了最大流量,中间路由器开始丢弃分组,这就通知了发送方它的拥塞窗口开得过大。
在这里插入图片描述
由上图,发送方在拥塞窗口为3的情况下,由于还没有收到报文段4的ACK,因此还是发送了2个报文段(6~7)。

窗口大小、窗口流量控制、慢启动对传输成块数据的TCP连接的吞吐量的相互作用:
在这里插入图片描述
在时间0,发送方发送了一个报文段。由于发送方处于慢启动中,其拥塞窗口为1个报文段,因此在继续发送前它必须等待该数据段的确认。

在时间1、2、3,报文段从左到右每次移动一个时间单元,在时间4接收方读取这个报文段并产生了确认。经过时间5、6、7,ACK移动到了左边的发送方。我们有了一个8个时间单元的往返时间RTT。

上图中ACK报文段有意被画得比数据报文段小,因为它通常只有一个IP首部和一个TCP首部。这里仅显示了单向的数据流动,且假定ACK和数据报文段的移动速率相等,实际不一定这样。

通常发送一个分组的时间取决于两个因素:传播时延(由光的有限速率、传输设备的等待时间等引起)和发送时延(取决于媒体速率,如媒体每秒可传输的比特数)。对于一个给定的通路,传播时延一般是固定的,而发送时延取决于分组的大小。在媒体发送速率较慢时发送时延占比较大,而在发送速率为千兆比特速率时传播时延占比较大。

当发送方收到ACK后,在时间8、9发送两个报文段,此时它的拥塞窗口为2个报文段。这两个报文段向右传送到接收方,在时间12、13接收方产生两个ACK。这两个返回到发送方的ACK之间的间隔与报文段之间的间隔一致,被称为TCP的自计时行为。由于接收方只有在数据到达时才产生ACK,因此发送方接收到的ACK之间的间隔与数据到达接收方的间隔是一致的(然而实际上,返回路径上的排队会改变ACK的传播时延)。
在这里插入图片描述
如上图,2个ACK的到达使得拥塞窗口从2个报文段增加为4个,这4个报文段在时间16~19被发送,这4个报文段的第1个ACK在时间23到达,在时间26,4个ACK全部到达,使得拥塞窗口从4个报文段增长为8个,从而使发送方在时间24~31发送了8个报文段。

在时间31之后,发送方和接收方之间的管道被填满,不能再容纳更多数据。从此每当接收方从网络上移去一个报文段,发送方就再发送一个报文段到网络上,返回路径上总是具有相同数目的ACK,这是连接的理想稳定状态。

以上通道的容量称为时延带宽积,计算公式如下:
在这里插入图片描述
带宽 * RTT。如一条穿越美国(RTT约为60ms)的T1电话线路(1 544 000b/s)的带宽时延乘积为11580字节,但穿越美国的T3电话线路(45 000 000b/s)的带宽时延乘积为337500字节,超过了TCP通告窗口的最大大小(65535字节),可通过TCP窗口大小选项避免此限制。

现在回答拥塞窗口应设为多大的问题,上例中,作为最大吞吐量,发送方在任何时候都有8个已发送的报文段未被确认。接收方的通告窗口小于这个数目时,不能达到最大吞吐量,因为接收方通告窗口限制了发送方能发送的段数目。
在这里插入图片描述
当数据到达一个大的管道(如一个快速局域网),并向一个较小的管道(如一个较慢的广域网)发送时会发生拥塞。当多个输入流到达一个路由器,而路由器的输出流小于这些输入流的总和时也会发生拥塞。

以下是典型的拥塞,之所以说典型,是由于大多数主机都连接在一个局域网上,并通过一个路由器与速率相对较低的广域网相连:
在这里插入图片描述
通常,R1和R3是同一路由器,R2和R4也是,但这并不必须,有时也会使用非对称路径。上图假定发送方不使用慢启动,它按照局域网的带宽尽可能快地发送报文段。

TCP提供了紧急模式,来告诉接收端有紧急数据放置在了数据流中,接收方决定如何进行处理。

紧急模式打开方法是置位TCP首部中的URG比特,将一个16bit的紧急指针置为一个正的偏移量,该偏移量与TCP首部中的序号相加可得出紧急数据的最后一个字节序号。

紧急指针应指向紧急数据的最后一个字节还是该字节之后的一个字节还有争论,Host Requirements RFC确认指向最后一个字节是正确的,但大多数实现,包括伯克利的实现继续使用另一种错误方式。

TCP必须通知进程接收到了紧急数据,接着进程可读取数据流,并必须被告知何时碰到了紧急数据指针,只要接收方当前读取位置到紧急数据指针之间有数据存在,就认为应用处于紧急模式,紧急指针通过后,应用回到正常模式。

TCP不能指出紧急数据从数据流的何处开始,只指出了紧急数据的结束位置。

许多实现不正确地称TCP的紧急模式为带外数据,如果一个应用确实需要一个独立的带外信道,第二个TCP连接是达到这个目的的最简单方法。许多运输层确实提供使用同一个连接的另一个逻辑数据通道作为正常的数据通道,但TCP没有提供此功能。

TCP的紧急模式与带外数据之间的混淆,是因为主要的编程接口socket API将TCP的紧急方式称为带外数据。

紧急方式可用于telnet和rlogin,当交互用户输入中断键时向服务器发送;也可用于FTP,当交互用户放弃一个文件的传输时。

rlogin和telnet从服务器到客户使用紧急方式是因为这个方向上的数据流可能要被客户的TCP停止(客户通告了一个大小为0的窗口),如果服务器进入了紧急方式,尽管它不能发送任何数据,但服务端TCP还是会立即发送紧急指针和URG标志,当客户TCP收到这个通知时就会通知客户进程,于是客户可以从服务器读取其输入并打开窗口。

在接收方处理第一个紧急指针前,发送方多次发送带URG的报文时,接收方只会维护一个紧急指针,每当有新的紧急指针到达时覆盖它。

测试在接收方窗口关闭时,发送方是如何发紧急数据的,可使用自定义的sock程序实现,接收端启动时的选项:
在这里插入图片描述
接收端使用-P选项使得在建立连接后从网络读取前暂停10秒钟。

之后启动sock的客户端:
在这里插入图片描述
客户端使用-S选项指定一个8192字节大小的发送缓存,并使用-n选项向服务端发送6个1024字节的报文段,还使用-U选项使客户在发送第5个报文段前先发一个1字节数据的URG报文段。
在这里插入图片描述
发送端连发四个报文段(报文段1~3、5)填满了接收方的接收缓存,之后发送端写了1个字节并设置URG发送,紧急指针被设为4098,尽管发送方不能发送任何数据,但还是发出了报文段6。发送端连续发送了5个这样的URG报文段,第一个URG报文段是客户端的-U选项带来的,之后的两个URG报文段是进程写最后两个1024字节的数据时发送(尽管TCP不能发送这2048字节的数据,但每次应用执行写操作时,TCP的输出功能被调用,可见,当TCP发出URG报文段后,还会再次发送URG报文段),第四个URG报文段是应用关闭其TCP连接时发出的(此时应用已经将所有数据都放到了发送缓存中,关闭连接时TCP会进行输出操作),第五个URG很可能是接收到第四行的ACK时产生的(发送方TCP很可能在第四行的ACK到达前已经将其第4个报文段放入队列以便输出)。

之后,接收方确认第四个报文段的1024字节的数据(报文段11),但同时通告窗口为0,发送方用一个包含紧急通知的报文段进行响应(报文段12)。

之后第十三行,接收方应用在10秒后被唤醒,并接收了2048字节的数据,于是接收方通告窗口为2048字节,于是发送方在报文段14、15中发送了最后的2048字节,其中,由于紧急指针在报文段14的范围内,因此它还被设置了紧急通知标志,而报文段15中则关闭了该标志。接收方在报文段16中再次通告了窗口,因此发送方就把最后的1个字节发送(报文段17),并且还设置了FIN标志,表示断开连接。

上图中,进入紧急模式时发送的字节序号是4097,但紧急指针指向4098,该实现将紧急指针设为紧急数据最后字节的下一个字节。

SYN和FIN在序号空间里各占一个字节,而其他TCP首部中的标志不占用序号。

如果API提供一种方法,使得发送方可以打开PUSH标志,而接收方可以查询一个接收的报文段是否设置了PUSH标志,此时该标志也不能被用作一个记录标记,因为当TCP超时后,会进行数据的重新分组。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值