传输控制协议(Transmission Control Protocol, TCP)之一

传输控制协议(Transmission Control Protocol, TCP)

TCP协议主为了在主机间实现高可靠性的包交换传输协议。本文将描述协议标准和实现的一些方法。因为计算机网络在现代社会中已经是不可缺少的了,TCP协议主要在网络不可靠的时候完成通信,对军方可能特别有用,但是对于政府和商用部门也适用。TCP是面向连接的端到端的可靠协议。它支持多种网络应用程序。TCP对下层服务没有多少要求,它假定下层只能提供不可靠的数据报服务,它可以在多种硬件构成的网络上运行。下面的图是TCP在层次式结构中的位置,它的下层是IP协议,TCP可以根据IP协议提供的服务传送大小不定的数据,IP协议负责对数据进行分段,重组,在多种网络中传送。

TCP的上面就是应用程序,下面是IP协议,上层接口包括一系列类似于操作系统中断的调用。对于上层应用程序来说,TCP应该能够异步传送数据。下层接口我们假定为IP协议接口。为了在并不可靠的网络上实现面向连接的可靠的传送数据,TCP必须解决可靠性,流量控制的问题,必须能够为上层应用程序提供多个接口,同时为多个应用程序提供数据,同时TCP必须解决连接问题,这样TCP才能称得上是面向连接的,最后,TCP也必须能够解决通信安全性的问题。

网络环境包括由网关(或其它设备)连接的网络,网络可以是局域网也可以是一些城域网或广域网,但无论它们是什么,它们必须是基于包交换的。主机上不同的协议有不同的端口号,一对进程通过这个端口号进行通信。这个通信不包括计算机内的I/O操作,只包括在网络上进行的操作。网络上的计算机被看作包传送的源和目的结点。特别应该注意的是:计算机中的不同进程可能同时进行通信,这时它们会用端口号进行区别,不会把发向A进程的数据由B进程接收的。

进程为了传送数据会调用TCP,将数据和相应的参数传送给TCP,于是TCP会将数据传送到目的TCP那里,当然这是通过将TCP包打包在IP包内在网络上传送达到的。接收方TCP在接收到数据后会通信上层应用程序,TCP会保证接收数据顺序的正确性。虽然下层协议可能不会保证顺序是正确的。这里需要说明的是网关在接收到这个包后,会将包解开,看看是不是已经到目的地了,如果没有到,应该走什么路由达到目的地,在决定后,网关会根据下一个网络内的协议情况再次将TCP包打包传送,如果需要,还要把这个包再次分成几段再传送。这个落地检查的过程是一个耗时的过程。从上面,我们可以看出TCP传送的基本过程,当然具体过程可能要复杂得多。

在实现TCP的主机上,TCP可以被看成是一个模块,和文件系统区别不大,TCP也可以调用一些操作系统的功能,TCP不直接和网络打交道,控制网络的任务由专门的设备驱动模块完成。TCP只是调用IP接口,IP向TCP提供所有TCP需要的服务。通过下图我们可以更清楚地看到TCP协议的结构。

上面已经说过了,TCP连接是可靠的,而且保证了传送数据包的顺序,保证顺序是用一个序号来保证的。响应包内也包括一个序列号,表示接收方准备好这个序号的包。在TCP传送一个数据包时,它同时把这个数据包放入重发队列中,同时启动记数器,如果收到了关于这个包的确认信息,将此包从队列中删除,如果计时超时则需要重新发送此包。请注意,从TCP返回的确认信息并不保证最终接收者接收到数据,这个责任由接收方负责。

每个用于传送TCP的通道都有一个端口标记,因为这个标记是由每个TCP终端确定的,因此TCP可能不唯一,为了保证这个数值的唯一,要使用网络地址和端口号的组合达到唯一标识的目的,我们称这个为了套接字(Socket),一个连接由连接两端的套接字标识,本地的套接字可能和不同的外部套接字通信,这种通信是全双工的。

通过向本地端口发送OPEN命令及外部套接字参数建立连接,TCP返回一个标记这个连接的名称,以后如果用户需要使用这个名称标记这个连接。为了保存这个连接的信息,我们假设有一个称为传输控制块(Transmission Control Block,TCB)的东西来保存。OPEN命令还指定这个连接的建立是主动请求还是被动等待请求。下面我们要涉及具体的功能了,TCP段以internet数据报的形式传送。IP包头传送不同的信息域,包括源地址和目的地址。TCP头跟在internet包头后面,提供了一些专用于TCP协议的信息。下图是TCP包头格式图:

源端口:16位;

目的端口:16位

序列码:32位,当SYN出现,序列码实际上是初始序列码(ISN),而第一个数据字节是ISN+1;

确认码:32位,如果设置了ACK控制位,这个值表示一个准备接收的包的序列码;

数据偏移量:4位,指示何处数据开始;

保留:6位,这些位必须是0;

控制位:6位;

窗口:16位;

校验位:16位;

优先指针:16位,指向后面是优先数据的字节;

选项:长度不定;但长度必须以字节记;选项的具体内容我们结合具体命令来看;

填充:不定长,填充的内容必须为0,它是为了保证包头的结合和数据的开始处偏移量能够被32整除;

 

我们前面已经说过有一个TCB的东西了,TCB里有存储了包括发送方,接收方的套接字,用户的发送和接收的缓冲区指针等变量。除了这些还有一些变量和发送接收序列号有关:

发送序列变量

SND.UNA - 发送未确认

SND.NXT - 发送下一个

SND.WND - 发送窗口

SND.UP - 发送优先指针

SND.WL1 - 用于最后窗口更新的段序列号

SND.WL2 - 用于最后窗口更新的段确认号

ISS - 初始发送序列号

 

接收序列号

RCV.NXT - 接收下一个

RCV.WND - 接收下一个

RCV.UP - 接收优先指针

IRS - 初始接收序列号

下图会帮助您了解发送序列变量间的关系:

当前段变量

SEG.SEQ - 段序列号

SEG.ACK - 段确认标记

SEG.LEN - 段长

SEG.WND - 段窗口

SEG.UP - 段紧急指针

SEG.PRC - 段优先级

