TCP/IP协议——TCP详解

TCP传输控制协议

TCP提供面向连接的、可靠的字节流服务。面向连接意味着两个使用TCP的应用(同常一个客户端一个服务器)在彼此交换数据之前必须先建立一个TCP连接。在一个TCP连接中,仅有两方进行通信,广播和多播不能用于TCP。TCP通过以下方式提供可靠性:
1,应用数据被分割成TCP认为最适合发送的数据块。这和UDP完全不同,应用程序产生的数据报长度保持不变。由TCP传给IP的信息单位称为报文段或段。
2,当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
3,当TCP收到来自TCP另一端的数据,它将发送一个确认。这个确认不是立即发送,通常推迟几分之一秒。
4,TCP将保持它首部和数据的检验和,这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP将丢弃这个报文段和不确认收到此报文段(希望发端超时并重发)。
5,既然TCP报文段作为IP数据报传输,而IP数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。如果必要,TCP将对收到的数据重新排序,将收到的数据以正确的顺序交给应用层。
6,既然IP数据报会发生重复,TCP的接收端必须丢弃重复的数据。
7,TCP还能提供流量控制。TCP连接的每一方都有固定大小的缓冲区,TCP的接收端只允许另一端发送接收端缓冲区所能接纳的数据。这将防止较快主机致使较慢主机的缓冲区溢出。
两个应用程序通过TCP连接交换8bit字节构成的数据流。TCP不在字节流中插入记录标识符。我们将这称为字节流服务。如果一方应用程序先传10字节,又传20字节,再传50字节,连接的另一方将无法了解发送方每次发送了多少字节。收方可以分四次接收这80个字节,每次接收20个字节。一端将字节流放到TCP连接上,同样的字节流将出现在TCP连接的另一端。另外,TCP对字节流的内容不做任何解释。TCP不知道传送的数据字节流是二进制数据,还是ASCII码,EBCDIC字符或者其他类型数据。对字节流的解释由TCP连接双方的应用解释。

TCP首部

TCP在IP数据报中的封装:
在这里插入图片描述
TCP首部格式,如果不计任选字段,通常是20字节。
在这里插入图片描述
每个TCP报文段都包含源端和目的端端口号,用于寻找发端和收端应用进程,这两个值加上IP首部中的源端IP地址和目的端IP地址可以唯一确定一个TCP连接。有时,一个IP地址和一个端口号也称为一个插口(socket)。插口对可以唯一确定互联网络中每个TCP连接的双方。
序号用来标识TCP发端向TCP收端发送的数据字节流,它表示在这个报文段中的第一个数据字节。如果将字节流看做两个应用程序的单向流动,则TCP序号对报文段中每个字节进行计数。序号是32bit无符号数,序号到达2^32-1后又从0开始。
当建立一个新连接时,SYN变1。序号字段包含这个主机选择的该连接的初始序号ISN,该主机要发送数据的第一个字节序号为这个ISN加一,因为SYN标志消耗了一个序号。
既然每个传输的字节都被计数,确认序号包含发送确认的一端期望收到的下一个序号。因此,确认序号应该是上次已成功接收的数据字节序号加一。只有ACK标志为1时,确认序号字段才有效。
发送ACK无任何代价,因为32bit的确认序号字段和ACK标志一样,总是TCP首部的一部分。因此,我们看到的一旦一个连接建立起来,这个字段总被设置,ACK标志也总被设置为1。
TCP应用程序提供全双工服务。这意味着数据能在两个方向上独立的进行传输。因此,连接的每一端必须保持每个方向上的传输数据序号。
TCP可以表述为没有选择确认或否认的滑动窗口协议。我们说TCP缺少选择确认是因为TCP首部中确认序号表示发放已成功收到的字节,但不包含确认序号所指的字节。当前还无法对数据流中选定的部分进行确认,例如,如果1~1024字节已成功收到,下一个报文段包含序号从2049 ~ 3072的字节,收端并不能确认这个新的报文段,它所能做的就是发回一个确认序号为1025的ACK,它也无法对一个报文段进行否认。例如,如果收到包含1025 ~ 2048字节的报文段,但它的检验和错,TCP接收端所能做的就是发回一个确认序号为1025的ACK。
首部长度给出首部中32bit的数目。需要这个值是因为任选字段的长度是可变的。这个字段占4bit,因此TCP最多有60字节的首部。然而没有任选字段,正常长度为20字节。
TCP首部中有6个标志比特,它们中的多个可同时设置为1。
URG 紧急指针有效
ACK 确认序号有效、
PSH 接收方应尽快将这个报文段交给应用层
RST 重建连接
SYN 同步序号用来发起一个连接
FIN 发端完成发送任务
TCP的流量控制由连接的每一方通过声明的窗口大小来控制。窗口大小为字节数,起始于确认序号字段指明的值,这个值是接收端正期望收到的字节。窗口大小是一个16bit字段,因而窗口大小最大为65535字节。
检验和覆盖了整个TCP报文段:首部和数据部分。这是一个强制性的字段,由发端计算和存储,并由收端验证。TCP检验和的计算和UDP检验和计算类似,使用一个伪首部。
只有当URG字段为1时,紧急指针才有效。紧急指针是一个正的偏移量,和序号字段的值相加表示紧急数据最后一个字节的偏移量。TCP紧急方式是发端向收端发送紧急数据的一种方式。
最常见的可选字段是最长报文大小,又称MSS。每个连接方通常都在通信的第一个报文段中指明这个选项,它指明本端所能接收的最大长度的报文段。
TCP报文段的数据部分是可选的,在一个连接建立和一个连接终止时,双方交换的报文段只有TCP首部。如果一方没有数据要发送,也使用没有任何数据的首部来确认收到的数据。在处理超时的很多情况,也会发送不带任何数据的报文段。

TCP连接的建立和终止

观察telnet命令在与丢弃服务对应的端口上与主机bsdi建立一条TCP连接及终止的tcpdump输出。
在这里插入图片描述
这7个报文段仅包含TCP首部,没有任何数据。输出格式如下:
源>目的:标志
这个标志代表TCP首部6比特中的4个。以下表示5个标识符的含义。
在这里插入图片描述
第一行中,1415531521:1415531521(0)表示分组的序号是1415531521,而报文段中的数据字节为0。tcpdump显示这个字段的格式是开始序号,冒号,隐含的结尾序号和数据字节数。显示序号和隐含结尾序号的优点是便于了解数据字节大于0时的隐含结尾序号。这个字段只有在满足1)报文段中至少含有一个数据字节或2)SYN,FIN,RST被设置为1时才显示。
第二行字段ack表示确认序号,它只有在首部ACK字段设置为1时显示。
每行显示的字段win4096表示窗口大小。
最后一个字段<mss 1024>表示由发端指明的最大报文段长度选项。发端将不接收超过这个长度的TCP报文段。
在图中,为了建立一条TCP连接:
1,请求端发送一个SYN段指明客户打算连接的服务器端口,以及初始序号(ISN,这里是1415531521)。这个SYN报文段为1。
2,服务器发回包含服务器的初始序号的SYN报文段作为应答,同时,将确认序号设置为客户的ISN加一以对客户的SYN段进行确认。一个SYN将占用一个序号。
3,客户必须将确认序号设置为服务器ISN加一以对服务器的SYN报文段进行确认(报文段3)。
这三个报文段完成连接的建立。这个过程也称为三次握手。
在这里插入图片描述
发送第一个SYN的一端将执行主动打开,接收这个SYN并发送下一个SYN的一端执行被动打开。
当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。ISN随时间而变化,因此每个连接都具有不同的ISN。RFC793指出ISN可看作是一个32bit的计数器,每4ms加一。这样选择序号的目的在于防止在网络中延迟的分组在以后又被重传,而导致某个连接的一方对它做错误的解释。

连接终止协议

建立一个连接需要三次握手,而终止一个连接需要四次握手。这由TCP的半关闭造成。既然一个TCP连接是全双工的,因此每个方向必须独立关闭。这原则就是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向连接。当一端收到一个FIN,它必须通知应用层另一端已经终止了那个方向的数据传送。发送FIN通常是应用层关闭的结果。
收到一个FIN只意味着这一方向上没有数据流动。一个TCP连接在收到FIN后仍能发送数据。而这对利用半关闭的应用来说是可能的。尽管在实际应用中只有很少的TCP程序这样做。
首先进行关闭的一方(即发送第一个FIN)执行主动关闭,而另一方(收到这个FIN)执行被动关闭。上节图中报文段4发起终止连接,它由telnet客户端关闭连接时发出,这在我们键入quit后发生。它将导致客户端发送一个FIN,用来关闭从客户到服务器的数据传输。
当服务器收到FIN后,它发回一个ACK,确认号为收到的序号加一(报文段5)。和SYN一样,一个FIN占用一个序号,同时TCP服务器还向应用程序传送一个文件结束符。接着这个服务器程序就关闭它的连接,导致它的TCP端发送一个FIN(报文段6),客户必须发回一个确认,并将确认号设置为收到的序号加一。
下图显示了一个连接的典型握手顺序,我们省略了序号。在这个图中,发送FIN将导致应用程序关闭它的连接,这些FIN的ACK是由TCP软件自动产生的。
在这里插入图片描述
连接通常是由客户端发起的,这样第一个SYN从客户传到服务器,每一端都能主动关闭这个连接,然而,一般由客户端决定何时终止,因为客户进程通常由用户交互控制,用户会键入诸如quit的命令终止进程。

连接建立的超时

有很多情况导致无法建立连接。一种情况是服务器没有处于正常状态,为了模拟这种状态,我们断开服务器主机的电缆,然后向它发送telnet命令,其tcpdump输出如下。
在这里插入图片描述
在这个输出中,客户端一直发送SYN进行连接,第二次发送SYN与第一个间隔5.8秒,第三个与第二个间隔24秒。
图中没有显示客户端在放弃建立连接尝试前进行SYN重传的时间,我们可以对telnet命令进行计时,发现时间差值76秒。大多数伯克利系统将建立一个新连接的最长时间限制为75秒。