连接进程是通过一系列状态表示的,这些状态有:LISTEN,SYN-SENT,SYN-RECEIVED,ESTABLISHED,FIN-WAIT-1,FIN-WAIT-2,CLOSE-WAIT,CLOSING,LAST-ACK,TIME-WAIT和 CLOSED。CLOSED表示没有连接,各个状态的意义如下:

LISTEN - 侦听来自远方TCP端口的连接请求;

SYN-SENT - 在发送连接请求后等待匹配的连接请求;

SYN-RECEIVED - 在收到和发送一个连接请求后等待对连接请求的确认;

ESTABLISHED - 代表一个打开的连接,数据可以传送给用户;

FIN-WAIT-1 - 等待远程TCP的连接中断请求,或先前的连接中断请求的确认;

FIN-WAIT-2 - 从远程TCP等待连接中断请求;

CLOSE-WAIT - 等待从本地用户发来的连接中断请求;

CLOSING - 等待远程TCP对连接中断的确认;

LAST-ACK - 等待原来发向远程TCP的连接中断请求的确认;

TIME-WAIT - 等待足够的时间以确保远程TCP接收到连接中断请求的确认;

CLOSED - 没有任何连接状态;

TCP连接过程是状态的转换,促使发生状态转换的是用户调用:OPEN,SEND,RECEIVE,CLOSE,ABORT和STATUS;传送过来的数据段,特别那些包括以下标记的数据段SYN,ACK,RST和FIN;还有超时,上面所说的都会时TCP状态发生变化。

 

下面的图表示了TCP状态的转换,但这图中没有包括错误的情况和错误处理,不要把这幅图看成是总说明了。

 

3.3. 序列号

请注意,我们在TCP连接中发送的字节都有一个序列号。因为编了号,所以可以确认它们的收到。对序列号的确认是累积性的,也就是说,如果用户收到对X的确认信息,这表示在X以前的数据(不包括X)都收到了。在每个段中字节是这样安排的:第一个字节在包头后面,按这个顺序排列。我们需要认记实际的序列空间是有限的,虽然很大,但是还是有限的,它的范围是0到2的32次方减1。我想熟悉编程的一定知道为什么要在计算两个段是不是相继的时候要使用2的32次方为模了。TCP必须进行的序列号比较操作种类包括以下几种:

(a) 决定一些发送了的但未确认的序列号;

(b) 决定所有的序列号都已经收到了;

(c) 决定下一个段中应该包括的序列号。

对于发送的数据TCP要接收确认,处理确认时必须进行下面的比较操作:

SND.UNA = 最老的确认了的序列号;

SND.NXT = 下一个要发送的序列号;

SEG.ACK = 接收TCP的确认,接收TCP期待的下一个序列号;

SEG.SEQ = 一个数据段的第一个序列号;

SEG.LEN = 数据段中包括的字节数;

SEG.SEQ+SEG.LEN-1 = 数据段的最后一个序列号。

请注意下面的关系:

SND.UNA < SEG.ACK =< SND.NXT

如果一个数据段的序列号小于等于确认号的值,那么整个数据段就被确认了。而在接收数据时下面的比较操作是必须的:

RCV.NXT = 期待的序列号和接收窗口的最低沿;

RCV.NXT+RCV.WND-1 = 最后一个序列号和接收窗口的最高沿;

SEG.SEQ = 接收到的第一个序列号;

SEG.SEQ+SEG.LEN-1 = 接收到的最后一个序列号;

 

上面几个量有如下关系:

RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND 或 RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND

测试的第一部分是检查数据段的开始部分是否在接收窗口中,第二部分是检查数据段的结束部分是否也在接收窗口内;上面两个检查通过任何一个就说明它包括窗口要求的数据。实际中的情况会更复杂一些,因为有零窗口和零数据段长,因此我们有下面四种情况:

 

 

段长度

接收窗口

测试

0

0

SEG.SEQ = RCV.NXT

0

>0

RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND

>0

0

不可接受

>0

>0

RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND或RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND

请注意接收窗口的大小可以为零,在窗口为零时它只用来接收ACK信息,因此对于一个TCP来说,它可以使用零大小窗口在发送数据的同时接收数据。即使接收窗口的大小为零,TCP必须处理所有接收到信息的RST和URG域。

我们也应用计数的方式保护了一些特定的控制信息,这是通过隐式地使用一些控制标记使数据段能够可靠地重新发送(或确认)为达到的。控制信息并不在段数据空间中传送,因此,我们必须采用隐式指定序列号进行控制。SYN和FIN是需要保护的控制量,这两个控制量也只在连接打开和关闭时使用。SYN被认为是在第一个实际数据之间的数据,而FIN是最后一个实际数据之后的数据。段长度(SEG.LEN)包括数据和序列号空间,如果出现了SYN,那么SEG.SEQ是SYN的序列号。

初始序列号选择

协议对于特定连接被重复使用没有什么限制。连接是由一对套接字定义的。新的连接实例被定义为连接的另一次恢复,这就带来了问题:TCP如果确定多个数据段是从以前连接的另一次恢复中取得的呢?这个问题在连接迅速打开和关闭,或因为内存原因被关闭然后又迅速建立后显示特别突出。

为了避免混乱,用户必须避免因此恢复使用某一连接,而使序列号发生混乱。我们必须保证序列号的正确性,即使TCP失败,根本不知道以前的序列号是什么的情况下也要保证序列号的正确性。当新的连接被创建时,产生一个新的初始序列号(ISN)产生子,它用来选择一个新的32位ISN。产生子和32位时钟的低度位字节相关,低位字节的刷新频率大概是4微秒,因此ISN的循环时间大概是4.55小时。因此我们把网络包的最长生存时间(MSL)小于4.55小时,因此我们可以认为ISN是唯一的。对于每个连接都有发送序列号和接收序列号,初始发送序列号(ISS)由发送TCP选择,而初始接收序列号是在连接建立过程中产生的。

对于将要连接或初始化的连接,两个TCP必须和对方的初始序列号同步。这通过交换一个控制位SYN和初始序列号完成。我们把带有SYN的数据段称为"SYNs"。同步的获得过程这里就不重复了,每方必须发送自己的序列号并返回对对方序列号的确认。

1) A --> B SYN 本方序列号是X

2) A <-- B ACK 本方序列号被确认

3) A <-- B SYN 对方序列号是Y

4) A --> B ACK 确认对方序列号

上面的第2步和第3步可以合并,这时可以成为3阶段,所以我们可以称它为三消息握手。这个过程是必须的,因为序列号不和全局时钟关联,TCP也可以有不同的机制选择ISN。接收到第一个SYN的接收方不可能知道这个数据段是不是被延时,除非它记住了在连接上使用的最近的序列号(这通常是不可能的),因此它必须要求发送者确认。

为了保证TCP获得的确认是刚才发送的段产生的,而不是仍然在网络中的老数据段产生的,因此TCP必须在MSL时间之内保持沉默。在本文中,我们假设MSL=2小时,这是出于工程的需要,如果用户觉得可以,他可以改变MSL。请注意如果TCP重新初始化,而内存中的序列号正在使用,不需要等待,但必须确认使用的序列号比当前使用的要大。

如果一台主机在未保留任何序列号的情况下失败,那么它应该在MSL时间之内不发出任何数据段。下面将会这一情况进行说明。TCP的实现可以不遵守这个规定,但是这会造成老数据被当成新数据接收,而新数据被当成老数据拒绝的情况。

每当数据段形成并进入输出队列,TCP会为它指定序列空间中的一个值。TCP中多复本检测和序列算法都依赖于这个地址空间,在对方发送或接收之前不会超过2的32次方个包存在于输出队列中。所有多余的数据段都会被删除。如果没有这个规定,会出现多个数据段被指定同一个序列号的情况,会造成混乱。数据段中序列号的多少和数据段中的字节数一样多。

在通常情况下,TCP保留下一个要发送的序列号和还未确认的最老的序列号,不要在没有确认的时候就再次使用,这样会有些风险,也正是因为这样的目的,所以序列空间很大。对于2M的网络,要4.5小时来耗尽序列空间,因为一个数据段可能的最大生存时间也不过十几分之一秒,这就留下了足够的空间;而在100M的网络上需要5.4分钟,虽然少了点,但也可以了。

如果在实现TCP时没有为保存序列号留下空间,那清除多余的包可能就不能实现了,因此推荐这种类型的TCP实现最好在失败后等待MSL时间,这样保证多余的包被删除。这种情况有时候也可能会出现在保留序列号的TCP实现中。如果TCP在选择一个另一个TCP连接正在使用的序列号时,这台主机突然失败了,这就产生了问题。这个问题的实质在于主机不知道它失败了多久,也不知道多余的复本是不是还在网络中。

处理这种问题的方法是等待MSL时间,如果不这样就要冒着对方错误接收数据的危险,要等待的时间也就称为“沉默时间”。实现者可以让用户选择是不是等待,但是无论用户如何也不见得非要等待MSL时间。

3.4. 建立一个连接

建立连接应用的是三消息握手。如果双方同时都发送SYN也没有关系,双方会发现这个SYN中没有确认,于是就知道了这种情况,通常来说,应该发送一个"reset"段来解决这种情况。三消息握手减少了连接失败的可能性。下面就是一个例子,在尖括号是的就是数据段中的内容和标记。其它的就不多说了。

在第2行,TCP A发送SYN初始化序列号,表示它要使用序列号100;第3行中,TCP B给出确认,并且期待着A的带有序列号101的数据段;第4行,TCP A给出确认,而在第5行,它也给出确认,并发送了一些数据,注意第4行的序列号与第5号的一样,因为ACK信息不占用序列号空间内的序列号。同时产生请求的情况如下图所示,只复杂一点。

使用三消息握手的主要原因是为了防止使用过期的数据段。为了这个目的,必须引入新的控制消息,RESET。如果接收TCP处理非同步状态,在接收到RESET后返回到LISTEN状态。如果TCP处理下面几种状态ESTABLISHED,FIN-WAIT-1,FIN-WAIT-2,CLOSE-WAIT,CLOSING,LAST-ACK,TIME-WAIT时,放弃连接并通过用户。我们下面就详细说明后一种情况。

通过上面的例子,我们可以看出TCP连接是如何从过期数据段的干扰下恢复的。请注意第4行和第5行中的RST(RESET信号)。

半开连接和其它非正常状态

如果一方在未通过另一方的情况下关闭连接,或双方虽然失败而不同步的情况我们称为半开连接状态。在一方试图发送数据时连接会自动RESET。然而这种情况毕竟属于不正常情况。应该做出相应的处理。如果A处的连接已经关闭,B处并不知道。当B希望发送数据到A时,就会收到RESET信号,表示这个TCP连接有误,要中止当前连接。

假设A和B两个进程相互通信的时候A的TCP发生了失败,A依靠操作系统支持TCP的存在,通常这种情况下会有恢复机制起作用,当TCP重新恢复的时候,A可能希望从恢复点开始工作。这样A可能会试图OPEN连接,然后在这个它认为还是打开的连接上传送数据,这时A会从本地(也就是A的)TCP上获得错误消息“未打开连接”。A的TCP将发送包括SYN的数据段。下面的例子将显示这一过程:

上面这个例子中,A方收到的信息并没有确认任何东西,这时候A发现出了问题,于是发送了RST控制信息。另一种情况是发生在A失败,而B方仍然试图发送数据时,下面的例子可以表示这种情况,请注意第2行中A对B发送来的信息不知所云。

在下面的例子中,A方和B方进行的被动连接,它们都在等待SYN信息。过期的包传送到B方使B回应了,而收到回应的A却发现不对头,传送RST控制信息,B方返回被动LISTEN状态。