第一次超时时间

在上图中,第一次超时时间5.8秒,将近6秒,第二个超时时间几乎24秒,运行十多次测试后,发现第一次超时时间为5.59~5.93秒,第二次超时则总是24秒。
这是因为BSD版的TCP软件采用一种500ms的定时器,这种定时器用于确认本章的所有各种各样的超时。当我们键入telnet命令时,将建立一个6秒的定时器(12个时钟滴答),但它可能在5.5秒~6秒的任意时刻超时。尽管定时器初始化为12个时钟滴答,但定时计数器会在设置后第一个0 ~ 500ms的任意时刻减一。从那以后,定时器大约每隔500ms减一,但在第一个500ms是可变的(我们使用大约是因为TCP每隔500ms获取系统控制的瞬间,系统内核可能会优先处理其他中断)。
在这里插入图片描述
当滴答计数器为0时,6秒定时器就会超时,这个定时器会在以后的24秒(48个滴答)重新复位。之后的下一个定时器将更接近24秒,因为当TCP的500ms定时器被内核调用时,它就会被修改一次。

服务类型字段

tcpdump输出中出现了符号[tos 0x10],这是IP数据报内服务类型字段。BSD/386中的telnet客户进程将这个字段设置为最小时延。

最大报文段长度

最大报文段长度(MSS)表示TCP传往另一端的最大数据块长度。当一个连接建立时,连接的双方都要通告各自的MSS。我们已经见过的MSS都是1024。这导致IP数据报通常是40字节长:20字节的TCP首部和20字节的IP首部。
在有些书中,将它看作可协商选项,它并不是任何条件下都可协商,当建立一个连接时,每一方都有用于通告它期望收到的MSS选项(MSS选项只能出现在SYN报文段中),如果一方不接收来自另一方的MSS值,则MSS默认为536字节(这个默认值允许20字节的IP首部和20字节的TCP首部以适合576字节的IP数据报)。
一般来说,如果没有分段,MSS越大越好。报文段越大允许每个报文段传送的数据越多,相对IP和TCP首部有更高的网络利用率。当TCP发送一个SYN时,或者是因为一个本地应用进程想发起一个连接,或者是因为另一端主机收到了一个连接请求,它能将MSS值设置为外出接口上的MTU长度减去固定IP首部和TCP首部。对于一个以太网,MSS值可达1460,使用IEEE802.3可达1452字节。
本章涉及的MSS为1024,这是因为许多BSD实现版本需要MSS为512的倍数。
如果目的IP地址为非本地的,MSS通常默认值为536,而区分地址是本地还是非本地的很简单。如果目的IP地址的网络号和子网号都和我们相同,则是本地的,如果目的地址的网络号与我们完全不相同,则是非本地的,如果目的地址的网络号与我们相同而子网号与我们不同,则可能是本地,可能是非本地的。大多数系统都提供了一个配置选项,让系统管理员说明不同的子网属于本地还是非本地。这个选项的设置将确定MSS可以选择尽可能的大(达到外出接口的MTU长度)或者默认值536。
MSS让主机限制另一端发送数据报的长度。加上主机也能控制它发送数据报的长度,这将使以较小MTU连接到一个网络上的主机避免分段。

TCP的半关闭

TCP提供了连接的一端在结束它的发送后还能接受来自另一端数据的能力。这就是所谓的半关闭。正如我们之前提到的只有很少的应用程序使用它。
为了使用这个特性,编程接口必须为应用程序提供一种方式说明"我已经完成了数据的传送,因此发送一个文件结束(FIN)给另一端,但我还想接收另一端发来的数据,直到它给我发来文件结束(FIN)",
如果应用程序不调用close而调用shutdown,且第二个参数值为1,则插口API支持半关闭。然而大多数应用程序通过调用close来终止两个方向的连接。
下图显示了一个半关闭的典型例子。让左方的客户端开始半关闭,当然也可以由另一端开始。开始的两个报文段和TCP连接关闭图中是一样的,初始端发送FIN,接着另一端发出对个FIN的ACK报文段。但后面有所不同,因为接收半关闭的一端仍能发送数据,我们只显示一个数据和一个ack报文段,但可能发送了很多数据和ack报文。当收到半关闭的一端在完成它的数据传送后,将发送一个FIN关闭这个方向的连接,这将传送一个结束符给发起这个半关闭的应用进程。当对第二个FIN进行确认后,这个连接便彻底关闭了。
在这里插入图片描述
为什么要有半关闭?一个例子是Unix上的rsh命令,它将完成在另一个系统上执行命令。如rsh bsdi sort < datafile,将在主机bsdi上执行sort排序命令,rsh命令 标准输入来自文件datafile。rsh将在它与另一个主机上执行的程序间建立一个TCP连接。rsh的操作很简单,它将标准输入datafile复制给tcp连接,并将结果从tcp连接中复制给标准输出。
在这里插入图片描述
在远端主机bsdi上,rshd服务将执行sort程序,它的标准输入和标准输出都是tcp连接。sort程序只有读取到所有输入数据后才能产生输出。所有的原始数据通过tcp连接rsh客户端传送到sort服务器进行排序,当输入到达文件尾时,rsh客户端执行这个tcp连接的半关闭,接着sort服务器在它的标准输入上收到一个文件结束符,对数据进行排序,并将结果写在它的标准输出上,rsh客户端接收来自tcp连接另一端的数据,并将排序的文件复制到它的标准输出上。
没有半关闭,需要其他的一些技术让客户通知服务器,客户端已完成它的数据传送,但仍要接收来自服务器的数据,使用两个tcp连接也可以作为一个选择,但使用半关闭的单连接更好。

tcp的状态变迁图

在这里插入图片描述
在这个图上我们要注意的第一点是一个状态变迁的子集是典型的,我们用粗的实线箭头表示正常的客户端状态变迁状态,用粗的虚线箭头表示正常的服务器状态变迁。
第二点是两个导致进入established状态的变迁对应打开一个连接,而两个导致从established离开的变迁对应关闭一个连接,established状态是连接双方能够进行双向数据传递的状态。
图中左下角四个状态放在一个虚线框内,标为主动关闭,其他两个状态close_wait和last_ack也用虚线框住,并标为被动关闭。
在这个图中11个状态的名称是有意与netstat命令显示的状态名称一致,netstat对状态的命名几乎与RFC793中最初描述的一致,closed状态并不是一个真正的状态,而是这个状态图的假想开始和终点。
从listen到syn_sent的状态是正确的,但伯克利版的tcp并不支持它。只有当syn_rcvd状态是从listen状态进入,而不是从syn_sent状态进入,从syn_rcvd回到listen的状态变迁才有效。这意味着如果我们执行被动关闭(进入listen),收到一个syn,发送一个带ack的syn(进入syn_rcvd),然后收到一个rst,而不是ack,便又回到listen状态等待另一个链接请求的到来。
TCP链接建立和终止对应的状态图:
在这里插入图片描述

2MSL等待状态

TIME_WAIT状态也称2MSL等待状态。每个具体TCP实现必须选择一个报文段最大生存时间MSL,它是任何报文段被丢弃前在网络内的最长时间,我们知道这个时间是有限的,因为tcp报文段以ip数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。
对于一个具体实现所给定的MSL值,处理的原则是:当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIME_WAIT状态停留的时间为2倍的MSL,这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)。
这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的插口不能再被使用,这个连接只能在2MSL结束后再被使用。
遗憾的是,大多数TCP实现强加了更为严格的限制,在2MSL等待期间,插口中使用的本地端口在默认情况下不能再被使用。
在连接处于2MSL等待时,任何迟到的报文段将被丢弃,因为处于2MSL等待的、该插口定义的连接在这段时间内不能被使用。因此在要建立一个有效的连接时,来自该连接的较早替身的迟到报文段作为新连接的一部分不可能不被曲解(一个连接由一个插口对定义,一个连接的新的实例称为该连接的替身)。
在状态图中客户执行主动关闭并进入TIME_WAIT是正常的,服务器通常执行被动关闭,不会进入TIME_WAIT状态,这暗示如果我们终止一个客户程序,并立即重新启动这个客户程序,则这个新客户程序将不能重用相同的本地端口,这不会带来什么问题,因为客户使用本地端口,而并不关心这个端口号是什么。
然而,对于服务器,情况就有所不同,因为服务器使用熟知端口,如果我们终止一个已经建立连接的服务器程序,并试图立即重新启动这个服务器程序,服务器程序将不能把它的这个熟知端口赋值给它的端点,因为那个端口是处于2MSL连接的一部分。在重新启动服务器程序前,它需要1~4分钟。

平静时间的概念

对于来自某个连接的较早替身的迟到报文段,2MSL等待可防止将它解释成使用相同插口对的新连接的一部分,但这只有在处于2MSL等待连接中的主机处于正常状态才有效。如果处于2MSL等待端口的主机出现故障,它会在MSL秒内重启,并立即使用故障前仍处于2MSL的插口对来建立新的连接吗?如果是这样,在故障前从这个连接发出而迟到的报文会被错误的当做属于重启后新连接的报文段,无论如何选择重启后新连接的初始序号,都会发生这种情况。为了防止这种情况,RFC793指出TCP在重启后MSL秒内不建立任何连接,这称为平静时间。

FIN_WAIT-2状态

在FIN_WAIT_2状态我们已经发出了FIN,并且另一端已对它进行了确认。除非我们在实行半关闭,否则将等待另一端意识到它已经接收了一个文件结束符说明,并向我们发送FIN来结束另一端的连接。只有当另一端的程序完成这个关闭,我们这端才会从FIN_WAIT_2状态进入TIME_WAIT状态。
这意味着我们这端可能永远保持这个状态,另一端也处于CLOSE_WAIT状态,并保持这个状态直到应用层决定关闭。