现实中的情况太多了,我们列举一些产生RST控制信息的规则如下:通常情况下,RST在收到的信息不是期待的信息时产生。如果在不能确定时不要轻易发送RST控制信息。下面有三类情况:

  1. 如果连接已经不存在,而发送来的消息又不是RST,那么要返回RST。如果想拒绝对不存在的连接进行SYN,可以使用这种办法。如果到达的信息有一个ACK域,返回的RST信息可以从ACK域中取得序列号,如果没有这个域,就把RST的序列号设置为0,ACK域被设备为序列号和到达段长度之和。连接仍然处于CLOSE状态。

  2. 如果连接处于非同步状态(LISTEN,SYN-SENT,SYN-RECEIVED),而且收到的确认是对未发出包的确认或是接收到数据段的安全级别与不能连接要求的相一一致时,就发送RST。如果SYN未被确认时,而且收到的数据段的优先级比要求的优先级要高,那么要么提高本地优先级(得事先征得用户和系统的许可)要么发送RST;如果接收数据段的优先级比要求的优先级低,就算是匹配了,当然如果对方发现优先级不对提高了优先级,在下一个包中提高了优先级,这就不算是匹配了。如果连接已经进入SYN,那么接收到数据段的优先级必须和本地优先级一样,否则发送RST。如果到达的信息有一个ACK域,返回的RST信息可以从ACK域中取得序列号,如果没有这个域,就把RST的序列号设置为0,ACK域被设备为序列号和到达段长度之和。连接仍然处于与原来相同的状态。

  3. 如果连接处于同步状态(ESTABLISHED,FIN-WAIT-1,FIN-WAIT-2,CLOSE-WAIT,CLOSING,LAST-ACK,TIME-WAIT),任何超出接收窗口的序列号的数据段都产生如下结果:发出一个空确认数据段,此段中包括当前发送序列号,另外还包括一个确认指出希望接收的下一个数据段的序列号,连接仍然保存在原来的状态。如果因为安全级,优先级之类的问题,那就发送RST信号然后进入CLOSED状态。

RST过程

除了SYN-SENT状态外的其它状态中,所有的RST数据段可以通过检查SEQ域查明。如果序列号在接收窗口中,RST是有效的。当连接处于SYN-SENT状态时,如果ACK域确认SYN,那么RST也是合法的。

RST的接收方首先确认它的合法性,然后进行状态转换。如果接收方处于LISTEN状态,它就忽略RST包。如果接收方处于SYN-RECEIVED状态,而且以前处于LISTEN状态,接收方返回LISTEN状态,否则接收方关闭连接进入CLOSED状态。当接收方处于其它状态时,直接关闭连接回到CLOSED状态。

 

3.5. 关闭连接

CLOSE是一个操作,它的意思就是“本方已经有数据发送”。由于是全双工的,所以会造成一些麻烦,因为接收方对于处理接收方的连接有点麻烦。我们以一种简单的方式对待CLOSE,发送CLOSE的一方在接收到对方的CLOSED之前,还要继续接收数据。因此程序可以在一个CLOSE之后初始化几个SEND,然后开始RECEIVE,直到接收到对方的CLOSED而RECEIVE失败为止。我们假设TCP可以通知用户连接关闭,即使仍在RECEIVE也可以,这样用户就可以正常关闭了。这样,TCP可以在连接关闭前可靠地发送数据。下面列举了三种基本情况:

情况1:本地用户关闭

这种情况下,可以建立一个FIN段放入发送队列。TCP不再接收用户的SEND指令,TCP进入FIN-WAIT-1状态。RECEIVE在这种状态下是允许的。所有数据段和FIN在未接收到确认以前会一直发送。当另一个TCP确认FIN,并发送自己的FIN后,本地的TCP可以确认这个FIN了。请注意,TCP也可以在确认FIN时不返回自己的FIN,直到用户关闭连接时再返回自己的FIN。

情况2:TCP从网络上接收到一个FIN

如果在没有请求的情况下收到FIN,TCP可以返回ACK并通知用户连接已经关闭。用户可以以CLOSE响应,TCP在发送完剩下的数据后发送自己的FIN,然后TCP等待对这个FIN的确认,在接收到后,它关闭连接。如果确认超时,可以关闭连接并通知用户完事。

情况3:双方同时关闭

双方同时关闭会导致交换FIN。双方会在收到对自己FIN的确认后关闭连接。

 

3.6. 优先和安全

TCP的操作必须在两个优先级相同的端口间进行。TCP使用的优先和安全参数在IP协议中定义。我们这里所说的安全/间隔就是指的IP中定义的优先,用户组和处理规定。如果不符合则发送RST。这些内容请大家看上一节中的叙述。TCP在操作过程中也会检查接收数据段的优先级,还可以在操作中提高优先级。虽然运行在无安全环境中,主机也必须能够处理安全参数。

3.7. 数据通信

建立了连接以后就是传送数据了,TCP通过重新传送保证每个数据段到达对方,因为有了重新传送,所以对方可能接收到两个相同的包,那就必须根据内部的序列号判断哪个数据段是可以接收的。发送方通过使用SND.NXT跟踪下一个要发送的数据段,而接收方则跟踪RCV.NXT来知道下一个要接收的数据段。发送方要还未确认的最老的序列号保存于SND.UNA。

当发送方形成数据段并发送它后SND.NXT增大;当接收方接收到数据段后RCV.NXT增大并发送确认;当发送方接收到确认后SND.UNA增大。它们三者在不同的时间增大,这是因为传送时延造成的。而增大多少则由数据段中数据的大小决定。注意:连接进入ESTABLISHED状态后,所有的段必须包括当前的确认信息。而CLOSE用户操作的性质类型于推操作,这和在接收到的数据段中的FIN标记一样。

重传超时

因为网络中有不类型的网络,而使用TCP的范围又很广,因此重传超时必须动态决定。下面给出一个例子,通过例子可以看出确定重传超时的过程。下面有两个变量说明时延的问题,一个是环路时间(RTT),它是由一个序列码得到的,这个序列码在发送时给出,在接收到确认时被覆盖;另一个平滑环路时间(SRTT):

SRTT = ( ALPHA * SRTT ) + ((1-ALPHA) * RTT)

通过上面的式子,可以得到重传超时(RTO):

RTO = min[UBOUND,max[LBOUND,(BETA*SRTT)]]

其中UBOUND是超时的上界(如1分钟),LBOUND是超时的下界(如1秒钟),ALPHA是平滑因子(如0.8到0.9),BETA是延时变量(如1.3到2.0)。

传送紧急消息