复位报文段

TCP首部中的RST比特是用于复位的。一般来说,无论何时一个报文段发往基准的连接出现错误,TCP都会发出一个复位报文段(这里提到的基准连接是指插口对指明的连接)。

到不存在的端口的连接请求

产生复位的一种情况是当连接请求到达时,目的端口没有进程在听。对于UDP,当一个数据到达目的端口而目的端口没有在使用,将产生一个ICMP不可达的信息,而TCP则用复位。
产生这个例子很容易,用telnet客户程序来指明一个目的端口没有在使用的情况:
在这里插入图片描述
以下是这个命令的分组交换过程:
在这里插入图片描述
在这个图中需要值得注意的是复位报文段中的序号字段和确认序号字段。因为ACK比特在到达的报文段中没有被设置为1,复位报文段的序号被设置0,确认序号被设置为进入的ISN加上数据字节数。尽管在到达的报文段中没有真正的数据,但SYN比特从逻辑上占用1字节的序号空间,因此这个例子中复位报文段中确认序号被设置为ISN与数据长度(0),SYN比特所占的1的总和。

异常终止一个连接

终止一个连接的正常方式是发送一个FIN,有时也称为有序释放,因为在所有排队数据都已经发送后才发送FIN,正常情况下没有任何数据丢失。但也有可能发送一个复位报文段而不是FIN来中途释放一个连接,有时这称为异常释放。异常终止一个连接对应用程序来说有两点:1)丢弃任何待发数据并立即发送复位报文段;2)RST的接收方会区分另一端执行的是异常关闭还是正常关闭。应用程序使用的API必须提供产生异常关闭而不是正常关闭的手段。
socket api通过“linger on close”选项(SO_LINGER)提供了这种异常关闭的能力。以下是一个异常终止程序发送RST的tcpdump输出。
在这里插入图片描述
前三行是建立连接,第四行是发送的数据,第五行是数据的确认,第六行是终止客户程序而键入的文件结束符。由于我们指明使用异常关闭而不是正常关闭,因此主机bsdi端的TCP发送一个RST而不是FIN,RST报文包含一个序号和一个确认序号。需要注意的是RST报文段不会导致另一端产生任何响应,另一端根本不进行确认。收到RST的一方将终止该连接,并通知应用层复位连接。

检测半打开连接

如果一方已经关闭或异常终止连接而另一方却不知道,我们将这样的TCP连接称为半打开的。任何一端的主机异常都可能导致发生这种情况,只要不打算在半打开的连接上传输数据,仍处于连接状态的一方就不会监测到另一方出现异常。
半打开连接的另一个常见原因是当客户主机突然掉电而不是正常的结束客户应用程序后关机。这可能发生在使用PC机作为telnet的客户主机上,例如,用户在一天的工作结束时关闭PC机电源。当关闭PC机电源时,如果不再有要向服务器发送的数据,服务器将永远不知道客户程序已经消失。当用户在第二天打开PC机,并启动新的telnet客户程序,在服务器主机上会启动一个新的服务器程序,这样会导致服务器主机中产生很多半打开的TCP连接。
能很容易的建立半打开连接,在bsdi上运行telnet客户程序,通过它与svr4的丢弃服务建立连接,我们键入一行字符,然后通过tcpdump进行观察,然后断开服务器主机和以太网的电缆,可以模拟服务器出现异常。服务器主机重启后,重新接上电缆,并从客户向服务器发送一个字符。由于服务器的TCP已经重新启动,它将丢失复位前连接的所有信息,因此它不知道数据报文段中提到的连接。TCP的处理原则是接收方以复位作为应答。该例子的tcpdump输出显示:
在这里插入图片描述
1~3行是连接建立过程,第4行向服务器发送数据,第5行确认数据,然后是断开以太网电缆,重新启动svr4,并重新接上电缆,这个过程几乎需要190秒,接着从客户端键入一行数据,当键入回车后,数据被发往服务器,这导致服务器产生一个响应,但要注意的是服务器应该重新启动,它的ARP高速缓存为空,因此需要一个ARP请求和应答,第9行表示RST被发送出去,客户收到复位报文段后显示连接已被另一端终止。

同时打开

两个应用程序同时彼此执行主动打开的情况是可能的,尽管发生的可能性小,每一方必须发送一个SYN,且这些SYN必须传给对方,这需要每一方使用一个对方熟知的端口号作为本地端口,这称为同时打开。
例如,主机A的应用程序使用本地端口7777,并与主机B的端口8888执行主动打开,主机B的应用程序使用本地端口8888与主机A的端口7777执行主动打开。
TCP特意设计了可以同时处理打开,对于同时打开它仅建立一条连接而不是两条。
当出现同时打开的情况,两端几乎同时发送SYN,并进入SYN_SENT状态,当每一端收到SYN,状态变为SYN_RCVD,同时它们都再发SYN并对收到的SYN确认,当双方都收到SYN及对应的ACK,状态都变迁为established,以下是状态变迁图:
在这里插入图片描述
一个同时打开的连接需要交换4个报文段,比正常三次握手多一个。此外没一端既是客户端也是服务器。

同时关闭

我们之前讨论过一方发送一个FIN执行主动关闭,双方都执行主动关闭是可能的,TCP协议也允许同时关闭。当应用层发出关闭命令时,两端均从established变为FIN_WAIT_1,这将导致双方各发送一个FIN,两个FIN经过网络传送分别到达另一端,收到FIN后,由FIN_WAIT_1变为CLOSING,并发送最后ACK,当收到最后ACK后,状态变为TIME_WAIT。
在这里插入图片描述

TCP选项

TCP首部可以包含选项部分,仅在最初的TCP规范中定义的选项是选项标结束,无操作和最大报文段,在我们的例子中,几乎每个SYN报文段我们都遇到过MSS选项。当前TCP选项的格式:
在这里插入图片描述
每个选项的开始是1字节的kind字段,说明选项的类型。kind字段为0或1的选项仅占1个字节,其他选项kind字节后还有len字节,它说明的长度是总长度,包括kind字节和len字节。设置无操作选项的原因在于允许发方填充字段为4字节的倍数。

TCP服务器的设计

大多数的TCP服务器进程是并发的,当一个新的连接请求到达服务器时,服务器接收这个请求,并调用一个新的进程来处理这个客户请求。不同的操作系统使用不同的技术来调用新的服务器进程,在Unix系统中,常用的技术是使用fork函数来创建新的进程,如果系统支持,也可以使用线程。
我们感兴趣的是TCP与若干并发服务器的交互作用。当一个服务器进程接收一个来自客户进程的服务请求时如何处理端口?如果多个连接请求几乎同时到达会发生什么情况?

TCP服务器端口号

通过观察任何一个TCP服务器,我们能了解TCP如何处理端口号,我们使用netstat命令来观察telnet服务器,下面是没有telnet连接时的显示。
在这里插入图片描述
-a标志显示网络中的所有主机端,而不仅仅是established的主机端,-n标志以点十分进制显示IP地址,而不是通过DNS将IP地址转化为主机名,同时还要求显示端口号而不是服务名称,-f net则要求仅显示TCP和UDP的主机。
显示本地地址为* .23,表示传入的连接将被任何一个本地接口所接受。本地端口23位telnet熟知端口号。远端为* . *表示还不知道远端IP地址和端口号,因为该端还处于listen状态,正在等待连接请求的到达。
现在我们在主机slip上启动一个telnet客户程序来连接这个telnet服务器, netstat输出如下:
在这里插入图片描述
第一行处于established状态,本地IP为该连接到达的接口。处于listen状态的服务器仍然存在,这个服务器进程是当前telnet服务器用于接收其他的连接请求。当传入的连接请求到达并被接收时,系统内核中的tcp模块就创建一个处于established的进程,另外,处于established状态的端口不会变化,与listen状态的进程相同。
现在我们在主机slip上启动另一个telnet客户进程,并仍与这个telnet服务器相连,其netstat输出如下:
在这里插入图片描述
现在我们有两条从相同主机到相同服务器的established连接,它们的本地端口号是23,远端端口号不同,不会造成冲突,因为每个telnet客户进程要使用一个外设端口,并且这个外设端口会选择主机slip当前未使用过的端口,因此它们的端口号肯定不同。
这个例子重申了tcp使用本地地址和远端地址组成的四元组,目的IP,目的端口,源IP,源端口来处理传入的多个连接请求。

TCP的交互数据流

如果按照分组数量计算,约有一半的TCP报文段包含成块数据,另一半则包含交互数据。如果按字节计算,则成块数据和交互数据比例约为90%和10%,这是因为成块数据的报文段基本上都是满长度的,而交互数据则小的多。很明显,TCP需要同时处理这两类数据,但使用的处理算法则有所不同。
在一个Rlogin连接上键入一个交互命令时所产生的数据流,每一个交互按键都会产生一个分组,也就是说每次从客户传到服务器的是一个字节的按键,而且,Rlogin需要远程服务器回显我们键入的字符。这样会产生四个报文段:1,来自客户的按键, 2,来自服务器的按键确认, 3,来自服务器的按键回显,4,来自客户的按键回显确认。我们一般可以将报文段2和3合并,按键确认和按键回显一起发送。
下图是我们键入5个字符date\n时的数据流。第一行客户发送字符d到服务器,第二行是该字符的确认及回显,第三行是回显字符的确认。与字符a有关的是4~6行,与字符t有关的是7 ~ 9行,第10 ~ 12行与字符e有关,第3 ~ 4,6 ~ 7,9 ~ 10,12 ~13行之间的半秒左右的时间差是键入两个字符之间的时延。注意13 ~ 15行稍有不同,从客户发送服务器的是一个字符(按下return键后产生的Unix系统的换行符),而回显的则是两个字符,这两个字符分别是回车和换行字符,它的作用是将光标回移到左边并移动到下一行。第16行是来自服务器的date命令输出,这30个字节由28个字符和最后的回车换行组成,紧接着从服务器发往客户的7个字符是在服务器主机上的客户提示符,第19行确认了这7个字符。
在这里插入图片描述
注意TCP是怎么确认的,第一行以序号0发送数据字节,第二行通过将确认序号设为1,也就是最后成功收到的字节的序号加1,来对其进行确认。
下图是数据交换的时间系列,把从bsdi发送到svr4的7个ack确认标记为经受时延的ACK,通常TCP在接收到数据时并不立即发送ack,相反,它推迟发送,以便将ack与需要沿该方向发送的数据一起发送,有时称这种现象为捎带ack。绝大多数采用的时延为200ms,也就是说TCP将以最大200ms的时延等待是否有数据一起发送。如果观察bsdi接收到的数据和发送ack之间的时间差,就会发现它们是随机的,123.5,109.0。相反观察到发送ack的实际时间为139.0,539.3,这些时间之间的差则是200ms的倍数。这里所发生的情况是因为TCP使用了一个200ms的定时器,该定时器以相对内核引导的200ms固定时间溢出,由于将要确认的数据是随机到达的(在16.4,474.3等),TCP在内核的200ms定时器的下一次溢出时得到通知,这有可能是1~200ms中的任一时刻。
在这里插入图片描述
如果观察svr4为产生所收到的每个字符的回显所使用的时间,则这些时间分别是16.5,16.3,16.5等,由于这个时间总是小于200ms,因此我们在另一端从来没有观察到一个经受时延的ack,在经受时延的定时器溢出前总是有数据要发送。

Nagle算法

在一个Rlogin连接上客户一般每次发送一个字节到服务器,这产生41字节的分组,20字节的IP首部,20字节的TCP首部和1一个字节的数据。在局域网上,这些小分组通常不会引起麻烦,因为局域网一般不会出现拥塞,但在广域网上,这些小分组会增加拥塞出现的可能,一种简单并且好的做法是采用nagle算法。
该算法要求一个tcp连接上最多只能有一个未被确认的未完成的小分组,在该分组的确认到达前不能发送其他小分组。相反,tcp收集这些少量分组,并在确认到来时以一个分组发出去。该算法的优越之处是它是自适应的,确认到达越快,数据也就发送越快,而在希望减少微小分组的低速广域网上,则会发送更少的分组。

关闭Nagle算法

有时我们也需要关闭nagle算法,一个典型的例子是x窗口系统服务器:小消息(鼠标移动)必须无时延的发送,以便进行某种操作的交互用户提供实时反馈。这里将举一个更容易说明的例子,在一个交互注册过程中键入终端的一个特殊功能键,这个功能键将产生多个字符序列,经常从ASCII码的转义字符开始。如果tcp每次得到一个字符,它很可能发送序列中的第一个字符,然后缓存其他字符并等待该字符的确认,但当服务器收到该字符后,它并不发送确认,而是继续等待接收序列中的其他字符,这就会经常触发服务器的经受时延的确认算法,表示剩下的字符没有在200ms内发送,对交互用户而言,这将产生明显的时延。插口API用户可以使用TCP_NODELAY选项来关闭nagle算法。

TCP成块数据流

TCP使用被称为滑动窗口协议的流量控制方法,该协议允许发送方在停止并等待确认前可以连续发送多个分组,由于发送方不必每发送一个分组就停下来等待确认,因此该协议可以加速数据的传输。
我们从主机svr4向网络发送8个1024字节的数据,下图显示了这个过程的时间序列。发送方首先传送3个数据报文段(4~6),下一个报文段7仅确认了前两个数据报文段,确认号为2048,这是因为当一个分组到达时,它首先被设备中断例程所处理,然后放置到IP的输入队列中,三个报文段4,5,6依次到达并按接收顺序放置在IP输入队列中,IP将按同样的顺序将它们交给TCP,当TCP处理报文段4时,该连接被标记为产生一个经受时延的确认,TCP处理下一个报文段5,由于TCP现在有两个未完成的报文段需要确认,因此产生一个序号为2048的ACK,并清除该连接产生经受时延的确认标志,TCP处理下一个报文段6,连接又被标记为产生经受时延的确认,在报文段9到来之前,由于时延定时器溢出,因此产生一个序号为3073的ACK(报文段8),报文段8的窗口大小为3072,表明TCP在接收缓存中还有1024字节的数据等待被应用程序读取。
报文段11~16说明了通常使用的“隔一个报文段确认”的策略,报文段11,12,13到达并被放入IP的接收队列,当报文段11被处理时,连接被标记为产生一个经受时延的确认,当报文段12被处理时,它们的ACK产生且经受时延的确认标志被清除,报文段13使得连接再次标记为产生经受时延的确认,但在定时器溢出之前,报文段15处理完毕,因此该确认立即被发送。
在这里插入图片描述
注意到报文段7,14,16中的ACK确认了两个报文段是很重要的,使用TCP滑动窗口协议时,接收方不必确认每一个接收到的分组,在TCP中,ACK是累积的,它标识接收方已正确收到了一直到确认号减1的所有字节。在本例中,三个确认的数据为2048字节而两个确认的数据是1024字节。
用tcpdump看到的是TCP的动态活动情况,我们在线路上看到的分组顺序依赖许多无法控制的因素,发送方TCP的实现,接收方TCP的实现,接收进程读取数据和网络的动态性。对于两个TCP而言,没有一个单一的,正确的方法来交换给定数量的数据。为了显示情况怎么变化,下图显示了同样两个主机之间交换同样数据时另一个时间系列。一些情况发生了变化,这一次接收方没有发送一个3073的ACK,而是等待并发送了4097的ACK,接收方仅发送了四个ACK,三个确认了2048字节,另一个确认了1024字节。最后1024字节的数据的ACK出现在报文段17中,与FIN的ACK一道发送。
在这里插入图片描述
快的发送方和慢的接收方
下图显示了另一个时间序列,这次是从一个快的发送方到一个慢的接收方,它的活动状态又有所不同。发送方发送4个背靠背的数据报文段去填充接收方的窗口,然后停下来等待一个ACK,接收方发送ACK(报文段8),但通告窗口大小为0,说明接收方已收到所有数据,但这些数据都在接收方的TCP缓冲区,因为应用程序还没有机会读取这些数据,另一个ACK在17.4ms发送,表示接收方现在可以接受另外4096个字节的数据,它虽然看起来像一个ACK,但是不确认任何新数据,只是用来增加窗口的右边沿,称为窗口更新。
发送方发送最后4个报文段(10~13),再次填充了接收方的窗口,注意到报文段13中包含两个比特标志,PUSH和FIN,随后从接收方传来另外两个ACK,它们确认了最后的4096字节数据和FIN(标号为8192)。
在这里插入图片描述

滑动窗口

下图用可视化的方法显示了我们上述观察到的滑动窗口协议。
在这里插入图片描述
在这个图中,我们将字节从1到11进行标号,接收方通告的窗口为提出的窗口,它覆盖了从第4字节到第9字节的区域,表明了接收方已确认了包含第3字节在内的数据,且通告窗口大小为6,窗口大小是与确认序号相对应的,发送方计算它的可用窗口,该窗口表明多少数据可以立即被发送。当接收方确认数据后,这个滑动窗口不时的向右移动,窗口两个边沿的相对运动增加或减少了窗口的大小,我们使用三个术语描述窗口左右边沿的移动。
1,窗口左边沿向右边沿靠近为窗口合拢,这种现象发生在数据被发送和确认时。
2,当窗口右边沿向右移动时将允许发送更多的数据,我们称之为窗口张开。这种现象发生在另一端的接收进程读取已经确认的数据并释放TCP的接收缓存时。
3,当右边沿向左移动时,我们称为窗口收缩,RFC强烈建议不要使用这种方式,但TCP必须在某一端产生这种情况时进行处理。

窗口大小

由接收方提供的窗口的大小通常由接收进程控制,这将影响TCP的性能。我设置服务器应用程序接收缓存为6144个字节,我们在sun上启动客户程序并发送8192字节的数据。下图报文段2提供的窗口大小为6144字节,由于这是一个比较大的窗口,因此客户立即连续发送了6个报文段,然后停止,报文段10确认了所有数据,但提供的窗口大小为2048,这很可能是接收程序没有机会读取多余2048字节的数据,报文段11,12完成了客户的数据传输,且最后一个报文段带有FIN标志。报文段13包含与报文段10相同的确认号,但通告了一个更大的窗口大小,报文段14确认了最后的2048字节的数据和FIN,报文段15,16仅用于通告一个更大的窗口大小。报文段17,18完成通常的关闭过程。
在这里插入图片描述

PUSH标志

在每一个TCP例子中,我们都看到了PUSH标志,发送方使用该标志通知接收方将所收到的数据全部提交给应用程序,这里的数据包括与PUSH一起传输的数据以及接收方TCP已经为接收进程接收到的其他数据。
在最初的TCP规范中,一般假定编程接口允许发送进程告诉它的TCP何时设置PUSH标志,例如,在一个交互程序中,当客户发送一个命令给服务器,它设置PUSH标志并停下来等待服务器的响应。通过允许客户应用程序通知其TCP设置PUSH标志,客户进程通知TCP在向服务器发送一个报文段时不要因等待额外数据而使已提交数据在缓冲区滞留。类似的,当服务器TCP接收到一个设置了PUSH标志的报文段时,它需要立即将这些数据递交给服务器进程而不能等待判断是否有额外数据到达。

慢启动