TCP的紧急机制是允许发送者使接收者接收一些紧急消息,并让接收方在接收到这一消息后立刻通知用户。这种机制是在数据流是加入一个点,指出这是紧急数据的结束点,当接收方要接收到这个点之前,它会通知用户进入紧急状态,在接收到这个点的数据后,它会通知用户进入通常状态。如果这个紧急点在用户进入紧急状态时更新,这个更新必须对用户透明。

应用一个紧急域的方法可以达到上述目的,而URG控制标记则指明紧急域是否被使用,而且在数据段中必须加入指示紧急点的序列号,如果没有这个标记则说明没有紧急点。如果需要发送紧急数据,发送方必须起码发送一个字节。

管理窗口

如果我们学习过网络基础,我们一定知道有一个窗口协议,TCP中每个数据段都包括下一个希望接收到的序列号。窗口比较大会提高传送速度,如果传送过来的数据超过的窗口大小,数据会被抛弃。这样会加重网络负担。如果TCP开始时的接收窗口比较大,而到最后这个窗口缩小了,这种作法可不是好办法。对于健壮的TCP来说,最好不要自己缩小窗口,但要做要准备对方的TCP缩小窗口。

即使发送窗口大小为零,发送TCP也必须做好准备接收数据并且可以发送至少一位新数据。即使接收窗口为零,发送方也会重发,重发间的间隔时间一般为两分钟。当接收TCP的接收窗口为零时,在它接收到数据时,它依然会返回确认,其中包括希望收到的序列码和当前窗口大小(为零)。

发送TCP将数据打成符合当前大小的包,但在重新传送队列中可能重新打包。这种重新打包并不是必须的,但是会很有好处。如果一个连接只有单向数据流,窗口信息可以在确认信息中得到,这些的序列号都是一个,因此没法区别出谁先谁后。但这并不是一个严重的问题。改进的方法是在数据段中加入最新的期待序列号,这样就可以区别出谁先谁后了。窗口管理对传送效率有很大影响,下面是一些建议:

当使用更少的大数据而要获得高效率时分配非常小的窗口使数据以许多小段传送。对于发送者来说要避免在发送小数据段时必须等到窗口大到一定程度再进行。确认不要被延时。接收窗口如果大小为零时,要传送的数据段可能被分成越来越小的段。如果发送TCP仅仅发送窗口允许大小的数据段,可能这些数据段会被换成比这个段大(或者小)的数据段发送。接收方时不时地进行窗口大小调整会使大数据段分为小段,而不成对。尽量把小的窗口合成大的窗口。

3.8. 接口

网络协议的分层结构决定了TCP有两个接口,向上的用户接口和下面的接口。对下层接收口的描述不是很清楚,这个工作由下层协议来描述,但是这里我们也描述一些TCP要使用要的参数。

用户/TCP接口

下面描述一些TCP命令,这些命令是功能性的,因各系统不同具体实现可能不同。最后,我们必须清楚,不同的TCP实现可能有不同的用户接口,但是有一些功能是最基本的,本节描述的功能便是这个基本功能集。

TCP用户命令

下面定义的命令与其它高级语言中定义的方法差不多,但有些陷井操作需要的,如SVC,UUO,EMT。下面只是一个功能介绍,具体实现可能大为不同,如有些系统会把这里的多条命令结合成一条命令使用。为了实现通信功能,TCP不能只会接收命令,而且能够返回消息给它服务的进程,这些消息包括:

(a) 关于连接的通常消息,如中断,远程关闭等等;

(b) 对用户命令做出应答,是成功还是失败。

Open

格式:OPEN (local port, foreign socket, active/passive [, timeout] [, precedence] [, security/compartment] [, options]) -> local connection name

我们假定本地TCP注意到它所服务的进程标记,而且将检查进程的认证。因实现不同,本地网络和源地址的TCP认证可能由TCP进行也可以由下层协议进行(如IP协议)。这些考虑主要是基于安全性的考虑。

如果active/passive标记设置为passive,TCP会检测到达的连接请求,它是被动的,被动的连接可以有一个完全指定的外套接字用于等待特定的连接或未指定的外套接字来等待任何呼叫。一个完全定义的被动呼叫可以通过执行一系列的SEND命令而变为主动的。创建一个传输控制块(TCB),其中一部分参数是由OPEN命令参数而来。在主动OPEN时,TCP会立刻开始同步连接。如果给出参数timeout,会允许呼叫者应用于所有TCP数据。如果在timeout规定的时间内还未把数据送到目的地,就关闭连接,一般的默认值是5分钟。

参数precedence或security/compartment是用于规范用户对某一连接的安全性的,如果没有指定则使用默认值。TCP会匹配这两个参数,只有在security/compartment一致而且接收到的precedence小于等于要求的precedence时才会打开连接。当连接的precedence大于要求的值时,从接收到的数据段中取得这一值,并在连接中一直使用这个比较大的值。具体实现时可以给用户权力控制precedence的决定过程。例如,用户可以要求precedence必须安全一致,或在提升precedence时要通知用户。

本地连接名(local connection name)由TCP返回用户,它可以代替<本地套接字,外套接字>标记一个连接。

Send

格式:SEND (local connection name, buffer address, byte count, PUSH flag, URGENT flag [,timeout])

这个命令使指定缓冲区内的数据发向指定连接,如果连接未打开则返回一个错误,一些实现中可能在打开连接以前调用SEND,由SEND自动打开连接。如果设置了PUSH标记,数据必须立刻传送给接收者,而且PUSH位在缓冲区中的最后一个段是必须设置。如果没有设置,此数据段就会因为效率的考虑而和下一个SEND命令传送的数据一起传送。如果设置了URGENT标记,那就必须象上面所述的一样紧急处理。传送方设置的URGENT数目不一定非要等于通知接收方用户的次数。

如果在OPEN中没有指定外套接字,那缓冲区数据会被发送到隐式外套接字。未使用外套接字打开连接的用户仍然可以SEND,而不用知道外套接字地址。但是,如果在指定外套接字前进行SEND,则会出错。用户可以使用STATUES确定连接状态。如果指定了timeout,对于当前连接的当前用户timeout会更改为新的连接。

最简单的实现方法就是在没有把数据发送完毕以前,SEND不把控制权交给发送进程,但是这样可能会造成死锁(例如,双方都试图SEND,而不准备RECEIVE),效率也不好。好的实现方法是在发送一段数据后返回控制权给发送进程,当然如果能够多个SEND同时发送,那更好不过了。有多个SEND需要服务时一般采取先来先服务。

对于本地来说,我们现在假定的是SEND在发送数据后会产生类似中断的东西告诉发送进程数据发送的情况;当然SEND也可以在发送完后立刻告知进行数据发送的情况。我们可以乐观地认为发送是成功的,如果发送失败,连接会因为超时而关闭。在实现中,即使是同步返回情况,也需要一些异步信号,但这些异步信号是用于处理连接的,而不是用于处理数据发送的。既然允许多个SEND同时工作,因此有必要区别返回的信息是哪一个SEND的。具体的情况下面会讨论到。

Receive

格式: RECEIVE (local connection name, buffer address, byte count) -> byte count, urgent flag, push flag

此命令分配一个接收缓冲区给指定的连接。如果下面不是一个OPEN命令或者此调用进行未被授权使用此连接返回错误。最简单的实现方法是在缓冲区没有填充完以前不返回控制权,但这样可能会造成严重的死锁。更复杂的实现方法允许同时存在多个RECEIVE,这样会提高效率。这样是在控制复杂的情况下取得了高效率。

如果在PUSH之前的数据已经填满缓冲区,那么对于RECEIVE的响应中就不用设置PUSH位了。缓冲区会容纳尽可能多的数据,如果在缓冲区填充满以前看到PUSH位,将返回缓冲区中的数据并设置PUSH。在处理紧急状态时,如果有URGENT标记,还有紧急数据;如果没有URGENT标记了,就返回所有紧急数据,用户也离开紧急状态。请注意,在紧急指针指向的点以后的那些数据不能和紧急数据一起返回,即使它们在同一个缓冲区内,当然,如果用户指定要这么做例外。

为了区别多个RECEIVE并保证缓冲区不被充满,返回的数据中也要包括缓冲区指针和一个计数器指明现在接收了多少数据。RECEIVE可以有自己专用了缓冲区,也可以和用户共享一个缓冲区。

Close

格式: CLOSE (本地连接名)

此命令关闭连接,如果连接未打开,或未授权可以关闭连接返回错误。在关闭的时候应该注意正常关闭,让所有的发送都发送完数据,也可以如上所说在CLOSE后面加上几个SEND,这就要求用户在收到CLOSE后仍然要进行接收。因此,CLOSE意指“我没有更多的数据要发了”,并不代表“我不再发送任何数据了”。关闭方也可能在超时前不能发出所有数据,这种情况下,要由CLOSE转入ABORT状态。用户可以自己决定在任何时间关闭连接,也可以根据TCP返回的提示关闭。因此关闭操作要和外TCP进行通信,因此在关闭状态可能要呆一会儿,在CLOSE没有返回前调用打开,会返回错误。

Status

格式: STATUS (本地连接名) -> status data

这个命令和具体的实现有关,而且有可能会有负作用。返回的信息通常来自有连接相关的TCB。返回的数据块中包括下面的信息:本地套接字,外套接字,本地连接名,接收窗口,发送窗口,连接状态,等待确认的缓冲区数,等待接收的缓冲区数,紧急状态,优先级,security/compartment和传输超时。因此实现不同,所以上述数据项中可能有几项没有意义或根本不存在。如果调用进程没有被授权使用这一连接,返回错误。这一点会防止未被授权的进程获得连接状态。

Abort

格式: ABORT (本地连接名)

此命令中止所有SEND和RECEIVE,删除TCB,将发送特殊的RESERT信息到对方TCP。具体的返回信息会因实现不同而不同。

TCP到用户信息

假定操作系统提供一种可以使TCP异步传送信息到用户程序的机制。当TCP确实通知用户程序时会返回一些特定的信息。通常在这些信息中也会有错误信息,在其它情况下会有关于完成SEND或RECEIVE或其它用户调用的相关信息。会提供下面的几种消息:

本地连接名 都提供

响应串 都提供

缓冲区地址 发送和接收

字节记数 接收

Push标记 接收

Urgent标记 接收

 

TCP和下层接口

TCP实际上调用下层服务才能在网络上传输数据,在互联网上我们通常认为TCP的下层是IP协议。如果下层是IP层,它提供一些类于服务类型和生存时间的参数。TCP使用这些参数的如下设置:

Type of Service = Precedence: routine, Delay: normal, Throughput: normal, Reliability: normal;或是数字00000000。

Time to Live = 一分钟,或是数字00111100。

请注意:假定的最大数据段的生存时间为2分钟,这里人为指定为1分钟。

如果下层是IP而且使用源地址路由,接口必须允许路由信息的通信。这对建立连接和进行路由是十分重要的。当然也可以不使用IP协议作为TCP的底层协议,但无论下层协议是什么,都必须提供源地址,目的地址和协议域,以及一些决定TCP长度的域,总之一句话,要能够提供类似于IP的功能。

3.9. 事件处理

下面说明的过程是可能的实现,其它实现和本例的过程可能有一点点不同,但只在细节,而决不在结果。TCP的活动可以总结为对事件的响应。事件可以分为三类:用户调用,接收数据段和超时。下面描述的是TCP对具体事件的响应,在许多情况下,相关的动作(响应)要和连接状态相关。

用户调用的有:

OPEN

SEND

RECEIVE

CLOSE

ABORT

STATUS

接收数据段的有:

SEGMENT ARRIVES

超时的有:

USER TIMEOUT

RETRANSMISSION TIMEOUT

TIME-WAIT TIMEOUT

TCP对用户的响应可能是立即的也可能是延时的。错误信息以字符串的形式给出。下面就是一个具体的错误信息:error: connection not open。另外,请记住序列号空间大小为2的32次方。处理数据段的顺序为首先接收,再检查序列号,如果是要接收的就放入接收队列。另外,没有说明状态转移时,TCP保持原来的状态。

OPEN调用

CLOSED状态