在前面的例子中,发送方一开始便向网络发送多个报文段,直到达到接收方通告的窗口大小,当接收方和发送方在同一局域网时,这种方式是可以的。但是如果在发送方和接收方之间存在多个路由器和速率较慢的链路时,这可能会出现一些问题,一些中间路由器必须缓存分组,并且可能会耗尽存储器空间。现在,TCP需要支持一种慢启动的算法,该算法通过观察到新分组进入网络的速率应该与另一端返回确认的速率相同而进行工作。
慢启动为发送方的TCP增加了另一个窗口,拥塞窗口,记为cwnd,当与另一个网络的主机建立TCP连接时,拥塞窗口被初始化为1个报文段(即另一端通告的报文段大小),每收到一个ACK,拥塞窗口就增加一个报文段,发送方取拥塞窗口与通告窗口中的最小值作为发送上限,拥塞窗口是发送方使用的流量控制,而通告窗口则是接收方使用的流量控制。
发送方开始时发送一个报文段,然后等待ACK,当收到该ACK时,拥塞窗口从1增加为2,即可以发送两个报文段,当收到这两个报文段的确认时,拥塞窗口就增加为4,这是一种指数关系。在某个点上可能到达了互联网的容量,于是中间路由器开始丢弃分组,这就通知发送方它的拥塞窗口开的过大。

成块数据的吞吐量

我们看下窗口大小,窗口流量控制以及慢启动对成块数据的TCP连接的吞吐量的影响。下图显示左边的发送方和右边的接收方之间的一个TCP连接的时间系列。
在这里插入图片描述
在时间0,发送方发送了一个报文段,由于发送方处于慢启动中,因此继续发送以前它必须等待数据段的确认。在时间1,2,3,报文段从左向右移动一个时间单元,在时间4接收方读取这个报文段并产生确认,经过时间5,6,7,ACK移动到左边发送方,我们有一个8时间单元的往返时间RTT。当发送方收到ACK后,它的拥塞窗口为2个报文段,此时在时间8,9发送两个报文段,这两个报文段向右传给接收方,在时间12,13接收方产生两个ACK,这两个返回到发送方的ACK的时间间隔与报文段之间的间隔一致,被称为TCP的自计时行为。由于接收方只有在数据到达后才产生ACK,因此发送方接收到的ACK的时间间隔和数据到达接收方的间隔是一致的。2个ACK到达后,使拥塞窗口变为4,可发送4个报文段,4个ACK的到达可使拥塞窗口变为8,可发送8个报文段。

带宽时延乘积

现在来回答窗口应该设置多大的问题,在我们的例子中,作为最大的吞吐量,发送方在任何时候有8个已发送的报文段未确认。接收方的通告窗口必须不小于这个数目,因为通告窗口限制了发送方能够发送的段的数目。可以计算通道的容量为capacity(bit)=bandwidth(b/s)*round-trip time(s)。一般称之为带宽时延乘积,这个值依赖于网络速度和两端的RTT,可以有很大的变动,例如,一条穿越美国(RTT为60s)的T1电话线路(1544000b/s)的带宽时延乘积为11580字节。但是一条穿越美国的T3电话线路(45000000b/s)的带宽时延乘积为337500字节,这个数值超过了最大所允许的TCP通告窗口大小(65535)。
无论是带宽还是时延均会影响发送方和接收方之间通路的容量,一个增加了一倍的RTT也会使通路容量增加一倍,增加一倍的带宽也可以使管道容量增加一倍。

拥塞

当数据到达一个大的管道(如快速局域网)并向一个较小的管道(如较慢的广域网)发送时就会发生拥塞,当多个输入流到达一个路由器,而路由器的输出流小于这些输入流的总和时也会发生拥塞。下图显示了一个典型的大管道向小管道发送报文的情况,之所以说典型,是因为大多数主机都连接在局域网上,并通过一个路由器与速率相对较低的广域网相连。
在这里插入图片描述
在该图中,我们标记R1为瓶颈,因为它是拥塞发生的地方,它从左侧速率较高的局域网接收数据并向右侧速率较低的广域网发送,当路由器R2将所接受到的分组发送到右侧的局域网时,这些分组之间维持与其左侧广域网上相同的间隔,尽管局域网拥有更高的带宽。类似的,返回的确认之间的间隔也与其在路径中最慢的链路上的间隔一致。
在上图中,已经假定发送方不启用慢启动,它按照局域网的带宽尽可能的发送编号为1~20的报文段,ACK之间的间隔与最慢链路一致。假定瓶颈路由器具有容纳这20个分组的缓存。如果这个不能保证,就会引起路由器丢弃分组。

紧急方式

TCP提供紧急方式,它使一端可以告诉另一端有些具有某种方式的紧急数据已经放置在普通数据流中,另一端被通知这个紧急数据已被放置在普通数据流中,由接收方决定如何处理。可以通过设置TCP首部中的两个字段来发出这种从一端到另一端的紧急数据已被放置在数据流中的通知。URG比特被置为1,并且一个16bit的紧急指针被置为一个正的偏移量,该偏移量必须与TCP首部的序号字段相加,以便得出紧急数据的最后一个字节的序号。
TCP必须通知接收进程,何时已收到一个紧急数据指针以及何时某个紧急数据指针还不在此连接上,或者紧急指针是否在数据流中向前移动。接着接收进程可以读取数据流,并必须能够告知何时碰到了紧急指针。只要从接收方当前读取的位置到紧急指针之间有数据存在,就认为程序处在紧急方式,在紧急指针通过之后,应用程序便转回到正常方式。TCP本身对紧急数据知之甚少,没有办法指明紧急数据从数据流何处开始,TCP通过连接传送的唯一信息就是紧急指针已经开始和指向紧急数据最后一个字节的指针,其他事情留给应用程序去做。
不幸的是,许多实现不正确的称TCP的紧急方式为外带数据,如果一个应用程序确实需要一个独立的外带信道,第二个TCP连接是达到这个目的的最简单的方法。
紧急方式有什么作用呢,两个最常见的例子是Telnet和Rlogin从服务器到客户使用紧急方式是因为这个方向上的数据流很可能要被客户TCP停止,但是如果服务器进入紧急方式,尽管它不能够发送任何数据,服务器TCP也会立即发送紧急指针和URG标志,当客户TCP接收到这个通知时就会通知客户进程,于是客户可以从服务器读取其输入,打开窗口并使数据流动。如果接收方处理第一个紧急指针之前,发送方多次进入紧急指针会怎么样呢,在数据流中的紧急指针会向前移动,而其在接收方的前一个位置将丢失。接收方只有一个紧急指针,每当对方有新的值到达时它将会被覆盖,这意味着如果发送方进入紧急指针时所写的内容对接收方非常重要,那么这些字节数据必须被发送方用某种方式特别标记。我们将看到Telnet通过在数据流中加入一个值为255的字节作为前缀来标记它所有的命令。

TCP超时与重传

TCP提供可靠的运输层,它使用的方法之一就是确认从另一端收到的数据。但数据和确认都有可能会丢失,TCP通过在发送时设置一个定时器来解决这个问题。如果当定时器溢出时还没收到确认,它就重传该数据,对任何实现而言,关键之处就在于超时和重传的策略,即怎么决定超时间隔和如何确认重传频率。
对每个连接,TCP管理4个不同的定时器。
1,重传定时器使用于当希望收到另一端的确认,我们将详细讨论这个定时器及一些相关问题,如拥塞避免。
2,坚持定时器使窗口大小信息保持不断流动,即使另一端关闭了其接收窗口。
3,保活定时器可检测到一个空闲连接的另一端何时崩溃或重启。
4,2MSL定时器测量一个处于TIME_WAIT状态的时间。

超时与重传的简单例子

首先观察TCP所使用的重传机制,我们将建立一个连接,发送一些分组来证明一切正常,然后拔掉电缆,发送更多的数据,再观察TCP的行为。tcpdump的结果如下。
在这里插入图片描述
1,2,3行表示正常的建立连接过程,第4行是字符传输,第5行确认,接着我们从svr4拔掉电缆,第6行表示下一行字符传输,第7~18行是这个报文段的重传,19行是发送方的TCP最终放弃并发送一个复位信号。现在检查重传之间不同的时间差,它们取整后分别为1,3,6,12,24,48和多个64秒,第一次发送后所设置的超时时间实际上为1.5秒,此后该时间在每次重传时增加1倍直至64秒。这个倍乘关系被称为指数退避,首次分组传输和复位信号传输之间的时间差约为9分钟,该时间在目前的TCP实现中总是不可变的。

往返时间测量

TCP超时和重传中最重要的部分就是对一个给定的连接的往返时间RTT的测量。由于路由器和网络流量均会变化,因此我们认为这个时间可能经常会发生变化,TCP应该跟踪这些变化并相应的改变其超时时间。首先TCP必须测量在发送一个带有特别序号的字节和接收到包含该字节的确认之间RTT。在上一章中,我们提到数据报文段和ACK之间并没有一一对应关系,这意味着发送方可以测量到的一个RTT,是在发送1~1024字节的报文段,和接收1 ~ 1024字节的确认报文段之间的时间。用M表示测量到的RTT。
最初的TCP规范使TCP使用低通过滤器来更新一个平滑的RTT估计器(记为O)。R<-αR+(1-α)M。这里α是一个推荐值为0.9的平滑因子,每次进行新测量时,这个平滑的RTT将得到更新。每个新估计的90%来自前一个估计,10%取自新的测量。该算法在给定这个随RTT变化而变化的平滑因子的条件下,RFC793推荐的重传超时时间RTO的值应该设置为RTO=Rβ。这里的β是一个推荐值为2的时延离散因子。Jcobson详细分析了在RTT变化范围很大时,使用这个方法无法跟上这种变化,从而引起不必要的重传。正如Jcobson记述的那样,当网络已经处于饱和状态时,不必要的重传会增加网络的负载,对网络而言这就像火上浇油一样。
除了被平滑的RTT估计器,所需要做的还有跟踪RTT的方差。在往返时间变化起伏很大时,基于均值和方差来计算RTO,将比作为均值的常数倍数来计算RTO能提供更好的响应。均值偏差是对标准偏差很好的逼近,但却更容易计算,这就引出了下面用于每个RTT测量M的公式。
在这里插入图片描述
这里的A是被平滑的RTT而D是被平滑的均值偏差,Err是刚得到的测量结果与当前的RTT估计器之差。A和D均被用于计算下一个重传时间。增量g起平均作用,取0.125,偏差的增益为h,取0.25。当RTT变化时,较大的偏差增益将使RTO快速上升。Jcobson指明了一种使用整数运算来计算这些公式的方法,并被许多实现所采用。将Jcobson与最初的方法比较,我们发现被平滑的均值计算公式是类似的,而增益可使用不同的值,而且Jcobson计算RTO的方式依赖于被平滑的RTT和被平滑的均值偏差,而最初的方法则使用了被平滑的RTT的一个倍数。