创建新的TCB保存连接状态信息,填充本地套接字标记,外套接字,优先级,security/compartment和用户超时信息。注意一部分外套接字在被动OPEN中可能未说明。如果是主动的,而外套接字未指定,返回"error: foreign socket unspecified";如果是主动的,而外套接字指定了,发送一个SYN数据段。选择初始发送序列号ISS。SYN数据段的格式如下<SEQ=ISS><CTL=SYN>,设置SND.UNA为ISS,SND.NXT为ISS+1,进行SYN-SENT状态,然后返回。

如果调用者不能访问指定的本地套接字,返回"error: connection illegal for this process"。如果没有空间接收新的连接,返回"error: insufficient resources"。

LISTEN状态

如果处于主动状态,指定了外套接字,可以将连接从被动改为主动,并选择ISS。发送一个SYN数据段,设置SND.UNA为ISS,SND.NXT为ISS+1。进入SYN-SENT状态。和SEND一起的数据可以和SYN数据段一起发送,也可以在进入ESTABLISHED状态后发送。如果没有空间接收请求,返回"error: insufficient resources"。如果未指定外套接字,返回"error: foreign socket unspecified"。如果处于以下状态:SYN-SENT状态,SYN-RECEIVED,ESTABLISHED状态,FIN-WAIT-1状态,FIN-WAIT-2状态,CLOSE-WAIT状态,CLOSING状态,LAST-ACK状态或TIME-WAIT状态时返回"error: connection already exists"。

SEND调用

CLOSED状态

如果用户无权访问连接,返回"error: connection illegal for this process"。否则返回"error: connection does not exist"。

LISTEN状态

如果指定了外套接字,可以将连接从被被动改为主动,选择一个ISS。发送SYN数据段,设置SND.UNA为ISS,SND.NXT为ISS+1。进入SYN-SENT状态。和SEND一起的数据可以和SYN数据段一起发送,也可以在进入ESTABLISHED状态后发送。如果没有空间接收请求,返回"error: insufficient resources",如果未指定外套接字,则返回"error: foreign socket unspecified"。

SYN-SENT状态和SYN-RECEIVED时

在进入ESTABLISHED状态后将需要传送的数据加入队列。如果队列已无空间,则返回"error: insufficient resources"。

ESTABLISHED状态和CLOSE-WAIT状态

将缓冲区分段,发送缓冲区数据,并使它带有确认值RCV.NXT。如果没有空间保存缓冲区,则返回"error: insufficient resources"。如果设置了紧急标记,那么SND.UP <- SND.NXT-1,并设置紧急指针指向发送数据段中的相应位置。

在FIN-WAIT-1,FIN-WAIT-2,CLOSING,LAST-ACK和TIME-WAIT状态时返回"error: connection closing",不理会请求。

RECEIVE调用

CLOSED状态

如果用户没有权利访问这个连接,返回"error: connection illegal for this process"。如果有权利,则返回"error: connection does not exist"。

在LISTEN,SYN-SENT和SYN-RECEIVED状态下

在进入ESTABLISHED状态后,将数据放入队列准备处理。如果队列中没有空间,返回"error: insufficient resources"。

在ESTABLISHED,FIN-WAIT-1和FIN-WAIT-2状态下

如果没有接收到足够的数据段满足请求,将请求放入队列。如果队列中没有空间记录RECEIVE,返回"error: insufficient resources"。重新将接收数据段放入接收缓冲区,并返回给用户。在这种情况下,可以设置PUSH标记。如果RCV.UP比现在传送给用户的要大,通知用户有紧急数据。

CLOSE-WAIT状态

因为远方TCP已经发送了FIN,RECEIVE必须由现在在缓冲区但还未传送给用户的数据满足。如果没有数据了,RECEIVE会得到"error: connection closing"的响应。

在CLOSING,LAST-ACK和TIME-WAIT状态下返回"error: connection closing".

 

CLOSE 调用

CLOSED状态

如果用户没有权利访问这个连接,返回"error: connection illegal for this process"。如果有权利,则返回"error: connection does not exist"。

LISTEN状态

存在着的任何RECEIVE都会返回"error: closing"的响应。删除TCB,然后进入CLOSED状态并返回。

SYN-SENT状态

删除TCB并返回"error: closing"响应给任何在队列中的SEND或RECEIVE。

SYN-RECEIVED状态

如果没有发送SEND,而且没有等待发送的数据了,则形成并发送FIN数据段,进入FIN-WAIT-1状态;否则在进入ESTABLISHED状态后将命令保存在队列中等待处理。

ESTABLISHED状态

将这一命令保存在队列中,直到所有正在进行的SEND完成后,形成并发送FIN数据段,然后进入FIN-WAIT-1状态。

在FIN-WAIT-1和FIN-WAIT-2状态下

严格地说,这将会是个错误,会收到"error: connection closing"。只要第二个FIN没有发出,也可以收到一个"ok"响应。

CLOSE-WAIT状态

直到所有SEND命令完成后再将这个请求放入队列;然后发送FIN数据段,进入CLOSING状态。

在CLOSING,LAST-ACK和TIME-WAIT状态下返回"error: connection closing"。

 

ABORT调用

CLOSED状态

如果用户没有权利访问这个连接,返回"error: connection illegal for this process"。如果有权利,则返回"error: connection does not exist"。

LISTEN状态

任何存在的RECEIVE都应该返回"error: connection reset",删除TCB,进入CLOSED状态,然后返回。

SYN-SENT状态

对于所有在队列中的SEND和RECEIVE,都返回"connection reset",删除TCB,进入CLOSED状态,然后返回。

在SYN-RECEIVED,ESTABLISHED,FIN-WAIT-1,FIN-WAIT-2和CLOSE-WAIT状态

发送数据段<SEQ=SND.NXT><CTL=RST>,对于所有在队列中的SEND和RECEIVE,都返回"connection reset",;所有要进行传送或重新传送的数据段立刻进行,删除TCB,进入CLOSED状态,然后返回。

在CLOSING,LAST-ACK和TIME-WAIT状态下返回"ok",删除TCB,进入CLOSED状态,然后返回。

 

STATUS 调用

CLOSED状态