Karn算法

在一个分组重传时会产生这样一个问题:假定一个分组被发送。当超时发生时,RTO正如之前所示进行退避,分组以更长的RTO进行重传,然后收到一个确认,那么这个ACK是针对第一个分组的还是针对第二个分组呢,这就是所谓的重传二义性。[Karn and Partridge 1987]规定,当一个超时和重传发生时,在重传数据的确认最后到达之前,不能更新RTT估计器,因为我们并不知道ACK对应哪儿个传输,也许第一次传输被延迟而并没有丢弃,也有可能第一次传输的ACK被延迟。并且,由于数据被重传,RTO已经得到了一个指数退避,我们在下一次传输时使用这个退避后的RTO,对一个没有被重传的报文段,除非收到一个确认,否则不要计算新的RTO。

往返时间RTT的例子

现在通过一些例子来检查TCP的超时和重传,慢启动以及拥塞避免等方方面面的实现细节。将32768字节的数据从主机slip发送到另一台主机的丢弃服务上,由于slip和bsdi之间的MTU为296字节,因此这些操作会产生128个报文段,每个报文段包含256字节的用户数据,整个传输过程的时间约为45秒,我们观察到了一个超时和三次重传。当该传输进行时,在slip上使用tcpdump来截获所有的发送和接收的报文段,并通过使用-D选项打开插口排错功能,这样便可以通过运行一个修改后的trpt程序来打印出连接控制块中与RTT,慢启动和拥塞避免等有关的多个变量。对于给出的跟踪结果下图没有完全显示,但是将在后续看到它的各个部分,下图显示前5秒中的数据和确认的传输过程。与前面的tcpdump的输出相比,下图对显示稍微做了修改,虽然仅能够在运行tcpdump的主机上测量分组发送和接收的时间,但下图希望显示出分组正在网络中传输以及接收主机何时能产生ACK。
在这里插入图片描述

往返时间RTT的测量

在上图左边的时间轴上有三个括号,它们表明为进行RTT计算对哪儿些报文段进行了计时,并不是所有报文段都被计时。大多数源于伯克利的TCP实现在任何时候对每个连接测量一次RTT值,在发送一个报文段时,如果给定连接的定时器已经被使用,则该报文段不被计时。在每次调用500ms的TCP的定时器例程中,就增加一个计数器来完成计时,这意味着如果一个报文段的确认在它发送550ms后到达,则该报文段的往返时间RTT是1个滴答或2个滴答。对每个连接而言,除了这个滴答计数器,报文段中数据的起始序号也被记录下来。当收到一个包含这个序号的确认后,该定时器被关闭。如果ACK到达时数据没有被重传,则被平滑的RTT和被平滑的均值偏差将基于这个新测量进行更新。
上图连接上的定时器在发送报文段1时启动,并在确认到达时终止。尽管它的RTT是1.061秒,但插口排错的信息显示该过程经历了3个TCP时钟滴答,即1500ms。下一个被计时的报文段3,当2.4秒后传输报文段4时,由于连接的定时器已被启动,因此该报文段不能被计时。当报文段5到达时,确认了正在被计时的数据。虽然我们从tcpdump的输出结果可以看到RTT是0.808秒,但它的RTT被计算为1个时钟滴答。定时器在发送报文段6时再次被启动,并在1.015秒后接受它的确认时终止,测量的RTT是2个滴答。报文段7和报文段9不能被计时,因为定时器已经被使用。而且当收到报文段8(第769字节的确认)时,由于该报文段不是正在计时的数据的确认,因此什么也没有更新。
下图显示了本例中通过tcpdump的输出得到的实际RTT与时钟滴答计数之间的关系。
在这里插入图片描述
在图的上端表示间隔为500ms的时钟滴答,图的下端表示tcpdump的输出时间及定时器何时被启动和关闭。在发送报文段1和接收报文段2之间经历了3个滴答,时间为1.061秒,因此假定第一个滴答发生在0.03秒(第一个滴答一定在0~0.061秒之间),接着该图表示了第二个被测量的RTT为什么被计为1个滴答,而第三个被计为2个滴答。

RTT估计器的计算

让我们看一下RTT估计器(平滑的RTT和平滑的均值偏差)是如何被初始化和更新的,以及每个重传超时是如何被计算的。变量A和D分别被初始化为0和3秒,初始的超时重传使用下面的公式计算:RTO=A+2D=0+23=6秒。(因子2D只在这个初始化计算中使用,正如前面提到的,以后使用4D和A相加计算RTO)。这就是传输初始SYN所使用的RTO。结果这个初始SYN丢失了,然后超时并引起重传,下图给出了tcpdump输出文件的前4行。
在这里插入图片描述
当超时在5.802秒发生后,计算当前RTO的值是RTO=A+4D=0+4
3=12秒,因此应用于RTO的退避指数为12。由于这是第一次超时,使用倍数2。因此下一次超时时间取值为24秒。再下一次为超时时间的4倍,得出值48秒。
ACK在重传后467ms到达,A和D的值没有被更新,这是因为Karn算法对重传的处理比较模糊。下一个发送的报文段是第4行的ACK,但它只是一个ACK,所以没有被计时(只有数据报文段才会被计时)。
当发送第一个数据报文段时,RTO没有改变,这同样是由于Karn算法。当前的24秒一直被使用,直到进行一个RTT测量。
当第一个数据报文段的ACK到达时,经历了3个时钟滴答,估计器被初始化为A=M+0.5=1.5+0.5=2, D=A/2=1。在前面,A和D初始化为0,RTO的初始计算值为3。这是使用第一个RTT的测量结果M对估计器进行首次计算的初始值,计算的RTO值为RTO=A+4D=2+41=6s。当第二个数据报文段的ACK到达时,经历了一个时钟滴答,估计器按如下更新:
Err=M-A=0.5-2=-1.5,A=A+gErr=2-0.125
1.5, D=D+h(|Err|-D)=1+0.25*(1.5-1)=1.125,RTO=A+4D=1.8125+4*1.125=6.3125。Err,A和D的定点表示与实际使用的定点计算有一些微小差别,这些不同使RTO取6秒。

拥塞举例

现在观察一下数据报文段的传输过程,下图显示了报文段中数据的起始序号和该报文段发送时间的对比图。它提供了一种较好的数据传输的可视化过程,通常代表数据的点将向上和向右移动,这些点的斜率表示传输速率,当这些点向下向右移动表示发生了重传。
在这里插入图片描述
可以发现发生在时刻10,14,21附近的三个重传,我们还可以看到这是三个点中只进行了一个报文段重传,因为只有一个点下垂低于向上的斜率。仔细检查这几个下垂点中的第一个,整理tcpdump输出如下图。
在上图中,除了下面将要讨论的报文段72,已经去掉了其他所有的窗口通告,主机slip总是通告窗口大小为4096,主机vangogh则通告8192。
在这里插入图片描述
看来报文段45丢失或损坏了,这一点无法从该输出上确认,能够在主机slip上看到对第6657字节(报文段58)以前数据的确认(不包括6657在内),紧接着的是带有相同序号的8个ACK。正是接收到报文段62,也就是第三个重传ACK,才引起自序号6657开始的数据报文段(报文段63)的重传。的确,源于伯克利的TCP实现对收到的重复ACK进行计时,当收到第3个时,就假定一个报文段已经丢失并重传自那个序号的一个报文段,这就是Jcobson快速重传算法,该算法通常与的快速恢复算法一起使用。注意在重传后(报文段63),发送方继续正常的数据传输(报文段67,69,71),TCP不需要等待对方确认重传。
现在检查一下在接收端发生了什么,当按序收到正常数据(报文段43),接收TCP将255个字节的数据交给用户进程,但下一个收到的报文段46是失序的:数据的开始序号(6913)并不是期望收到的序号(6657),TCP保存256字节的数据,并返回一个已成功接收数据的最大序号加一(6657)的ACK。被vangogh接收到的后面7个报文段也是失序的,接收方保存这些数据并产生重复ACK。目前TCP尚无办法告诉对方缺少一个报文段,也无法确认失序数据,此时主机vangogh所能够做的就是继续发送确认序号为6657的ACK。当缺少的报文段到达时,接收方在其缓存中保存第6657~8960字节的数据,并将这2304字节数据交给用户进程,所有这些数据在报文段72中确认,请注意此时通告窗口大小为5888(8192-2304),这是因为用户进程没有机会读取这些已准备好的2304字节数据。

拥塞避免算法

慢启动算法是在一个连接上发起数据流的方法,但有时会达到中间路由器的极限,此时将分组丢失,拥塞避免算法是一种处理分组丢失的方法。
该算法假定由于分组收到的损坏引起的丢失非常少,因此分组丢失就意味着在源主机和目的主机之间某个地方发生了拥塞,有两种分组丢失的指示:发生超时和接收到重复的确认。拥塞避免算法和慢启动算法是两个目的不同,独立的算法。但是当拥塞发生时,我们希望降低分组进入网络的速率,于是可以调用慢启动算法实现这一点。在实际中这两个算法通常一起实现。
拥塞算法和慢启动算法需要对每个连接维持两个变量,一个拥塞窗口cwnd,和一个慢启动门限ssthresh,这样得到的算法工作过程如下:
1,对一个给定的连接,初始化cwnd为1,ssthresh为65535字节。
2,TCP输出例程的输出不能超过cwnd和接收方通告窗口的大小。拥塞避免是发送方使用的流量控制,而通告窗口则是接收方的流量控制。前者是发送方感受到的网络拥塞的估计,而后者则与接收方在该连接上的可用缓存大小有关。
3,当拥塞发生时(超时或收到重复确认),ssthresh被设置为当前窗口的一半(cwnd和接收方通告窗口的最小值,但最少为2个报文段),此外如果是超时引起的拥塞,则cwnd被设置为1(慢启动)。
4,当新的数据被确认时,就增加cwnd,但增加的方法依赖于我们是否正在进行慢启动或拥塞避免。如果cwnd小于或等于ssthresh,则正在进行慢启动,否则正在进行拥塞避免。慢启动一直持续到我们回到当拥塞发生时所处位置的半时候才停止,然后转为执行拥塞避免。
慢启动算法初始设置cwnd为1,此后每收到一个确认就加1,呈指数增长。拥塞避免算法要求每次收到一个确认将cwnd增加1/cwnd,与慢启动的指数相比,这是一种加性增长。我们希望在一个往返时间内最多为cwnd增加1个报文段(不管这个RTT收到多少ACK),然而慢启动将根据这个往返时间收到的ACK个数增加cwnd。
下图是一个慢启动和拥塞避免的可视化窗口,以段为单位显示cwnd和ssthresh,但它们实际上都是以字节为单位维护。
在这里插入图片描述
在该图中,假定当cwnd为32个报文段时会发生拥塞,于是设置ssthresh为16报文段,而cwnd为1个报文段。在时刻0发送一个报文段,并假定在时刻1收到它的ACK,此时cwnd增加为2,接着发送2个报文段,并假定时刻2收到它们的ACK,于是cwnd增加为4(每个ACK增加1)。这种指数算法在进行到时刻3和4之间收到8个ACK后cwnd等于ssthresh才停止。从该时刻开始,cwnd以线性方式增长,在每个往返时间内增加1个报文段。
正如我们在图中看到的,术语慢启动并不准确,它只是采用比引起拥塞更慢些的分组传输速率,但在慢启动期间进入网络的分组数增加的速率仍然是增加的,只有在达到ssthresh拥塞避免算法起作用时,这种增加的速率才慢下来。

快速重传及快速恢复算法

拥塞避免算法的修改1990年提出,在介绍修改之前,我们认识到收到一个失序报文段时,TCP立即需要产生一个ACK(重复的ACK),这个重复的ACK不应该被延迟,该重复的ACK的目的在于让对方知道收到一个失序的报文段,并告诉对方希望收到的序号。由于我们不知道一个重复的ACK是由一个丢失的报文段引起的,还是由于仅仅出现了几个报文段的重新排序,因此我们等待少量重复ACK的到来。假如这只是一些报文段的重新排序,则在重新排序的报文段被处理并产生一个新的ACK之前,只可能产生1~2个重复的ACK。如果一连串收到3个或3个以上的重复ACK,就非常可能是一个报文段丢失了,于是我们就重传丢失的报文段,而无需等待超时定时器溢出,这就是快速重传法。接下来执行的不是慢启动算法而是拥塞避免算法,这就是快速恢复算法。
在拥塞举例一节的图中可以看到收到3个重复的ACK之后没有执行慢启动,相反,发送方进行重传,接着在收到重传的ACK之前,发送了三个新的报文段。
在这种情况下没有执行慢启动的原因是由于收到重复的ACK不仅仅告诉我们一个分组丢失了。由于接收方只有在收到另一个报文段时才会产生重复的ACK,而该报文段已经离开了网络进入接收方的缓存,也就是说,在收发两端之间仍然有流动的数据,而我们不想执行慢启动来突然减少数据流。
这个算法通常按如下算法实现:
1,当收到3个重复的ACK时,将ssthresh设置为当前拥塞窗口的一半,重传丢失的报文段,设置cwnd为ssthresh加上3倍的报文段大小。
2,每次收到另一个重复的ACK时,cwnd增加一个报文段大小并发送1个分组(如果新的cwnd允许发送)
3,当下一个确认新数据的ack到达时,设置cwnd为ssthresh,这个ACK应该是在进行重传后的一个往返时间内对步骤1中重传的确认。另外,这个ACK也应该是对丢失分组和收到的第一个重复的ACK之间所有报文段的确认。这一步采用的是拥塞避免,因为当分组丢失时我们将当前的速率减半。

拥塞举例(续)

通过使用tcpdump和插口排错选项来观察一个连接,就会在发送每一个报文段时观察到cwnd和ssthresh。如果MSS为256字节,则cwnd和ssthresh的初始值分别为256和65535。每当收到一个ACK时,我们看到cwnd增加一个MSS,取值分别为256,768,1024,1280等。假定不会发生拥塞,则最终拥塞窗口将超过接收方的通告窗口,意味着通告窗口将对数据流进行限制。
一个更有趣的例子是观察在拥塞发生时的情况。这个例子发生4次拥塞。为建立连接而发送的初始SYN有一个因超时引起的重传,接着在数据传输过程中有3个分组丢失。下图显示了当初始SYN重传并接着发送前7个报文段时变量cwnd和ssthresh的值。使用tcpdump的记号来表示数据字节:1:257(256)表示1~256字节。当SYN超时时,ssthresh被设置为其最小值。为进入慢启动阶段,cwnd被设置为1个报文段。当收到SYN和ACK时,没有对这两个变量做任何修改,因为新的数据还没有确认。当ACK257到达时,因为cwnd小于等于ssthresh,因此仍然处于慢启动,于是将cwnd增加256字节。当收到ACK513时,进行同样的处理。当ACK769到达时,我们不再处于慢启动,而是进入拥塞避免状态,新的cwnd计算方法为:cwnd=cwnd+segsizesegsize/cwnd+segsize/8。考虑到cwnd实际上以字节而非报文段来维护,因此这就是我们前面提到的增加1/cwnd,这个例子中我们计算cwnd=768+256256/768+256/8=885字节。当下一个ACK1025到达时,计算结果为991字节。
在这里插入图片描述

TCP的坚持定时器

我们已经看到TCP通过让接收方指明希望从发送方接收的数据字节数来进行流量控制。如果窗口大小为0将有效阻止发送方发送数据,直到窗口变为非0。在之前的例子中有,发送方接收到打开窗口ACK立即打开窗口并发送数据。但是TCP的ACK是不可靠的,可能会丢失。如果一个确认丢失了,则双方可能因为等待对方而连接终止。接收方等待接收数据,因为它已经发送了窗口不为0的确认,而发送方在等待允许发送数据的窗口更新。为防止这种死锁发生,发送方使用一个坚持定时器来周期性的向接收方查询,以便发现窗口是否已增大。这些从发送方发出的报文段称窗口探查。

一个例子

为了观察到实际中的坚持定时器,我们启动一个接受进程。它监听来自客户的连接请求,接受该连接请求。然后从网上读取数据前休眠很长一段时间。设置使服务器在接受连接和进行第一次读动作之间进行休眠。客户运行在主机bsdi上,并向服务器的5555端口执行1024字节的写操作。其tcpdump如下图。
报文段1~13显示的从客户到服务器的正常的数据传输过程,有9216字节的数据填充了窗口。服务器通告窗口大小为4096字节,且默认的插口缓存为4096。但实际上它接收了9216字节数据。在报文段13中,服务器确认了前面4个数据报文段,然后通告窗口为0,从而使客户停止发送数据。这就引起客户设置坚持定时器。如果在该定时器到时期间客户还没有接收到一个窗口更新,它就探查这个空的窗口以决定窗口更新是否丢失。由于服务器进程处于休眠状态,所以TCP缓存9216字节的数据并等待应用进程读取。请注意客户发出的窗口探查之间的时间间隔,在收到一个大小为0的窗口通告后的第一个间隔为4.949秒,下一个间隔是4.996秒,然后的间隔分别是6,12,24,48,60。
在这里插入图片描述
为什么这些间隔总是比5,6,12,24,48,60小一个零点几秒呢,因为这些探查被TCP的500ms定时器超时例程所触发。当定时器时间到时,就发送探查窗口,并大约在4ms后收到一个应答。接收到应答使得定时器被重新启动,但到下一个时钟滴答之间的时间间隔为500-4ms。
计算坚持定时器时使用了普通的TCP指数退避。对于一个典型的局域网连接,首次超时时间算出来是1.5秒,第二次超时增加一倍,为3秒,再下次乘以4为6秒,之后乘以8为12秒,但是坚持定时器总是在5~60秒之间。
窗口探查包含一个字节的数据(9127),TCP总是允许在关闭连接前发送一个字节的数据,请注意,尽管如此,所返回的窗口为0的ACK并不是确认该字节(确认9126在内的所有数据),因此这个字节被持续重传。坚持状态的TCP从不放弃窗口探查,每隔60s发送一次,持续到窗口被打开或应用进程使用的连接被终止。

糊涂窗口综合征