如果用户没有权利访问这个连接,返回"error: connection illegal for this process"。如果有权利,则返回"error: connection does not exist"。

LISTEN状态

返回"state = LISTEN"和TCB指针。

SYN-SENT状态

返回"state = SYN-SEND"和TCB指针。

SYN-RECEIVED状态

返回"state = SYN-RECEIVED"和TCB指针。

ESTABLISHED状态

返回"state = ESTABLISHED"和TCB指针。

FIN-WAIT-1状态

返回"state = FIN-WAIT-1"和TCB指针。

FIN-WAIT-2状态

返回"state = FIN-WAIT-2"和TCB指针。

CLOSE-WAIT状态

返回"state = CLOSE-WAIT"和TCB指针。

CLOSING状态

返回"state = CLOSING"和TCB指针。

LAST-ACK状态

返回"state = LAST-ACK"和TCB指针。

TIME-WAIT状态

返回"state = TIME-WAIT"和TCB指针。

接收到数据段

如果处于CLOSED状态,所有到达的数据都被抛弃。包括RST的也不例外。如果接收到的数据段不包括RST,则返回一个RST。对于发送TCP要选择它可以认知的确认和序列号。如果关闭ACK位,则序列号为0,格式如下:<SEQ=0><ACK=SEG.SEQ+SEG.LEN><CTL=RST,ACK>如果打开ACK位,格式如下:<SEQ=SEG.ACK><CTL=RST>。

如果处于LISTEN状态,首先检查RST,接收到的RST应该被忽略。下来应该检查ACK,如果连接处于LISTEN状态下,任何ACK都是错误的,应该返回RST段,它的格式如下:<SEQ=SEG.ACK><CTL=RST>。

第三步应该检查SYN,如果SYN位设置了,检查安全性,如果接收数据段中的security/compartment与TCB中的不能匹配,返回RST段,格式如下:<SEQ=SEG.ACK><CTL=RST>。在SEG.PRC大于TCB.PRC的情况下,如果用户和系统允许,则设置TCB.PRC<-SEG.PRC,如果用户和系统不允许,发送RST段并返回。其格式如下:<SEQ=SEG.ACK><CTL=RST>。如果SEG.PRC小于TCB.PRC,继续进行。设置RCV.NXT为SEG.SEQ+1,IRS为SEG.SEQ中的值,其它控制信息和数据以后处理。应该选择ISS,并发送SYN段,其格式如下:<SEQ=ISS><ACK=RCV.NXT><CTL=SYN,ACK>。SND.NXT设置为ISS+1,SND.UNA设置为ISS的值,连接状态改为SYN-RECEIVED。其它控制信息和数据将在SYN-RECEIVED状态中处理,但以于SYN和ACK的处理不应该重复。如果未完全指定LISTEN域,现在就应该指定了。

第四步是处理其它数据和控制信息。其它的数据段(不包括SYN)应该有ACK,因此不要对它进行ACK处理。如果接收到RST段,则它是非法的。如果确实接收到了,抛弃它,直接返回。

如果处于SYN-SENT状态。首先检查ACK位,如果设置了ACK位,而且SEG.ACK =< ISS或SEG.ACK > SND.NXT,发送RST。(除非设置了RST位,如果这样,抛弃这个数据段直接返回)。RST段的格式如下:<SEQ=SEG.ACK><CTL=RST>。不用理会收到的数据段,直接返回。如果SND.UNA =< SEG.ACK =< SND.NXT,那么ACK是可以接受的。

下一步检查RST位。在设置了RST的情况下,如果ACK是可以接受的,通知用户"error: connection reset",抛弃接收到的数据段并进入CLOSED状态,删除TCB并返回。如果ACK不能接受,抛弃数据段返回。

第三步检查安全性和优先级。如果security/compartment和TCB中的security/compartment不匹配,发送RST段。在发送RST段的时候,如果已经有ACK了,格式为:<SEQ=SEG.ACK><CTL=RST>,如果没有ACK,格式为<SEQ=0><ACK=SEG.SEQ+SEG.LEN><CTL=RST,ACK>。在没有发现不匹配的时候,如果有ACK,段中的优先级必须和TCB中的匹配,如果不匹配,发送RST,其格式为:<SEQ=SEG.ACK><CTL=RST>;如果未发现不匹配,而且没有ACK的情况下,如果接收到的数据段中的优先级高于TCB中的优先级,在得到用户和系统许可的情况下可以将TCB中的优先级升高,如果用户或系统不许可,提高prec,然后以如下格式发送RST:<SEQ=0><ACK=SEG.SEQ+SEG.LEN><CTL=RST,ACK>,如果接收数据段中的优先级小于TCB中的优先级则继续。在发送了RST后,抛弃数据段并返回。

第四步检查SYN位。只有在ACK位是合法或没有ACK而且数据段中不包括RST的情况下才能进行这一步。如果设置了SYN位,而且security/compartment和优先级合法,那么RCV.NXT设为SEG.SEQ+1,IRS设为SEG.SEQ。SND.UNA应该增加和SEG.ACK相等,在重新发送队列中原来等待确认而现在已经被确认的数据段也被清除。如果SND.UNA > ISS,改变连接状态为ESTABLISHED,形成下面格式的ACK段并发送: <SEQ=SND.NXT><ACK=RCV.NXT><CTL=ACK>。队列中用于发送的数据的控制信息也一起发送,如果段中没有数据或控制信息,则进行第六步,否则返回。如果SND.UNA > ISS不成立,形成SYN,ACK段,格式如下:<SEQ=ISS><ACK=RCV.NXT><CTL=SYN,ACK>,并发送它。如果在段中没有数据或控制信息,待进入ESTABLISHED状态后再进行处理。

第五步,如果SYN或RST位没有设置,抛弃数据段返回。

下来我们来看看其它状态。首先应该检查序列号。在下面状态下

SYN-RECEIVED状态

ESTABLISHED状态

FIN-WAIT-1状态

FIN-WAIT-2状态

CLOSE-WAIT状态

CLOSING状态

LAST-ACK状态

TIME-WAIT状态

你可能感兴趣的:(数据结构,应用服务器,网络协议,网络应用,Security)