基于窗口的流量控制方案,如TCP所使用的,会导致一种“糊涂窗口综合征SWS”,如果这种状况发生,则少量的数据将通过连接进行交换,而不是满长度的报文段。
该现象可发生在两端中的任何一端,接收方可以通告一个小的窗口(而不是等到有大的窗口进行通告),发送方可以发送少量数据(而不是等待其他数据以便发送一个大的报文段),可以在任何一端采取错误避免糊涂窗口综合征。
1,接收方不通告小窗口,通常的算法是接收方不通告一个比当前窗口大的窗口,除非窗口可以增加一个报文段大小或者可以增加接收方缓存空间的一半,不论实际多少。
2,发送方避免出现糊涂窗口综合征的措施是以下条件之一满足时才发送数据:a,可以发送一个满长度的报文段,b,可以发送至少是接收方通告窗口大小一半的报文段,c,可以发送任何数据并且不希望接收ack(也就是说我们还有未被确认的数据)或者该连接上不能使用Nagle算法。
条件b是对付那些总是通告小窗口的主机,条件c使我们在有尚未被确认的数据以及在不能使用nagle算法下,避免发送小报文段。如果应用程序在进行小数据的写操作,条件c可以避免出现糊涂窗口综合征。
这三个条件可以让我们回答这个问题,在有尚未被确认的数据情况下,如果nagle算法阻止我们发送小的报文段,那么多小才算小呢,条件a可以看出小是字节数小于报文段长度,条件b用来对付老的原始的主机。条件b要求发送方始终监视另一方通告的窗口大小,这是一种发送方猜测对方接受缓存大小的企图。

TCP的保活定时器

没有任何数据流通过空闲的TCP连接,也就是说,如果TCP连接的双方都没有向对方发送数据,则这两个TCP模块不交换任何信息。例如,没有可以在其他网络协议中发现的轮询。这意味着我们可以启动一个客户和服务器建立连接,然后离去数小时直至数月,连接依然保持。中间路由器可以崩溃重启,电话线可以挂断重接,但只要两端的主机没有被重启,则连接依然保持。
这意味着两个应用进程,客户进程和服务器进程,都没有使用应用级的定时器来检测非活动状态。而这种非活动状态可以导致应用进程的任何一个终止其活动。然而,许多时候一个服务器希望知道客户主机是否已崩溃关机或崩溃重启,许多实现的保活定时器提供这种功能。
在连接两个端系统的网络中出现临时故障时,保活选项会引起一个实际上很好的连接终止。例如一个中间路由器崩溃并重启时发送保活探测,那么TCP会认为客户主机已崩溃,而实际上并非如此。
保活功能主要由服务器应用程序提供。服务器应用程序希望知道客户主机是否已崩溃,从而可以代表客户使用资源。一个说明现在需要使用保活功能的常见例子是当个人计算机用户使用的TCP/IP向一个使用telnet的主机注册时。如果一天结束时,他们仅仅关闭电源没有注销,那么便会留下一个半开放的连接,一个半开放的连接发送数据会导致一个复位,但那时来自正在发送数据的客户端。如果客户已经消失,使得在服务器留下一个半开放连接,而服务器又在等待来自客户的数据,则服务器将永远的等待下去,保活功能就是在服务器端检测这种半打开连接。
在这个描述中,使用保活选项的一端称为服务器,另一端为客户,客户也可以使用这个选项,但通常都是服务器使用这个选项,如果双方都特别需要了解对方是否消失,双方都可以打开这个选项。如果一个给定的连接在两个小时内没有任何动作,则服务器就向客户端发送一个探查报文段。客户主机必须处于以下四种状态之一。
1,客户主机依然正常运行,并从服务器可达。客户的TCP响应正常,而服务器也知道对方是正常工作的。服务器在两个小时后将保活定时器复位,如果在两个小时定时器到时之前有应用程序的通信量通过连接,则定时器在交换数据后两个小时复位。
2,客户主机已崩溃,并且关闭或正在重新启动。在任何一种情况下,客户的TCP都没有响应。服务器将不能收到对方的探查响应,并在75秒后超时。服务器总共发送10个这样的探查,每个间隔75秒,如果服务器没有收到一个响应,它就认为客户已经关闭并终止连接。
3,客户主机崩溃并已经重新启动。这是服务器将收到一个保活探查的响应,不过是一个复位,使得服务器终止这个连接。
4,客户主机正常,但是服务器不可达,这与状态2相同。因为TCP不能够区分状态2和4的区别,它所能发现的就是没有收到探查的响应。
服务器不用关注客户主机被关闭和重新启动的情况(指操作员关闭或崩溃)。当系统被操作员关闭时,所有应用程序也被终止,这会使客户的TCP在连接上发送一个FIN,接收到FIN将使服务器的TCP向服务器进程报告文件结束,使服务器可以检测到这个情况。
在第一种情况下,服务器的应用进程没有感受到保活探查的发生,TCP层负责一切,这个过程对应用程序都是透明的。直到2,3,4情况的发生,在这三种情况下,服务器应用进程将收到来自它的TCP的差错报告,第2种情况,差错是诸如连接超时之类的信息,而第三种情况是连接被对方复位,第四种情况看起来像连接超时,也可根据是否收到与连接有关的ICMP差错来返回其他差错。

保活举例

现在讨论上述2,3,4情况。

另一端崩溃

首先观察另一端崩溃且没有重新启动的情况所发生的现象。我们采用以下步骤模拟。
1,客户(主机bsdi的程序)和主机svr4上的标准回显服务器间建立一个连接。客户使用保活功能。
2,验证数据可以通过该连接。
3,观察客户TCP每隔两小时发送保活分组,并观察被服务器TCP确认。
4,将以太网电缆从服务器上拔掉直到最后,使客户认为服务器已崩溃。
5,我们预期服务器在断定连接已中断前发送10个间隔为75秒的保活探查。
tcpdump输出如下。
在这里插入图片描述
客户在1,2,3行向服务器发送helloworld并受到回显,第4行是一个保活探查,发生在两个小时以后,在第六行的TCP报文段能够发送以前,首先观察到的是一个ARP请求和应答。第六行的保活探查引出来自另一端的响应,两个小时后,在第七行和第八行发生了同样的分组交换。
如果能够观察到第6行和第10行的保活探查中的所有字段,我们就会发现序号字段比下一个将要发送的序号字段小1,因为报文段中没有数据,tcpdump不能打印出序号。正是接收到这个不正确的序号,才导致服务器的TCP对保活探查进行响应,这个响应告诉客户,服务器下一个期望的序号是14。接着我们拔掉电缆,并期望两个小时后的再次探查失败,当这下一个探查发生时,注意到从来没有看到电缆上出现TCP报文段,这是因为主机没有响应ARP请求,放弃之前,我们仍然观察到客户每隔75秒发送一次探查,总共10次。

另一端崩溃并重启

在这个例子中,我们可以观察到当客户崩溃并重新启动时发生的情况,最初的环境与前一个类似,但我们在验证链接有效之后,将服务器从以太网断开,重新启动,再链接到网络上,我们希望看到下一个保活探查产生一个服务器的复位。因为现在服务器不知道关于这个连接的任何信息。tcpdump结果如下。
在这里插入图片描述
我们建立连接,从客户发送9个字节的数据到服务器,两个小时后,客户发送第一个保活探查,其响应是一个来自服务器的复位,客户应用进程打印出连接被对端复位的差错,这是有意义的。

另一端不可达

在这个例子中,客户端没有崩溃,但在保活探查发送后10分钟内无法到达,可能是一个中间路由器崩溃,或一条电话线临时故障。为了仿真这个例子,我们从主机slip经过一个拨号slip链路与主机vangogh建立连接,然后断掉链路。路由器bsdi上收集的tcpdump如下。
在这里插入图片描述
1~3行连接是正常的,两个小时后第一个保活探查正常,但在两个小时后发生下一个探查前,断了路由器sun和netb之间的slip链路,第6行的保活探查引发一个来自路由器sum的ICMP网络不可达差错,对于主机slip接收的TCP而言,这是一个软差错。它报告收到了一个ICMP差错,但差错的接收者并没有终止这个连接,在发送主机最后放弃前,一共发送了9个保活探查,间隔75秒。这是返回给应用程序的差错产生了一个不同的报文,没有到达主机的路由。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: TCP/IP协议详解卷1(英文版)是一本经典的网络技术书籍,对于理解和掌握TCP/IP协议是非常有帮助的。 本书首先介绍了网络通信的基本原理和TCP/IP协议的体系结构,然后详细讲解了每一层协议的功能、特点和实现方法。其中,TCP协议和IP协议是重点的讲解对象,分别从数据结构、传输机制、拥塞控制、错误处理等多个方面逐一剖析,涵盖了从实现到应用的全过程。 另外,本书还介绍了TCP/IP协议的各种应用协议,如HTTP、FTP、SMTP等,以及网络安全、路由选择、网络管理等相关技术。通过阅读本书,不仅可以理解TCP/IP协议的原理和流程,还可以掌握实际应用中的场景、需求和解决方案。 总的来说,TCP/IP协议详解卷1(英文版)是一本系统而详尽的网络技术著作,对于网络技术爱好者、工程师以及网络管理员都是一本不可多得的参考书籍。无论是学习TCP/IP协议的初学者还是深入研究的专业人员,都可以从中获得丰富的知识和经验。 ### 回答2: TCP/IP协议详解卷1英文版,是一本介绍互联网传输控制协议和网络互联协议的著作。本书从TCP/IP的电脑网络体系结构出发,介绍了TCP、UDP、IP、ICMP、ARP等网络协议的原理、功能和应用,进一步讲解了TCPIP协议在数据传输、数据包格式、错误处理和路由选择等方面的应用。 本书虽然详细介绍了TCP/IP协议的技术细节,但是作者用通俗易懂的语言阐述了其内在原理和运作机制,使得普通读者也能够理解。同时,作者介绍了TCP/IP协议在各种应用场景下的具体实现和应用方法,从而为网络管理员和网络开发者提供了宝贵的参考。 总之,TCP/IP协议详解卷1英文版是一本介绍TCP/IP协议基础知识的重要参考书,对于想要深入了解互联网传输控制协议和网络互联协议的人群具有重要的参考价值。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值