RFC793-TCP中文翻译

简介

翻译自:rfc793

TCP是一个高可靠的主机到主机之间的通讯协议,这些主机通常位于分组交换网络(基于IP协议)之中。

本文档主要描述TCP的一些基本功能、实现时的相关步骤、以及在使用TCP时用户和程序需要关心的相关接口。

动机(Motivation)

计算机通讯系统在军事、政府、社会中越来越重要。 本文档主要关注军事方面的通讯需求,特别是如何实现可靠的通讯以及在出现网络拥塞时如何保证网络可用性,这些问题也同样出现在政府和社会通讯系统中。

随着战略战术计算机通讯网络的研发和部署,有必要提供一些连通这些网络的方法以及可以支持大部分应用进程可以互相通讯的标准协议。出于对这种标准的需要,负责研究与工程的副部长助理将本文讨论的TCP协议作为美国国防部系统中进程通讯协议标准的基础。

TCP是面向连接的、端到端的可靠通讯协议,它被设计到一个支持多网络应用的层级网络协议栈中。TCP主要为两个不同主机中的进程提供可靠的通讯服务,这两个主机处于独立并且相互连通的计算机通讯网络中。在网络协议栈(OSI七层或者TCP/IP五层网络架构)中,处于TCP下面的网络协议不保证数据通讯的可靠性。TCP假定会从下层协议获取简单的但是可能乱序的数据报服务。原则上TCP可以在很多种类的通讯系统上运行,其中包括分组交换网络、电路交换网络(虚电路)、硬线连接的网络等。

TCP基于Cerf 和 Kahn在【Cerf, V., and R. Kahn, "A Protocol for Packet Network Intercommunication", IEEE Transactions on Communications,Vol. COM-22, No. 5, pp 637-648, May 1974.】论文中提出的相关概念。在网络协议栈中,TCP位于IP之上,IP协议可以为TCP提供接收与发送可变长度的TCP数据段服务,IP协议把这些数据段放在网际报文'信封'中。网络报文可以在不同网络中定位源TCP(端口)和目的TCP(端口)。当TCP数据段需要在多个网络和互连网关转发和传输时,IP协议需要处理TCP数据段的拆包和重组问题。IP协议可以携带关于优先级、安全等级、TCP数据段拆包相关信息,这些信息可以穿过不同的网络保证了端(进程)到端之间的通信。

                           Protocol Layering

                        +---------------------+
                        |     higher-level    |
                        +---------------------+
                        |        TCP          |
                        +---------------------+
                        |  internet protocol  |
                        +---------------------+
                        |communication network|
                        +---------------------+

                                Figure 1

本文档约定TCP协议实现程序需要和主机中的上层协议实现程序共同运行。有些计算机系统需要通过一个前端机去联网,这个前端机安装有TCP和IP协议软件,同样也包括其他相关的网络软件。TCP规范为上层网络协议提供了一套接口,这套接口同样适合前端机场景,只要主机到前端机之间实现了相关的通讯协议(host-to-front end protocol)。

范围(Scope)

TCP为在多网络环境中的主机进程提供进程到进程的可靠通讯服务。TCP希望成为多种网络环境中广泛使用的通讯协议。

关于本文档(About this Document)

本文档描述了实现TCP的具体规则,包括与上层协议交互时的规则、与其他TCP程序进行交互所需要的规则。本章剩余部分对协议接口和具体操作进行了详细地描述。第二章总结了TCP设计的哲学基础。第三章描述了不同的事件(新数据段到达、用户调用、出错等)出现的时候TCP采用的动作和TCP数据段的数据格式。

接口(Interfaces)

TCP接口包括面向用户或者应用进程的接口和面向底层协议(比如IP协议)的接口。

我们会比较详细的介绍应用程序和TCP之间的接口,这个接口包括一组调用,这种调用类似于操作系统为应用程序提供的操作文件的系统调用。比如用来打开链接和关闭链接的调用,在建立的链接上发送数据和接收数据的调用。同时TCP组件和应用程序异步通讯的功能比较受期待。针对特定的操作系统平台,TCP在实现的过程中是可以有合理的自由度去设计接口,对于TCP/user之间的接口,任何合理的实现都应该保持一个约定的最小范围内的功能列表。

TCP和底层协议之间没有规定具体的实现细节,但是假设有一种机制可以在两层协议间进行异步的消息传递。通常人们希望底层协议会定义一个这样的异步接口。这里的底层协议在本文档中一般指IP协议。

操作(Operation)

正如上面文章描述的,TCP主要的目的是为两个不同进程间提供可靠的、安全的逻辑电路(logical circuit)或者连接(connection)服务。在不可靠的网络通讯系统中提供这种稳定可靠的服务需要在以下几个领域具备基本的实现(facilities):

  1. 基本数据传输
  2. 可靠性
  3. 流量控制
  4. 多路复用
  5. 长连接
  6. 优先级和安全

关于TCP这几个实现的基本操作(basic operation)都在后续章节进行介绍。

TCP介绍

基本数据传输

TCP可以在两个用户进程之间进行双向的字节流传输,它是通过把一定数量的字节放在数据段中并在网络上传输来实现(字节流传输)的。通常,TCP自主决定什么时候对数据进行打包(block)并发送出去。

有时候用户需要确定提交给TCP的数据已经被TCP发送出去了。为了实现这个功能,TCP定义了一个推数据(PUSH)的功能。发送者需要指定要把数据推送(PUSH)到接收方,才能保证提交给TCP的数据被发送出去。一个推的指令会立即把在那个时刻收集到的数据(data up to that point)发送到接收方。发送PUSH指令的准确时间接收方可能不会知道,并且推送功能也不会提供这次推送的数据量相关信息。

可靠性(序号、超时、确认、重传

TCP必须能解决由通讯网络造成的数据损坏、丢失、重复、或者乱序问题。TCP通过给每个字节添加递增序列号并且要求接收方收到数据后发送一个确认(ack)来解决上述问题。如果在一定时间内没有收到确认,发送方会再次发送数据。接收方通过序列号为乱序的字节排序,并且可以把重复的数据段丢弃。通过给每个传送的数据段添加校验和可以检测数据是否被破坏,接收方收到数据后检测校验和并丢弃受到破坏的数据段。

只要TCP运行良好,并且通讯网络不会出现完全独立的两个子网(网络分区),数据的传输错误一般都不会影响数据的正常分发。TCP也通常可以从通讯网络错误中恢复。

流量控制(Flow Control)

TCP为接收方提供一种可以控制发送方发送数据量的方法。接收方在每次发送确认信息(ACK)的时候携带一个win参数来通知发送方剩余的可发送字节数。

多路复用(Multiplexing)

为了可以让在同一个主机上的多个进程同时使用TCP提供的通讯组件,TCP为每个主机提供了多个端口。Socket把TCP和底层的网络传输层串联起来。一对Socket独立标识一个连接。一个Socket可以同时用在多个连接中(java 中的serversocket便是如此,主动模式下的FTP 20数据端口也可以)。

每个主机会把不同的进程绑定到不同的端口上。通常会把经常使用的应用程序(日志或者日期服务,http,ft应用)绑定到固定的端口上。这些应用程序提供的服务之后可以通过这些端口地址访问。

连接(Connections)

上面文章讨论的可靠性和流量控制机制需要TCP为每个数据流建立和维护相关的状态信息。Socket、字节序列号、窗口大小还有其他类似的状态信息统称为连接。每个连接都被一对Socket唯一定义,每个Socket定义连接的一端。

当两个进程想要通讯,他们必须使用TCP建立一个连接(在每个端建立状态信息)。当他们通讯结束后,相对应的连接会被中断或者关闭以释放资源让其他进程继续使用。

因为连接可能建立在不可靠的计算机和不可靠的通讯网络上,所以基于时钟序号的握手机制可以避免错误地初始化连接。

优先级和安全(Precedence and Security)

TCP用户可以为连接设置相关的安全和优先级。当这些功能没有被显式设置的时候,会被设置一些默认值。

设计的哲学(PHILOSOPHY)

网络系统中的元素(Elements of the Internetwork System)

互连网络包括通讯网络以及连接到这些网络的主机,通讯网络通过网关(ip层面上,网关一般就是我们连的路由器,链路层或者物理层是指具体的网关设备)相互连接。这里假设通讯网络有可能是本地网络(以太网)也有可能是大的广域网(阿帕网),这些通讯网络(跨网通讯,不管是跨越多个局域网,还是跨越多个地区的城域网)都是基于分组交换技术。位于通讯网络不同层次的通讯协议、网关、以及主机共同组成了可以保证进程相互通讯的系统,这个系统支持在两个进程端口间的逻辑链路上进行双向的数据传输。

这里的packet一般指主机和网络之间一次交换的数据。我们一般不用关心数据包在网络间传输的格式。

主机就是连接到网络的电脑,从通讯网络角度来看,主机就是数据包的来源地和目的地。进程就是主机中活跃的元素(进程通常的定义就是执行中的程序)。 即使是终端、文件、io设备也被认为是进程间的通讯方式。

因为一个进程需要区分与其他多个进程之间的通讯,所以每个进程都可以申请多个端口。

操作模型(Model of Operation)

进程把需要发送的数据以参数的形式传给TCP的系统调用。TCP收到数据后,从参数中获取数据并打包成多个数据段,然后调用IP层提供的接口来把数据段发送到目的进程。负责接收的TCP把数据段传递到用户进程设置的缓存中,并通知用户进程。TCP数据段包括一些控制信息,从而保证可靠的有序的的数据传输。

网络通讯模型就是,每个TCP都有关联的网络通讯模块,TCP为上层用户提供了一个可以连接本地网络的接口。这个网络通讯模块把TCP数据段打包成数据报文并把这些数据报文路由到目的网络模块或者离本地网络最近的网关。为了通过本地网络,数据段是被包装在本地网络报文(local network packet)中的。

分组交换程序可能会进行打包、分段、或者其他的一些操作来保证可以通过本地分组(物理链路层的数据包)到达目的网络模块。

位于不同本地网络之间的网关,负责将IP报文从本地网络报文中拆解出来,然后找到下一个IP报文需要通过的本地网络。将IP报文包装到下一个本地网络协议包中并路由到目的网络的相关网关(网关和网关之间也是一个本地网络连接),或者直达目的网络模块(比如计算机)。

通过下一个本地网络时,网关有可能会把网络报文拆分成更小的网络报文(下一个本地网络MTU比较小)。在后面的传输过程中,有可能会继续拆分网络报文。网络报文段数据格式可以方便数据接收方(网络模块)把网络报文段重新组成一个网络报文。

网络模块(接收方)把TCP数据段从IP报文中拆解出来发送到上层的TCP模块。

上述操作模型看着非常简单,但是实际上整个过程是非常复杂的。一个重要的特性就是可以选择服务类型。服务类型可以指导网关或者网络模块在发送数据包到下一个网络的时候设置合适的参数。服务类型中就包括一个数据包优先级。报文也有可能会携带相关安全设置来允许主机或者网关在不同级别的安全环境中运行以便隔离数据包。

主机环境(The Host Environment)

TCP是操作系统的一个模块。用户使用TCP就像他们操作本地文件一样。TCP有可能会调用其他系统功能,比如操作数据结构。与网络相关的接口被相应的设备驱动(网卡)模块管理。TCP不直接调用网络设备驱动而是调用网络模块,网络模块会直接调用相应的设备驱动。

前端处理机可以方便的实现TCP的相关功能和机制,TCP并不会阻碍这种实现方式。但是,在这种实现方式下,主机到前端处理机之前的通讯协议必须支持本文档规定的相关功能,并且把相应的功能接口提供给上层用户。

接口(Interfaces)

TCP/User接口可以为使用TCP的用户提供打开、关闭、获取连接状态的功能。这些系统调用与用户操作文件进行的系统调用是一样的,比如打开文件、读取数据、关闭文件。TCP/IP接口为主机的TCP模块提供与网络系统中任何其他地方的TCP模块进行数据收发的功能。这些系统调用可以传递地址、服务类型、优先级、安全或者其他控制信息。

与其他协议的关系(Relation to Other Protocols)

下面展示了TCP在协议栈中的位置:

       +------+ +-----+ +-----+       +-----+
       |Telnet| | FTP | |Voice|  ...  |     |  Application Level
       +------+ +-----+ +-----+       +-----+
             |   |         |             |
            +-----+     +-----+       +-----+
            | TCP |     | RTP |  ...  |     |  Host Level
            +-----+     +-----+       +-----+
               |           |             |
            +-------------------------------+
            |    Internet Protocol & ICMP   |  Gateway Level
            +-------------------------------+
                           |
              +---------------------------+
              |   Local Network Protocol  |    Network Level
              +---------------------------+

                         Protocol Relationships

                               Figure 2.

TCP应该比较高效地支持上层协议。TCP为上层提供的接口应该与IP为TCP提供的接口一样简单易用。

可靠通讯(Reliable Communication)

在TCP连接上发送的数据流会可靠、按顺序地到达目的地。通过递增序列号和反馈机制保证传输的可靠性。概念上,每个字节都会赋予一个序列号。数据段传输的一段字节数据,第一个字节的编码就是这个数据段的编码。发送出去的数据段也会携带一个确认序列码,指示期望的下一个数据段的起始字节序列号。当TCP发送数据段的时候,会复制一份数据到重发队列中,并启动定时器。当收到对这个数据段的确认时,这个数据段会从重发队列中删除。如果数据段没有收到确认,并且定时器超时,TCP会重新发送对应的数据段。

确认机制只能确保TCP模块收到了数据,并不能确保应用程序确实收到了数据。

为了控制TCP模块之间的数据流量,流量控制机制被引入。接收方向发送方报告一个窗口(win)。这个窗口表示从接收方收到的最后一个字节开始,还能接收的字节数(接收方剩余的read buffer空间)。

连接的建立与清除

每个TCP端口都是独立选择的,所以端口号有可能会重复。为了保证TCP地址的唯一性,我们把IP地址与端口号连接在一起,IP:PORT,这两个可以保证全网唯一的连接。

一个连接会被两端的socket唯一确定。一个本地的socket可能会参与多个连接,每个连接的另一端就是其他主机上的socket。一个连接可以在两个方向传递数据,这个被称为全双工。

TCP可以为进程自由选择端口号。通常会为那些常用的socket(端口号)绑定常用的应用进程。我们可以假定某些进程拥有某些固定的端口号,这些进程只能绑定到那些它们拥有的端口上。(实现进程和端口的绑定规则由使用者自己规定,但是现在我们假定用户请求为一个进程绑定一个端口,或者可以把一组端口独立的赋予一个进程,比如把某些端口的高位为某些数值的一组端口赋予一个给定的进程。)

OPEN方法中指定的本地端口和远端socket确定了一个连接。之后,用户会使用tcp提供的一个本地连接来进行后续的操作。我们假设有Transmission Control Block(TCB)数据结构来记录连接的相关信息。一个关于代码实现的建议就是把指向TCB的指针当作这个连接的名字。OPEN调用也可以指定连接是主动建立还是被动建立(client connect、server listen)。

被动的OPEN调用表示进程想接收建立连接的请求而不是主动去建立一个连接。通常被动OPEN可以接收任何建立连接的请求(来自任何应用)。Foreign address都为零表示未指定socket,未指定socket只有在被动OPEN调用时能使用。如下图是在centos服务器上用netstat -nltp查看的。

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:5601            0.0.0.0:*               LISTEN      30577/node
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      21514/mysqld

一个对外提供服务的进程如果要为其他未知的进程提供服务,就需要一个被动OPEN并且Foreign Address都指定为0的接口调用。之后外部进程就可以向本地进程请求建立连接。如果这个服务使用的端口号是比较知名的端口号,那么请求服务时会比较方便。

一些标准服务都是提前绑定到了比较常见的一些端口上。比如,Telent服务被永久的绑定到了特定的端口上,其他的端口也为文件传输、远程job、文本生成器、回声、或者sink处理等服务预留(后面三个服务只为了测试使用)。有的端口会提供服务查询功能,并且会根据的查询信息在相应的端口上启动新的服务。预留端口是TCP协议规范的一部分,但是端口与服务的绑定关于超出了本文档的讨论范围。

进程可以打开一个被动的连接,等待其他进程向本进程发起建立连接的请求,当新连接建立时,本进程会收到相关的通知。两个进程同时发起向对方建立的申请,连接依然会正常建立。在分布式计算情况下,每个计算机都是相互独立异步运行,这种灵活建立连接的方式很方便地为分布式计算提供支持。

在本地等待被连接的服务和外部主动请求连接的OPEN请求,有两个规则可以确定本地服务是否会接收外部的连接请求。第一种情况下,本地服务准确指定了可以建立连接的外部地址。另一种情况是本地服务没有指定可以建立连接的外部地址,任何外部连接请求都会被处理。其他处理规则包括部分匹配限制。

如果存在多个本地服务去请求绑定同一个端口,并且有的指定 了外部服务地址(有的没有指定),那么当一个主动建立连接的请求到来的时候,指定了外部服务地址的本地服务会优先得到匹配(如果匹配的话)。

建立连接的过程中使用到了SYN控制标识符并且涉及到三个消息的交换。这个交换被称为三次握手。

当用户通过OPEN指令发送一个连接请求的时候,请求的数据段会携带SYN到达服务器,这个时候新的连接会被初始化,同时一个等待处理的TCB数据结构也会被建立并保存。本地和外部地址的匹配程度确定何时去初始化一个连接。当交互的两端同步了各自的初始序列号,这个连接便会正确建立。

关闭连接同样需要交换数据段,在这种情况下数据段中会包含FIN控制位。

数据通讯(Data Communication)

可以把数据看做是连接上的字节流。发送方通过SEND指令发送数据,如果发送的数据中指定了PUSH标志,数据会立即从发送方的缓存中发送出去。

TCP模块可以缓存用户传过来的数据,并以一定的方式发送出去,但是如果上层用户指定了PUSH标志,TCP模块必须把缓存的数据发送出去。如果接收方收到带有PUSH标志的数据,那么它应该马上把数据传递到用户进程。

push函数调用与发送数据段的大小无关。数据段中的数据有可能来自一个send调用,两个send调用或者多个send调用。

push函数和PUSH标志是为了把数据尽快发送到接收方,这个操作没有相关记录的服务。

push函数和介于TCP/USER之间的buffer有一定的关系。每次带有PUSH标志的数据放入用户读缓存的时候,这个buffer会立即提交到用户进程(无论buffer是不是满的),如果buffer满了,没有收到PUSH标志,那么buffer会直接提交到用户进程。

当前进程正在处理数据流,可以在数据流之后的某个时刻为某个数据标记为紧急,TCP提供了这种与接收方进行交流的方式。TCP不规定当收到紧急数据时用户进程如何处理,但是标准流程是,当收到紧急数据时,用户进程应该尽快处理相关数据。

优先级和安全(Precedence and Security)

TCP基于IP模块提供的服务类型字段和安全选项为每个建立连接的用户提供优先级和安全方面的服务。并不是所有的TCP模块都需要在不同级别的安全环境(multilevel secure,IBM Docs )中运行,有些可能只在非安全情况下使用,或者有些只在一级隔离中运行。有些TCP实现和服务有可能只会在多级别安全的某些场景下使用。

运行在多环境下的TCP模块,必须为每个发送出去的数据段添加安全、隔离区、优先级。TCP模块也必须为上层协议或者用户提供可以设置安全级别、隔离区、优先级的接口。

健壮性规则(Robustness Principle)

TCP遵循一般健壮性准则:对于要做的事情持保守态度,外部的东西要保持欢迎的态度。

功能规格(FUNCTIONAL SPECIFICATION)

头部格式(Header Format)

TCP数据段被包装成IP数据报文发送。IP数据报文包括几个信息字段,包括源地址和目的地地址。TCP头紧随IP头之后,表示TCP报文需要的几个字段。这么划分IP和TCP数据头可以为以后主机协议(host level protocols)提供更好的扩展。

TCP Header Format

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          Source Port          |       Destination Port        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                        Sequence Number                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Acknowledgment Number                      |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Data |           |U|A|P|R|S|F|                               |
   | Offset| Reserved  |R|C|S|S|Y|I|            Window             |
   |       |           |G|K|H|T|N|N|                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           Checksum            |         Urgent Pointer        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Options                    |    Padding    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                             data                              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

                            TCP Header Format

          Note that one tick mark represents one bit position.

                               Figure 3.

源端口: 16 bits

 源端口号

目的端口:16 bits

 目的端口号

序列号:32 bits

 如果没有SYN标志,数据段的序列号就是第一个字节的序列号。如果有SYN标志,序列号就是初始序列号,第一个要发送的字节序列号是初始序列号+1。

确认序列号:32 bits

  当ACK标志被设置,确认序列号表示接收方期望收到的下一个数据段开头的字节序号。连接建立后,该序列号一直会被传送。

数据偏移地址:4 bits

  表示数据数据体开始的位置。TCP的头部长度是32位的整数倍。

预留:6 bits

  为以后功能扩展预留,必须为0

控制位:6 bits (自左到右):

  1. URG: 紧急标识位
  2. ACK: 确认标识位
  3. PSH: 推送标志位
  4. RST: 复位连接标识位
  5. SYN: 同步标志位
  6. FIN: 结束标志位

窗口:16 bits

  接收方发送的确认字段中会携带一个数字,这个数字表示接收方最后一次成功确认之后还能接收的字节数。

校验和:16 bits

  伪首部、TCP报头、TCP数据按照16位分组,对这16位分组分别进行反码求和运算(二进制反码求和是怎样求的?_百度知道IP和TCP头校验和计算算法详解_jiangqin115的专栏-CSDN博客 关于二进制反码求和运算需要说明的一点是,先取反后相加与先相加后取反,得到的结果是一样的),最后把得到的数据进行反码运算获得的16数据就是校验和。 反码求和运算:

  1. 把校验和字段置为0;
  2. 对IP头部中的每16bit进行二进制求和;
  3. 如果和的高16bit不为0,则将和的高16bit和低16bit反复相加,直到和的高16bit为0,从而获得一个16bit的值;
  4. 将该16bit的值取反,存入校验和字段。
  5. 反码运算时,其符号位与数值一起参加运算。

如果最后字节数是奇数,为了获得校验码,需要在最后添加一个字节,这个字节的每位都是0。添加的字节不会被发送(只是为了计算校验和)。计算校验和的时候会在TCP头部前面添加96位的伪头部。伪头部包括源地址(ipv4)、目的地址(ipv4)、协议、TCP数据长度。这样TCP可以防止数据包错误路由。这些信息(地址信息)会通过TCP/IP接口从IP模块获取到。


                     +--------+--------+--------+--------+
                     |           Source Address          |
                     +--------+--------+--------+--------+
                     |         Destination Address       |
                     +--------+--------+--------+--------+
                     |  zero  |  PTCL  |    TCP Length   |
                     +--------+--------+--------+--------+

TCP长度包括TCP头部和数据长度,不包括添加的12个字节伪头部。

紧急指针:16 bits

 这个值与数据段的第一个字节的序列号相加就是紧急数据的起始位置。这个字段只有在紧急标志位设置了才会被处理。

选项:长度可变

 选项出现在TCP尾部,并且是长度是8位的倍数。所有的选项也会被计算在校验和内。选项可以从任意自己开始。选项有两种格式:

  1. 单字节,表示选项类型
  2. 单字节选项类型、一个字节表示选项长度、实际的选项数据

选项长度包括选项类型、选项具体的数据值。

选项列表的数据长度可能要比TCP头部指定的数据的起始位置要少。在End-of-Option与实际数据之间使用值为0的字节填充。

目前定义的选项包括:

      Kind     Length    Meaning
      ----     ------    -------
       0         -       选项列表结束标志
       1         -       误操作
       2         4       最大段长度

具体定义:

  • 选项列表结束标志位:
        +--------+
        |00000000|
        +--------+
         Kind=0

表示选项列表的结束,这个可能与TCP头部的数据起始位置(TCP头部结束位置)不相符。这个表示整个选项列表的结束,不是每个选项的结束。

  • 无操作:
        +--------+
        |00000001|
        +--------+
         Kind=1

这个选项一般用在其他选项之间,比如用来将某些选项对齐到某个字上。发送方可能不会使用这种选项,接收方应该时刻准备接收没有对齐到字的选项。

  • 最大段长度:16位
        +--------+--------+---------+--------+
        |00000010|00000100|   max seg size   |
        +--------+--------+---------+--------+
         Kind=2   Length=4

这个选项表示接收方可以接收的最大数据段,这个选项一般会在连接建立时刻指定(SYN)。如果没有指定这个字段的值,则表示数据段大小不受限制。

  • 填充选项:可变长度 填充选项用来保证TCP头部结束和数据开始部分对齐与32位数据边界。

术语(Terminology)

在我们详细讨论TCP操作的具体细节前,需要对一些重要的关键词做一些解释。需要几个变量来维护TCP连接的状态,把保存这种变量的数据结构叫做TCP控制块或者TCB。这些变量包括:本地或者对端端口号,连接的安全和优先级,指向用户发送或者接收数据buffer缓存的指针,指向重发队列和当前发送数据段的指针。关于发送和接收的序列号的信息也被存储在TCB中。

 发送序列变量

      SND.UNA - 发送但未确认的
      SND.NXT - 下次发送的字节序列号
      SND.WND - 发送窗口
      SND.UP  - 紧急数据指针
      SND.WL1 - 上次更新窗口使用的字节序列号
      SND.WL2 - 上次更新窗口使用的确认序列号
      ISS     - 初始序列号

 接收序列变量

      RCV.NXT - 下次希望接收的字节序列号
      RCV.WND - 接收窗口大小
      RCV.UP  - 紧急数据指针
      IRS     - 连接建立时初始序列号

下图有助于理解这几个变量:

  发送序列号空间

                   1         2          3          4
              ----------|----------|----------|----------
                     SND.UNA    SND.NXT    SND.UNA
                                          +SND.WND

        1 - 已经被确认的字节序列
        2 - 未确认的字节序列
        3 - 新数据发送可以使用的字节序列
        4 - 还没有分配的,未来会用的字节序列号

                          Send Sequence Space

                               Figure 4.

图4中的3表示发送窗口(发送窗口可以控制应用向TCP写数据的速度)大小。

  接收序列号空间

                       1          2          3
                   ----------|----------|----------
                          RCV.NXT    RCV.NXT
                                    +RCV.WND

        1 - 被确认的序列号
        2 - 打算接收的序列号
        3 - 还未被分配的序列号

                         Receive Sequence Space

                               Figure 5.

图5中的2表示接收窗口。

本节讨论的常用变量的数据都是从当前数据段中的相关字段中获取的。

    Current Segment Variables

      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是不存在的是因为没有相关的TCB数据块,因此也没有连接。这些状态更详细的解释:

  1. LISTEN-代表接收来自任何其他TCP组件的连接请求。
  2. SYN-SENT-发送完SYN标志位之后,等待对方发送ACK标志。
  3. SYN-RECEIVED 等待接收之前发送的syn的ack。
  4. ESTABLISHED 连接已经建立,用户进程可以收发数据。
  5. FIN-WAIT-1 等待之前发送fin的ack或者连接对端的fin标志。
  6. FIN-WAIT-2 等待对端的fin标识数据。
  7. CLOSE-WAIT 等待本地用户进程发送关闭指令(一般是半关闭,比如java socket的shutdownOutput)。
  8. LAST-ACK 等待之前接收发送的fin的ack。
  9. TIME-WAIT 对端收到本地发送回去fin ack的最长时间。
  10. CLOSED标识连接已经拆除。

事件驱动TCP连接状态的变化。事件包括:用户进程调用的方法,OPEN, SEND, RECEIVE, CLOSE,ABORT, and STATUS,收到的数据段、特别是包含了 SYN, ACK, RST and FIN 这些标志的数据段,还有超时等。

下图6描述了TCP的状态变化,同时也标注了导致状态变化的事件、TCP的相关行为,但是没有涉及一些异常条件和一些没有导致TCP状态变化的动作。在后续章节会详细描述TCP对于事件处理细节。

注意:这张图只是一个大概总结,并不是TCP规范的全部。

                              +---------+ ---------\      active OPEN
                              |  CLOSED |            \    -----------
                              +---------+<---------\   \   create TCB
                                |     ^              \   \  snd SYN
                   passive OPEN |     |   CLOSE        \   \
                   ------------ |     | ----------       \   \
                    create TCB  |     | delete TCB         \   \
                                V     |                      \   \
                              +---------+            CLOSE    |    \
                              |  LISTEN |          ---------- |     |
                              +---------+          delete TCB |     |
                   rcv SYN      |     |     SEND              |     |
                  -----------   |     |    -------            |     V
 +---------+      snd SYN,ACK  /       \   snd SYN          +---------+
 |         |<-----------------           ------------------>|         |
 |   SYN   |                    rcv SYN                     |   SYN   |
 |   RCVD  |<-----------------------------------------------|   SENT  |
 |         |                    snd ACK                     |         |
 |         |------------------           -------------------|         |
 +---------+   rcv ACK of SYN  \       /  rcv SYN,ACK       +---------+
   |           --------------   |     |   -----------
   |                  x         |     |     snd ACK
   |                            V     V
   |  CLOSE                   +---------+
   | -------                  |  ESTAB  |
   | snd FIN                  +---------+
   |                   CLOSE    |     |    rcv FIN
   V                  -------   |     |    -------
 +---------+          snd FIN  /       \   snd ACK          +---------+
 |  FIN    |<-----------------           ------------------>|  CLOSE  |
 | WAIT-1  |------------------                              |   WAIT  |
 +---------+          rcv FIN  \                            +---------+
   | rcv ACK of FIN   -------   |                            CLOSE  |
   | --------------   snd ACK   |                           ------- |
   V        x                   V                           snd FIN V
 +---------+                  +---------+                   +---------+
 |FINWAIT-2|                  | CLOSING |                   | LAST-ACK|
 +---------+                  +---------+                   +---------+
   |                rcv ACK of FIN |                 rcv ACK of FIN |
   |  rcv FIN       -------------- |    Timeout=2MSL -------------- |
   |  -------              x       V    ------------        x       V
    \ snd ACK                 +---------+delete TCB         +---------+
     ------------------------>|TIME WAIT|------------------>| CLOSED  |
                              +---------+                   +---------+

                      TCP Connection State Diagram
                               Figure 6.

序列号(Sequence Numbers)

TCP连接发送的每个字节都有一个序列号,这是设计中比较基础的概念。因为每个字节都编号了,所以可以对每个字节进行确认。确认机制具有累计的效果,比如X编号表示X之前的字节都收到了(不包括X)。这种机制可以实现重发情况下的数据去重。数据段中紧随header之后的字节编号最小,之后的字节序号累加。

必须要记住,序列空间是有限的,但是非常大。序列空间从0 to 2^32 - 1。因为序列号空间是有限的,所以所有处理序列号的算法都应该进行对232的取模运算。无符号的算法保留了序列号的关系,因为序列号总是从232到0来回循环往复。计算模运算的时候会有一些细微差距,所以在比较这些序列号时必须格外小心。"=<"表示小于等于(与2^32进行模运算)。

TCP必须包括的序列号比较有:

  • 确定那些对发送出去的数据进行确认的序列号。
  • 确定一个数据段包含的所有序列号都已经被确认(从重发队列里删除数据段)。
  • 确定接收的数据段包含接收方期望的序列号。(比如数据段与接收窗口重叠)

TCP发出去数据后会收到对应的确认。以下几个比较运算被用来处理确认。

SND.UNA = 最久没有确认的数据序列号 SND.NXT = 下一个发送数据的序列号 SEG.ACK = 连接对端期望接收到的下一个序列号 SEG.SEQ = 接收数据段的第一个字节的序列号 SEG.LEN = 数据段中的数据占用的字节数,包括SYN和FIN两个标志位占用的。 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长度的数据段,对于一个数据段,我们有四种接收的可能。

    Segment Receive  Test
    Length  Window
    ------- -------  -------------------------------------------

       0       0     SEG.SEQ = RCV.NXT

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

      >0       0     not acceptable

      >0      >0     RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND
                  or RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND

需要注意的是,当接收窗口大小为0时,除了ack数据段以外其他数据段都不会被接收。所以,TCP可以在窗口为0的情况下继续发送数据和接收确认细腻。然而,即使接收窗口为0,TCP也必须接收带有RST和URG标识的数据段。

我们使用编号方法同样也保护了某些控制信息。隐式地为某些控制信息编号,可以对这些控制信息进行重发和确认防止出现混乱(可以防止对一个控制信息进行多次处理)。控制信息不会占用数据段的数据部分(空间)。所以,我们必须有规则地为控制信息进行隐式编码。SYN和FIN是需要隐式编号的两个控制信息,并且这两个控制信息只用在连接建立和拆除过程。为了编码目的,SYN的编号是在它所处的数据段中第一个字节数据的编号之前,FIN的编号是在它所处的数据段中最后一个字节数据的编号之后。SEG.LEN包括数据字节编号和控制信息编号。当一个数据段有SYN标志时,SEG.SEQ就是SYN的编号。

初始编号的选择 一个TCP连接可以重复使用,规范没有任何限制。一个连接被一对socket确定。连接的新实例被称为连接实体。那么有问题就是,连接如何确定从上一个连接实体发送过来的重复数据段。当连接关闭并且被快速的重新打开时或者由于系统内存不足造成的连接断开然后重新建立时这种问题更为明显。

为了避免冲突,当之前连接实体发送的数据段还在网络上传输时,新的连接实体应该避免使用相同的序列号。即使TCP宕机或者丢失了所有关于之前使用的序列号的信息,我们也要保证不出现冲突。当一个新的连接实体建立时,一个初始序列号(ISN)选择器会用来选择一个新的32的初始编号。选择器使用一个32位的时钟(有可能是虚拟的),这个时钟的低位会以4微妙涨一个的速度增长。所以ISN会大概4.5个小时循环一次。因为我们假定一个数据段在网络中最多存留一个MSL(最大段生命周期)时间段,并且MSG比4.5个小时短很多,所以我们判断,ISN是唯一的。

每一个连接都有一个接收序列号和发送序列号。发送初始序列号由发送数据的TCP模块选择,接收序列号是在连接建立时从对面传过来的。

连接建立时,两端必须要同步对方的初始序列号。双方通过同步带有SYN标志和初始序列号的数据段来实现。所以为了实现上述目的,一般需要一个初始化序列号选择器并且需要一些握手机制来同步初始化序列号。

同步机制需要发送自己的初始化序列号给对方,并且接收对方发送过来的确认信息。连接的两端必须接收对方发送过来的初始序列号并且发回确认信息。

    1) A --> B  SYN my sequence number is X
    2) A <-- B  ACK your sequence number is X
    3) A <-- B  SYN my sequence number is Y
    4) A --> B  ACK your sequence number is Y

因为第二步和第三步可以合并成一步,所以一般称为三次握手。

三次握手步骤是必须的,因为序列号不是在网络空间中全局同步的,并且每个TCP模块都有自己不同的初始化序列号选择机制。接收方收到第一个SYN的时候没有办法确定这个SYN是否是之前连接实例发送的老旧的SYN数据包除非接收方一直保存之前连接使用的最后一个序列号(不是一直都可以记录的。),所以一般接收方需要发送方来检验这个SYN(通过向发送方返回ACK信息)。三次握手和时钟驱动的序列号选择器在【Dalal, Y. and C. Sunshine, "Connection Management in Transport Protocols", Computer Networks, Vol. 2, No. 6, pp. 454-473,December 1978.】中有详细讨论。

知道什么时候保持静默 TCP在丢失以前连接序列号的情况下(宕机后重启)必须保持一个MSL时间段,并且在这个时间段内不创建新的包含序列号的数据段,因为这个序列号可能会与网络上传输的老旧数据段的序列号重复。本规范中MSL采用2分钟。这是一个工程上的选择,根据各方面的经验,可以修改该值。如果TCP重新初始化,并且保留了之前的字节序号状态,这时不需要保持静默,但是必须保证后续的字节序号要比之前的大。(注:如果不存在宕机或者重启等情况,TCP建立新连接是不需要保持2分钟的静默时间的,因为TCP模块会记录之前连接使用的序列号。)

TCP静默时间概念 如果在一个主机组成的计算机网络系统中主机突然宕机了并且没有保存所有处于活跃状态(Closed以外的其他状态)的连接的相关状态信息的情况下,主机重启后需要等待约定的一段时间(MSL)才能开始重新建立连接。以下段落会继续介绍该概念。在网络系统中,可以冒着上个连接的数据被新连接接收或者新数据被当做重复数据丢掉的风险不去等待一段时间。

数据包每次从产生到发送到网络中时都会消耗一定的序号空间。TCP中的重复判断和序号计算算法都依赖于数据段唯一绑定到序列号空间中,并且在网络传输的数据段被确认之前以及这些数据段的副本在从网络中清除掉之前,后续发送的数据序列号不能循环超过2^32。如果没有这个规定,可以想象两个不同的数据段有可能会被赋予相同的序列号或者交叉的序列号,导致接收方没办法区分哪个是新的数据包,哪个是老的数据包。数据段包含的字节越多,该数据段占用的序列号空间就越多。

正常情况下,TCP跟踪发送下一个字节的序号,并且保存需要确认的最早的那个序号,这样可以防止TCP错误使用之前被使用过并且得到确认的序列号再次被使用。这种方式不能保证旧的重复数据从网络上剔除,所以序列空间被设置非常大,来减少因为网络中传输的老重复数据段导致数据传输的问题。在每秒2兆位的网速中,大概需要4.5个小时消耗掉2^32 的序号空间。数据段在网络中的最大存在周期一般不会超过几十秒,这被认为对现有的网络有足够的保护空间,即使网速到了10兆位/s。在100兆位/s的网速中用掉2^32的序号空间需要5.4分钟,虽然比较少,但是依然够用。

在TCP连接的一端,如果TCP模块没有记录上次使用的序列号,那么基本的重复判断和序号算法将会失效。比如当一个TCP模块建立了几个连接,每个连接的初始序号为0,经过宕机然后重启之后,连接有可能会重新创建之前的连接实例(这个时候的连接有可能处于半打开状态),这个链接实例发送的数据序列号有可能与之前连接实例发送的并且在网络中传输中的数据序列号相等或者有重叠。在不了解一个指定的连接之前的序号的情况下,TCP规范规定TCP发送模块应该等待一个MSL时间,来让之前连接实例发送的数据段从网络上排空。

即使主机能记住时间并且可以根据时间选择初始序列号,依然不能免于上述问题。

(注:前面几句话和后面没联系,故删除)现在假设突然宕机了,然后恢复,然后建立了该连接的另个一新的实例,实例的初始化序号是S1=ISN(t)(上一个连接实例使用的最后一个字节序列)。如果恢复得非常快,上一个连接实例发送的在S1周围的数据段(序号大于S1),会被接收方认为是新连接实例的数据。

现在问题是重启后的主机不知道宕机了多久,也不知道网络上是否还有上一个连接实例发送的老数据包。

解决这种问题的办法,就是在从宕机中恢复过来后,在发送数据前静默一段时间。如果主机不担心两个连接实例发送的新老数据包可能造成数据问题的话,可以在发送数据前不用等待一段时间。TCP实现应该为用户提供在宕机重启后是否选择等待这一选项,或者为用户提示性的为所有连接实现静默等待。然而在启动后至少保持MSL这些时间后,再提示用户选择是否等待显得不是那没有必要。

最后总结:每一个发送出去的数据段占用一个或者多个序列号,一个数据段发送之后,数据段的序列号处于使用或者繁忙状态中直到MSL时间过去,当遇到宕机时,连接发送的最后一段数据段会占用一段序列号空间,如果很快地建立了一个新的连接实例并且使用了上一个连接实例使用的数据序列的话,那么将会在接收端造成数据错乱的问题。

建立一个连接(Establishing a connection)

三次握手是建立连接的一个过程。这个过程一般是由一端发起,然后由另一端响应。当两端同时发起连接请求时,三次握手也同样适用。当同时发起连接建立请求时,发送方发出去SYN数据段之后,并没有收到对方对SYN的确认,而是收到对端同时发送过来的SYN数据段。对于接收方来说,有可能会在处于同时建立连接状态时又收到一个老旧的SYN标志数据段。使用reset标志段可以解决这个问题。

以下有几个建立连接的实例。尽管这几个实例没有使用带有用户数据的数据段来做连接同步操作,TCP也需要确保在能保证接收缓冲的数据都是有效的时候才会把数据传递到上层应用(只有把数据缓冲到连接建立阶段,数据才是有效的)。三次握手可以减少假连接的发生概率。这是协议实现在内存和消息检验之间做的权衡。

最简单的三次握手过程如下图。图的具体解释如下。每行都有序号为了方便引用描述。右箭头表示TCP数据段从A发送到B,左箭头则相反。省略号(...)表示数据段依然在网络中传输。"XXX"表示数据段丢失或者被拒绝接收。TCP状态表示离开或者接收到某些数据段(中间横线表示数据段的内容)之后触发的。数据段同时带有序列号、控制字段、ACK字段。为了明确起见,其余的字段比如窗口大小、地址、长度、文本什么的没有描述出来。

      TCP A                                                TCP B

  1.  CLOSED                                               LISTEN

  2.  SYN-SENT    --> <SEQ=100><CTL=SYN>               --> SYN-RECEIVED

  3.  ESTABLISHED <-- <SEQ=300><ACK=101><CTL=SYN,ACK>  <-- SYN-RECEIVED

  4.  ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK>       --> ESTABLISHED

  5.  ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK><DATA> --> ESTABLISHED

          Basic 3-Way Handshake for Connection Synchronization

                                Figure 7.

第二行表示A使用序号100发送数据。第三行B发送了一个SYN标志,同时对A发送的SYN做了一个反馈。B对A的反馈中期望下一个接收的序列号是101,这个SYN占用了100这个序列号。第四行A使用空的数据段对B的SYN做了一个反馈。第五行,A发送了一些数据。注意第五行序列号与第四行序列号是一样的,因为ACK数据段不占用序号空间(如果ACK占用序号空间,那我们也要对ACK标志位做ACK,这样会一直处于往返确认的循环中)。

同时建立稍微有些复杂,如下图。每个TCP的状态从CLOSED 变化到 SYN-SENT 再到 SYN-RECEIVED 再到ESTABLISHED.

      TCP A                                            TCP B

  1.  CLOSED                                           CLOSED

  2.  SYN-SENT     --> <SEQ=100><CTL=SYN>              ...

  3.  SYN-RECEIVED <-- <SEQ=300><CTL=SYN>              <-- SYN-SENT

  4.               ... <SEQ=100><CTL=SYN>              --> SYN-RECEIVED

  5.  SYN-RECEIVED --> <SEQ=100><ACK=301><CTL=SYN,ACK> ...

  6.  ESTABLISHED  <-- <SEQ=300><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED

  7.               ... <SEQ=101><ACK=301><CTL=ACK>     --> ESTABLISHED

                Simultaneous Connection Synchronization

                               Figure 8.

使用三次握手的主要原因是可以防止上一个连接实例发送的老旧的连接建立标志会对现在进行的连接初始化造成影响。为了防止这种老旧包对当前连接建立造成影响,引入了reset控制包。如果接收TCP(发送方处理逻辑请看图9)处于一个非同步的状态(比如SYN-SENT, SYN-RECEIVED)时接收到了一个reset控制包,他会返回到LISTEN状态。如果TCP处于已经同步状态(ESTABLISHED, FIN-WAIT-1, FIN-WAIT-2, CLOSE-WAIT, CLOSING, LAST-ACK, TIME-WAIT)他会拆除当前连接,并且通知上层连接用户(java中的connection reset by peer异常)。我们接下来讨论当连接处于半打开情况下,出现这种异常的场景。

      TCP A                                                TCP B

  1.  CLOSED                                               LISTEN

  2.  SYN-SENT    --> <SEQ=100><CTL=SYN>               ...

  3.  (duplicate) ... <SEQ=90><CTL=SYN>               --> SYN-RECEIVED

  4.  SYN-SENT    <-- <SEQ=300><ACK=91><CTL=SYN,ACK>  <-- SYN-RECEIVED

  5.  SYN-SENT    --> <SEQ=91><CTL=RST>               --> LISTEN


  6.              ... <SEQ=100><CTL=SYN>               --> SYN-RECEIVED

  7.  SYN-SENT    <-- <SEQ=400><ACK=101><CTL=SYN,ACK>  <-- SYN-RECEIVED

  8.  ESTABLISHED --> <SEQ=101><ACK=401><CTL=ACK>      --> ESTABLISHED

                    Recovery from Old Duplicate SYN

                               Figure 9.

图9简单描述了从重复老旧包中恢复的过程。第三行,之前连接实例的老包到达了B。B没法确认这是新包还是老包,所以它正常的对这个包进行了回馈。A收到回馈包后发现进行确认的序列号不对,所以他向B发送了一个reset控制包,这个控制包还是使用了91这个老包的序列号。B收到reset控制包之后重新进入LISTEN状态。当新的SYN(100)包到达B时,连接正常建立。如果第六行的SYN包早于RST到达B的话,情况会有些复杂。

半打开连接和其他异常状态状态的连接 连接的一端已经关闭连接并且没有通知对方或者由于突然宕机导致连接的一端关于TCP状态的记录都丢失,这种连接被称为半打开连接。如果它试图向对方发送数据,这种连接会被重置。

如果连接在A端的状态已经不存在,那么在B端的应用试图给A端发送数据的时候会收到A端发送过来的RESET控制消息。这个消息通知B,该连接出了某些问题,可以拆除该链接。

假设A和B正在通过TCP通讯,之后由于A宕机,导致A关于连接的状态都丢失了。有些系统有故障恢复机制,这个取决于A使用的TCP模块所在的操作系统实现。当A主机重启后,A仿佛重新开始,或者从某个恢复点恢复过来。所以A试图重新建立连接,或者在他认为已经建立的连接上发送数据。在第二种情况下,他会收到TCP模块的连接已经关闭的通知。第一种情况下,A试图发送SYN数据包。这种情况会在图十中描述。

      TCP A                                           TCP B

  1.  (CRASH)                               (send 300,receive 100)

  2.  CLOSED                                           ESTABLISHED

  3.  SYN-SENT --> <SEQ=400><CTL=SYN>              --> (??)

  4.  (!!)     <-- <SEQ=300><ACK=100><CTL=ACK>     <-- ESTABLISHED

  5.  SYN-SENT --> <SEQ=100><CTL=RST>              --> (Abort!!)

  6.  SYN-SENT                                         CLOSED

  7.  SYN-SENT --> <SEQ=400><CTL=SYN>              -->

                     Half-Open Connection Discovery

                               Figure 10.

当第三行的SYN到达B时,B处于同步状态,并且SYN携带的序列号超出了B的窗口范围,所以B返回了100,通知A希望下次发送100的序号。A收到100的反馈后发现,并不是自己曾经发送过的序列号,数据不同步,所以发送reset控制到到B。B在第五行关掉连接。A还是尝试与B建立连接。这又回到了图7描述的三次握手过程上。

当A宕机并且B还认为连接是同步的并且打算发送数据时,情况会比较有意思。这在图11有描述。在这种情况下A不会接收B发送过来的数据,因为A已经不存在之前的连接了,所以A发送了一个RST控制包。B收到RST控制包后重置了连接。

        TCP A                                              TCP B

  1.  (CRASH)                                   (send 300,receive 100)

  2.  (??)    <-- <SEQ=300><ACK=100><DATA=10><CTL=ACK> <-- ESTABLISHED

  3.          --> <SEQ=100><CTL=RST>                   --> (ABORT!!)

           Active Side Causes Half-Open Connection Discovery

                               Figure 11.

图12描述了A和B都处于被动打开状态(LISTEN)。上一个连接实例的SYN数据包到达了B,B进行了状态转换。B对A发送了一个对老包的确认。A收到确认后,向B发送了一个RST控制包,B重新进入监听状态。

      TCP A                                         TCP B

  1.  LISTEN                                        LISTEN

  2.       ... <SEQ=Z><CTL=SYN>                -->  SYN-RECEIVED

  3.  (??) <-- <SEQ=X><ACK=Z+1><CTL=SYN,ACK>   <--  SYN-RECEIVED

  4.       --> <SEQ=Z+1><CTL=RST>              -->  (return to LISTEN!)

  5.  LISTEN                                        LISTEN

       Old Duplicate SYN Initiates a Reset on two Passive Sockets

                               Figure 12.

还有很多其他情况也可能发生,RST的产生和处理情况准守一下规则:

重置的产生(Reset Generation)

通常情况下如果收到一个不属于本连接实例的数据段的话都要产生并发送重置控制包。如果不确定这个包是不是这个连接的,不能发送重置控制包。

有三种状态:

  1. 当连接不存在时除了reset控制包之外的其他数据包发送过来时,都应该向对端发送一个重置控制包。特别的,如果SYN试图与一个不存在的TCP模块建立连接,也应该通过发送重置控制包通知对方。如果接收到的数据段有ACK标志,重置数据包使用ACK的序列号,否则重置数据包使用序列号0,并且ACK字段设置为0与数据段长度的和。最后连接还是保持关闭状态。
  2. 如果连接处于非同步状态(LISTEN,SYN-SENT,SYN-RECEIVED)收到了未曾发送过的数据段的确认信息或者收到的数据段的安全等级、隔离级别与当前连接要求的不符合,接收端将会向发送端发送一个重置消息。如果我们发送的SYN还没有得到确认(同时打开的情况下)并且收到的数据段的优先级比本地的优先级高,那我们要么升级(如果用户或者系统允许的话,这里TCP应该会提供相应的控制接口)本地的优先级要么发送reset控制包到对端;或者如果接收的数据包的优先级比本地端要求的低,我们会继续处理(如果发送方不能把自己的优先级提升到与我们本地一样的优先级的话,连接会被关闭,这个是通过后续的数据段来检测到的)。如果我们的SYN已经被确认了,那么接收到的数据包的优先级必须与我们本地的优先级一样,否则会向对端发送reset控制包。如果接收到的数据段有ACK标志,重置数据包使用ACK的序列号,否则重置数据包使用序列号0,并且ACK字段设置为0与数据段长度的和。最后连接还是保持关闭状态。
  3. 如果连接已经处于同步状态(ESTABLISHED, FIN-WAIT-1, FIN-WAIT-2, CLOSE-WAIT, CLOSING, LAST-ACK, TIME-WAIT)并且收到不可接收的数据段(超过接收窗口大小或者不可接收的确认---没有发送过的数据包的确认,通过SND.UNA与SND.NXT确定)的时候,需要发送一个带有SND.NXT序列号的空数据包与一个期望下次收到的序列号的确认包,TCP保持现有状态。如果数据包包括安全级别、隔离、优先级,但是不满足本地连接对安全级别、隔离、优先级的要求,本地会关闭连接并进入到关闭状态,并发送RESET控制包,RESET控制包使用发送过来的确认字段中的序列号。

RESET处理(Reset Processing) 除SYN-SENT以外的其他状态,reset包都是通过SEQ字段来检查是否是正常的。如果reset包的序列号在窗口内,那么它就是合法的。在SYN-SENT状态中,如果接收到了对本地之前发送的SYN的确认的话,收到的reset包就是正常的。

接收方收到reset包之后先检查是不是正常的(在接收窗口内),如果是正常的然后改变状态。如果连接处于LISTEN状态,忽略这个reset包。如果连接处于SYN-RECEIVED状态并且之前是处于LISTEN状态的话,连接重新进入LISTEN状态,否则的话连接关闭连接并且进入CLOSED状态。如果连接处于其他状态,直接关闭连接并且通知上层用户进程。

关闭连接

CLOSE操作意味着我没有数据需要发送了。对于全双工的连接来说,CLOSE操作可能显得比较模糊,并且没有对对端做任何的说明。执行CLOSE操作的一端仍然可以继续读取数据,除非对端也停止发送数据。所以一个程序可以发送多个send指令最后跟着一个close,然后等待对面发送数据过来直到对面停止发送数据。当对面没有数据发送的时候,我们可以通知用户,让用户可以优雅的关闭连接。TCP在连接关闭前会可靠地把数据分发出去,如果一个用户不需要新数据了,那么他可以监听连接直到收到对面关闭连接的通知(-1),这样可以确保自己发送的数据对面都已经接收到了。用户发送完数据并且关闭连接后,需要一直读取连接传递上来的数据直到收到连接关闭的通知(-1)。

有三个关闭场景:

  1. 本地用户进程主动关闭连接

把位于等待发送队列中最后一个的数据段打上FIN标志。本地TCP不再接收用户发送的数据,并且本地连接进入FIN-WAIT-1状态。这个状态中的连接还可以继续接收数据。发送出去的数据包括FIN会一直重试发送直到收到确认。当连接对端收到所有数据并且为刚才发送的FIN返回了一个确认,并且也发送一个FIN,本地连接会确认对端发送的FIN。注意:收到对面的FIN指令后会对这个FIN做ACK,但是本地的FIN需要等待本地用户调用CLOSE方法来触发发送。

  1. 远端用户进程发送FIN指令关闭连接

收到FIN数据后,TCP会通知本地用户连接对端停止发送数据。用户会调用CLOSE方法来关闭本地连接,TCP把本地数据都发送到对端之后,发送FIN数据到对端。等收到FIN的确认信息后,本地连接被关闭并删除。如果发送出去的FIN很久没有得到确认,超过用户设置的一段时间后,连接被关闭。

  1. 两端用户进程同时关闭连接

连接两段同时关闭连接,两段会同时发送FIN控制包。当FIN控制包之前的数据都被处理和确认后,TCP可以对FIN控制包进行确认。两段收到ACK后,关闭连接。

      TCP A                                                TCP B

  1.  ESTABLISHED                                          ESTABLISHED

  2.  (Close)
      FIN-WAIT-1  --> <SEQ=100><ACK=300><CTL=FIN,ACK>  --> CLOSE-WAIT

  3.  FIN-WAIT-2  <-- <SEQ=300><ACK=101><CTL=ACK>      <-- CLOSE-WAIT

  4.                                                       (Close)
      TIME-WAIT   <-- <SEQ=300><ACK=101><CTL=FIN,ACK>  <-- LAST-ACK

  5.  TIME-WAIT   --> <SEQ=101><ACK=301><CTL=ACK>      --> CLOSED

  6.  (2 MSL)
      CLOSED

                         Normal Close Sequence

                               Figure 13.



      TCP A                                                TCP B

  1.  ESTABLISHED                                          ESTABLISHED

  2.  (Close)                                              (Close)
      FIN-WAIT-1  --> <SEQ=100><ACK=300><CTL=FIN,ACK>  ... FIN-WAIT-1
                  <-- <SEQ=300><ACK=100><CTL=FIN,ACK>  <--
                  ... <SEQ=100><ACK=300><CTL=FIN,ACK>  -->

  3.  CLOSING     --> <SEQ=101><ACK=301><CTL=ACK>      ... CLOSING
                  <-- <SEQ=301><ACK=101><CTL=ACK>      <--
                  ... <SEQ=101><ACK=301><CTL=ACK>      -->

  4.  TIME-WAIT                                            TIME-WAIT
      (2 MSL)                                              (2 MSL)
      CLOSED                                               CLOSED

                      Simultaneous Close Sequence

                               Figure 14.

优先级和安全

两个端口必须有相同的隔离级别和安全才能建立连接,并且连接的优先级要高于或者等于两个端口的优先级。

TCP使用的优先级和安全参数就是IP中定义的相关参数。本协议规范中的安全/隔离都是指IP中的安全相关的参数,包括安全、隔离、用户组和相关限制。

如果一个带有不相符的安全/隔离连接请求发送过来,或者优先级比较低,应该拒绝这个连接请求。应该在发送SYN之后收到对面的ACK时(给发送方一个提升自己优先级的机会),发现连接请求的优先级还是低的时候,需要拒绝这个链接。

需要注意的是,即使是TCP使用默认的安全配置,在接收到数据包时仍然需要检查数据包的优先级,并且在必要的时候需要试着提升自己的优先级。

即使是在非安全环境中,安全参数也有可能会使用(值可能表示数据是不需要加密的),所以当非安全环境中的主机TCP模块收到用户的安全参数时仍然需要接收这些参数,但是不需要把这些参数发送出去。

数据交流

连接一旦建立,数据就可以通过数据段的方式在连接上传递。因为数据段有可能会丢失或者有可能会校验出错,或者网络拥塞,TCP通过重发机制(超时)保证每个数据段都可以可靠传递。因为网络问题或者重传机制,重复的数据段有可能会被接收到。在序列号章节已经讨论过,TCP通过对接收数据段的序列号和确认序列号进行测试,来确定接收的数据段是不是可以接收的。

发送端通过SND.NXT变量来跟踪下一个发送的数据使用的序列号。接收端通过RCV.NXT变量跟踪下一个期望收到的数据使用的序列号。发送端使用SND.UNA变量跟踪最久发送的但是没有收到确认的序列号。如果数据通道短暂的空闲,并且发送出去的数据都得到确认了,上述三个变量应该是相等的。

当发送端将数据发送出去,发送端会增加SND.NXT变量的值。当接收端收到数据的时候它会增加RCV.NXT变量,并且会发送确认包。当发送方收到数据的确认信息后,他会增加SND.UNA变量的值。这三个值的差值可以看作是对通讯延迟的一个基本衡量。这三个变量的变化量是发送数据段的长度。注意,连接建立状态下,所有的数据段都应该包含当前的确认信息。

用户的CLOSE函数调用类似一个推的过程,FIN控制标志包含有PUSH标志的功能。

重发超时 因为组成互连网络的各种网络与使用TCP的用户之多,导致超时时间需要动态的确认。下面介绍一种计算超时时间的方法。

从发送一个带有序列号的字节出去到收到对这个字节序列的确认信息(确认包确认的序列号不必与发送数据的序列号相等)的时间叫做RTT。接下来计算平滑的SRTT:

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

基于上述公式,计算超时时间:

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

UBOUND是超时时间的上限,LBOUND是下限,ALPHA是平滑因子(e.g., .8 to .9),BETA 是延迟变化因子 (e.g., 1.3 to 2.0)。

紧急信息的传递

TCP紧急机制可以允许发送方让接收方接收某些紧急数据,并且允许接收方确定什么时候把紧急数据都接收完成(紧急数据边界)。这个机制允许在数据流中放一个指针来表示紧急数据的结尾。当前数据段如果有紧急指针,并且有紧急标志,那么TCP会通知用户进入紧急模式;当处理到紧急指针时,TCP通知用户进入紧急模式。当用户处于紧急模式时,紧急指针被更新了,用户是不会被通知的。

每个数据段都有紧急指针字段,紧急标志表明TCP必须把紧急指针字段的值与当前段序列号相加获得紧急指针的具体值。如果没有紧急标志的话,表明当前段没有紧急数据。

发送紧急数据最少也要一个字节。如果紧急数据段同时也标志了PUSH标志,那么数据会被尽快发送到对端用户进程。

管理窗口

窗口是发送方用来表明它还可以接收的数据序列号范围。这个窗口基本就可以认为是接收方的接收缓存大小。

大的窗口可以加快数据传输。如果传送过来的数据多于窗口大小,多余的数据会被丢弃。这会导致不必要的数据重传,增大网络负荷。过小的窗口会产生更多的传输时延。

这个机制允许一开始使用大的窗口,随后如果没有更多缓存空间的话,可以慢慢减小窗口尺寸。这被称作窗口缩小。可靠性规则制定,TCP不会主动减小窗口的大小,而是要根据对面TCP的情况来改变窗口大小。

即使发送窗口为0,TCP发送模块在收到用户传过来的数据后,也要发送最少一个字节。即使接收窗口为0,发送方也要定时发送数据到接收方。当接收窗口为0时,发送方的数据重试间隔为2分钟。当任一个TCP接收窗口为0时,定时发送数据会把接收方重新打开的窗口通知到发送方,这样是很有必要的。

当接收窗口为0时,如果收到了数据,也应该返回一个回馈来表示下一个期望收到的字节序列号。

为了适应接收窗口的大小,TCP把需要发送的数据打包成窗口大小,在重新发送队列中有可能会重新打包。这种重新打包不是必须的,但是也许很有帮助。

在一个单向的数据流连接中,确认信息中有相关窗口大小,这个确认信息使用相同的序列号,这样在多个确认包乱序到达时TCP没办法对这些确认包进行重新排序。这不是很严重的问题,但是这样会导致窗口会根据老确认包中的窗口大小来发送数据。这个问题的改善方法就是,对于收到接收方的确认包中,他确认的序列号必须比上一次处理的确认序列号大,发送方才会调整发送窗口。

窗口的管理对通讯性能有很大的影响。下面是对TCP实现的几个建议。

  1. 过小的窗口会让数据通过很小的数据段发送,如果网络性能提高,可以稍微增大窗口的大小。
  2. 接收方可以延迟更新窗口大小,当窗口大小可以达到最大窗口大小的一定比例后再更新(比例有可能是20%或者是40%)。
  3. 对于发送方的一个建议就是为了防止发送小数据包,可以等窗口到达一定大小后再发送数据。当用户调用PUSH函数时,TCP应该立即发送数据,不管是多小的包。
  4. 确认包不应该被延迟发送,否则对端会因超时重新发送数据。当收到小数据包,发送确认包时不更新窗口大小,等到窗口变大时,再通过确认信息更新窗口大小。
  5. 探测接收方窗口大小的数据包有可能会被截断成更小的数据包。如果一个字节的数据包被接收方接收了,那么这个字节会占用刚开始释放的窗口的一个字节的空间。当窗口不在是0时,发送方尽可能发送多的数据出去,那么这个数据有可能会被打包成很多大的或者小的数据包。随着时间推移,接收方会短暂停止分配更多的窗口空间,导致大的数据包会被切分成更小的数据包。时间久了数据会通过很多小数据包来传输。

这里给TCP实现的建议就是尽可能的将小窗口合并成大窗口。因为一些简单的TCP实现总是会分配一些小窗口。

接口(Interfaces)

有两种接口:用户进程到TCP的接口,TCP到更底层服务的接口。我们对用户进程到TCP之间的接口有详细的定义,但是TCP到底层协议之间的接口这里没有表述,因为这个是需要底层协议进行定义。这里底层协议就是IP,这里我们只涉及TCP使用的几个IP参数。

用户与TCP之间的接口

以下用户使用的TCP函数都是虚构的,因为每个操作系统的实现都不尽相同。所以,我们必须提示读者,每个TCP实现有可能有不同的TCP接口。但是所有的TCP实现都应该提供最小的一个服务子集,这个子集可以维持TCP的运行。这部分描述了所有TCP实现应该实现的功能接口。

用户命令

接下来的章节会功能性的描述每个用户/TCP接口。每个接口的名字和大多数高级语言中的函数方法名字一样,这种命名方法并不是排除一些自陷类型的服务调用(比如SVCs,UUOs,EMTs)。

下面的用户命令指定了TCP用于支持通讯所需的最基本的操作。不同的实现可能定义不同的方法格式,并且它们有可能会合并一些基础方法为一个方法,或者把一些基础方法分解成多个方法。特别的,用户可能希望当他们在对一个连接进行第一次的SEND 或者 RECEIVE时,TCP会自动打开这个连接。在提供通讯功能时候,TCP实现不能只接收用户请求,也同时给它服务的进程返回相关信息。相关信息包括:

  1. 连接的基本信息。(中断,远程关闭,绑定一个未指定的远程主机)
  2. 对于用户的方法调用给予反馈,比如成功或者各种类型的失败。

Open

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

我们假设TCP知道他服务的进程身份,并且进程在使用连接的时候TCP会检查进程有没有相应的权限。根据TCP实现的不同,连接的源地址的与端口是由TCP与IP来提供的。这些都是基于安全考虑,这样的话,就不会出现一个TCP主机装扮成另一个TCP主机。所以一个进程就不能通过与TCP合作来装扮成另一个进程。

当主动/被动标志被设置为被动时,相当于调用LISTEN方法,来等待连接。被动连接有可能会指定特定的外部主机地址来等待特定主机的连接,或者不指定主机地址,可以接收所有的主机发起连接的请求。指定了特定远端主机的连接通过SEND指令可以转变成主动连接。

一个TCB(传输控制块)数据结构被创建并且使用OPEN命令传过来的参数来填充部分字段的值。

主动的OPEN调用会立即启动连接建立(同步)步骤。

用户提交到TCP的数据如果在指定的超时时间段内没有发送到目的主机的话,TCP会关闭这个链接。这个全局的超时时间配置是5分钟。

TCP或者操作系统的某些组件会检查用户是否有权限设置打开一个指定了优先级或者安全/隔离级别参数的连接。如果这些参数没有设置,参数默认值变会指定。

TCP只会接收安全/隔离级别与OPEN设置相等的并且安全等级比OPEN设置的安全等级相等或者高的数据段。

连接的优先级取OPEN参数中指定的值与接收到的数据(SYN或者ACK中的)中优先级之间最大的那个值,并且这个优先级在之后的连接生命周期中一直固定不变。TCP实现也许允许用户控制优先级协商。比如,用户可以指定只能优先级相等,或者在提升优先级等级时必须得到用户的确认。

TCP会把本地连接的名字返回给用户。这个连接名字可以被认为是由<本地 socket, 外部 socket>指定的连接的别名。

Send

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

这个调用会把用户指定缓存中的数据通过指定的连接发送出去(未必真的发送)。如果连接没有打开,SEND方法可能会触发报错异常。一些TCP实现也许允许先执行SEND命令然后自动打开连接。如果用户进程没有使用这个连接的权限则会报错。

如果PUSH标志被设置了,数据会马上传送到接收方,并且在创建的最后的数据段中会设置PUSH标志。如果PUSH没有设置,那么数据有可能会等待下次的SEND调用然后合并在一起再发送。

如果URGENT紧急标志设置了,发送到目的地的数据段都会设置紧急指针字段值。如果紧急指针之前的紧急数据都没有被用户进程处理的话,TCP会把紧急条件报告给用户进程。紧急标志就是为了让接收方尽快处理相关数据,同时也可以标记什么时候紧急数据都被接收方收到了。发送方指定的紧急数据的次数与接收方收到的紧急数据通知的次数可能不一样。

如果在OPEN函数调用中没有指定外部地址,但是连接已经建立了(LISTEN建立的连接),一个指定的buffer窗口大小会被发送到连接的另一端。用户使用OPEN时没指定外部地址的话,在使用SEND时可以不用了解连接另一端的地址。

然而,在连接另一端没有确定前,用户调用SEND会报错。用户可以通过STATUS函数查看链接状态。在某些TCP实现中绑定一个未指定的外部地址会通知用户。

当SEND提供超时时间时,连接的超时时间会被这个时间替换。

在最简单的TCP实现中,调用SEND会一直阻塞,直到数据发送完成或者超时。但是这种简单实现会导致死锁(比如连接两端都发送数据而不接收数据)并且性能也不高,所以不推荐。稍微复杂的实现会让SEND方法立即返回,这样可以让用户进程与网络io同时进行,况且也可以调用多次SEND函数。SEND函数调用遵守先来后到准则,所以如果TCP来不及处理这些数据,会把这些请求数据放入队列中。

我们隐式的引出了异步的用户接口,SEND调用后,底层TCP模块会通过信号或者模拟中断来通知用户进程。一个可选的实现就是立即返回一个响应给调用进程。比如,SEND会立即返回一个本地确认,即使发送出去的数据还没有被远端TCP接收到。我们可以乐观的认为最终会成功。如果我们错了,连接会因为超时断开。在这种同步的调用实现中,也会有一些信号通知,只不过这些通知,但是这些通知是与连接相关的并不是关于数据收发的。

为了分别处理多个SEND发送之后的结果(成功或者失败),每个SEND调用都应该返回对应的响应状态码与存放接收数据缓存的内存地址。TCP到用户的接口会在下文讨论,主要讨论函数调动应该返回什么的信息给用户进程。

Receive

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

这个命令会为相关连接分配一个接收数据的缓存。如果在这个命令前没有执行OPEN命令或者进程没有相关权限,那么会抛出异常。

在最简单的实现里,调用这个函数的进程会一直阻塞,直到接收缓存(进程传递的缓存不是TCP的接收缓存)填满或者某些异常出现,但是这种实现方式会导致死锁。复杂的实现版本允许多个RECEIVE调用。这些RECEIVE的接收缓存会在数据段到达时被填充。精心设计的系统(有可能是异步系统)可以在收到PUSH标志或者RECEIVE接收缓冲被填满时通知调用进程,这样大大提升了系统吞吐量。

如果在收到PUSH标志前,接收到的数据已经可以填满RECEIVE接收缓冲,那么RECEIVE方法调用会返回,但是PUSH标志不会被标记。RECEIVE接收缓存会尽可能的被填充,但是如果在没被填满前收到了PUSH标志,那么数据会被立即返回,并且会设置PUSH标记。

如果有紧急数据到达,那么TCP会通过TCP-to-user信号通知用户进程。用户进程之后进入紧急模式。如果URGENT标志开启,需要等待更多的紧急数据。如果URGENT标志关闭,RECEIVE会返回接收到的所有紧急数据,并且用户进程进入正常模式。需要注意的是,紧急数据不能与非紧急数据处于同一个RECEIVE接收缓存中,除非有标志来明确这两种数据的边界。

为了区分多个正在等待接收数据的RECEIVE调用,同时也考虑有些接收缓存不会被填满,在调用返回时会跟着一个接收缓存的地址,以及读取的字节数量。

其他TCP实现,可能允许进程调用RECEIVE时使用TCP的内部接收缓存,或者TCP与用户进程共享一个环形缓存。

Close

格式: CLOSE (local connection name)

这个命令会将指定的连接关闭。如果连接没有打开,或者进程没有相关权限,异常会被返回。关闭命令是一个优雅的操作,当多个SEND命令发送的数据在传输中(或者重传)并且流量控制允许,这些数据会在成功发送到对面后,连接才会关闭。所以,可以在多个SEND命令后跟一个CLOSE命令,这样也会保证发送的数据会到达目的地。需要注意的是,即使连接处于半关闭状态,但是进程仍然可以通过RECEIVE命令收数据,因为对面进程仍然会发送数据。所以Close命令是我不想发送数据了,但是不代表我不想接收数据了。在超时前,关闭连接的一方有可能不能抛弃剩余的数据。在这种场景下Close命令可以转成ABORT命令,关闭连接。

用户可以在任何时间主动关闭连接,有可能会因为TCP的某些提示(远程关闭,重发超时,远端不可达)关闭连接。

因为关闭连接需要与对端进行通讯,所以关闭连接的一方会短暂地以处于closing状态。在TCP还没有对close做出响应前重新打开连接会报错。

Close有着与push一样的语义。

Status

格式:STATUS (local connection name) -> status data

不同的TCP实现,这个命令可能不一样,没有这个指令对整体TCP接口并没有影响。状态信息一般是从连接关联的TCB查询获得的。

该命令返回包含如下信息的数据块:

  1. 本地socket
  2. 外部socket
  3. 本地连接名称
  4. 接收窗口
  5. 发送窗口
  6. 连接状态
  7. 等待确认的缓存数量
  8. 等待接收的缓存数量
  9. 紧急状态
  10. 优先级
  11. 安全/隔离
  12. 超时时间

根据连接状态以及实现,某些字段可能没有或者没有任何意义。如果进程没有权限调用这个命令,则会返回一个异常,这样会防止没有授权的进程获取相关连接的状态。

Abort

格式:ABORT (local connection name)

这个命令会丢弃所有等待发送的数据和等待接收的数据,同时会移除TCB控制块,并且向对端发送一个RESET消息。根据实现的不同,用户进程可能会收到每个发送或者接收数据块的删除通知,或者只会收到一个ABORT确认。

TCP-to-User Messages

假定操作系统提供了TCP异步通知用户进程的方式。当TCP通知用户进程时,相关数据会传递给用户进程。本协议一般指异常消息。在其他情况下,有可能会通知关于SEND,RECEIVE或者其他命令的处理返回结果。

一般包含一下信息:

  1. 本地连接名称 经常
  2. 相应字符串 经常
  3. 缓存地址 发送或者接收
  4. 接收的字节数 接收
  5. PUSH标志 接收
  6. 紧急标志 接收

TCP/Lower-Level Interface

TCP实际上通过调用底层的协议模块来在网络上实现发送和接收数据。一个例子就是ARPA网中,底层模块是IP协议。 当底层协议是IP时,它提供服务类型参数和数据包存活时间选项。TCP使用如下参数来对接ip的这些参数:

      Type of Service = Precedence: routine, Delay: normal, Throughput:
      normal, Reliability: normal; or 00000000.

      Time to Live    = one minute, or 00111100.
      
      最大的数据段生命周期是2分钟。我们明确如果数据在一分钟内不能分发出去,便把这个数据段丢弃。

如果底层协议是IP(其他也能提供类似特性的协议)并且源地址路由( Source Routing_achejq的专栏-CSDN博客 )被使用,TCP提供的接口必须提供可以操作此类信息的接口。这个功能非常重要,这样TCP就可以获取源地址与目的地地址来计算校验和(头部的伪地址)。这个也可以保留请求的返回路由信息。

任何底层的协议必须提供源地址、目的地址、协议字段、检测TCP数据长度的方式,这些可以提供与ip一样的服务并且可以用来计算TCP的校验和。

事件处理(Event Processing)

这个章节描述的处理过程只是一个可能实现的示例。其他的实现可能有不同的处理序列,但是他们同本章描述的只有细微的差别,而不应该有很大的实质性的差别。

TCP的活动可以简单描述成对事件的响应。事件可以被分成三类:用户调用、数据段到达和超时。本章描述TCP处理这些事件的过程。在很多场景中,TCP的处理流程依赖当前连接所处的状态。

    Events that occur:
      User Calls
        OPEN
        SEND
        RECEIVE
        CLOSE
        ABORT
        STATUS
      Arriving Segments
        SEGMENT ARRIVES
      Timeouts
        USER TIMEOUT
        RETRANSMISSION TIMEOUT
        TIME-WAIT TIMEOUT

TCP/user接口模型是指用户命令会立即返回,并且延迟一段时间后会得到相应结果。在下文,‘signal’是指延迟获得结果。异常响应被描述为一段字符串。比如用户引用了不存在的连接会得到:error:connection not open这个响应。

在下文描述的针对序列号、确认序列号、窗口等等的运算都是基于232这个序列号空间做的取模。同样=<意思是等于或者小于某个数值(依旧是对232做取模运算后的)。

处理到达数据包的第一步就是检验序列号(比如数据段中的序列号位于接收窗口中),然后被放入队列中,然后通过序列号排序。

如果我们收到的数据段与之前已经收到的数据段有一部分重叠,那么我们重新构造这个数据段,只包含新的数据,然后重新组织头部信息来保持一致。

注意,如果没有提及连接状态变更,那么连接一直保持这个状态不变。

OPEN Call

CLOSED STATE (i.e., TCB does not exist)
        创建一个新的TCB来保存连接的状态,保存本地socket标识符(变量引用)、外部socket地址、优先级、安全/隔离、以及超时相关的信息。
        注意,被动打开的连接在没有指定外部socket地址,TCB中的相关信息需要等待外部进程进行SYN同步的时候进行填充。检查这个用户被允许的安全与优先级,
        如果不符合则返回“error: precedence not allowed”或者返回“error:  security/compartment not allowed.”如果是被动打开的话,连接进入LISTEN状态并且返回。
        如果是主动打开,但是没有指定外部socket地址,返回“error:foreign socket          unspecified”,如果主动打开,并且指定了外部socket地址,发送一个SYN数据包。
        选择一个初始化序列号,一个<SEQ=ISS><CTL=SYN>这样的格式的数据包被发送。把SND.UNA 值设置为初始化序列号,SND.NXT值设置为初始化序列号+1,进入SYN-SENT状态并返回。
        如果用户进程没有创建连接的权限,返回“error:  connection illegal for this process”。如果没有足够的内存空间来创建连接,返回“error:  insufficient resources”。
LISTEN状态
        如果打开时模式是主动模式,并且外地socket地址指定了,那么连接状态从被动转成主动,并选择一个初始化序列号。
        发送一个SYN数据包,把SND.UNA 值设置为初始化序列号,SND.NXT值设置为初始化序列号+1,进入SYN-SENT状态。与SEND命令发送的数据可能与SYN数据包一起发送,也有可能缓存起来等到连接进入ESTABLISHED时再发送。
        如果SEND命令设置了紧急标志,那么这个标志必须与数据包一起发送出去。如果没有内存空间来缓存发送的数据,返回“error:  insufficient resources”。
        如果外部地址没有指定那么返回“error:  foreign socket unspecified”。

SYN-SENT STATE
SYN-RECEIVED STATE
ESTABLISHED STATE
FIN-WAIT-1 STATE
FIN-WAIT-2 STATE
CLOSE-WAIT STATE
CLOSING STATE
LAST-ACK STATE
TIME-WAIT STATE
        返回:error:  connection already exists

SEND Call

CLOSED STATE (i.e., TCB does not exist)
        如果用户没有权限访问此链接,返回“error:  connection illegal for this process”。
        否则返回:“error:  connection does not exist”。
LISTEN STATE
        如果指定了外部socket地址,那么连接状态从被动转成主动,并选择一个初始化序列号。
        发送一个SYN数据包,把SND.UNA 值设置为初始化序列号,SND.NXT值设置为初始化序列号+1,进入SYN-SENT状态。与SEND命令发送的数据可能与SYN数据包一起发送,也有可能缓存起来等到连接进入ESTABLISHED时再发送。
        如果SEND命令设置了紧急标志,那么这个标志必须与数据包一起发送出去。如果没有内存空间来缓存发送的数据,返回“error:  insufficient resources”。
        如果外部地址没有指定那么返回“error:  foreign socket unspecified”。
SYN-SENT STATE
SYN-RECEIVED STATE
        保存数据,等到状态变成ESTABLISHED时,把数据发送出去。如果没有足够内存来保存数据,返回“error:  insufficientresources”。
ESTABLISHED STATE
CLOSE-WAIT STATE
        把数据打包成数据段,并且把确认信息、希望收到的下一个数据段序列号一并发送出去。如果没有足够的内存空间,返回“error:insufficient resources”。如果设置了紧急状态字段,把TCP维护的SND.UP(紧急指针) 值更新为SND.NXT-1并设置数据段的紧急状态指针。
FIN-WAIT-1 STATE
FIN-WAIT-2 STATE
CLOSING STATE
LAST-ACK STATE
TIME-WAIT STATE
        返回“error:  connection closing”,并且不会为当前请求服务。

RECEIVE Call

CLOSED STATE (i.e., TCB does not exist)
        如果用户没有权限访问此链接,返回“error:  connection illegal for this process”。
        否则返回:“error:  connection does not exist”。
LISTEN STATE
SYN-SENT STATE
SYN-RECEIVED STATE
        保存数据,等到状态变成ESTABLISHED时,把数据发送出去。如果没有足够内存来保存数据,返回“error:  insufficientresources”
ESTABLISHED STATE
FIN-WAIT-1 STATE
FIN-WAIT-2 STATE
        如果收到的数据不能满足receive请求,缓存receive请求。如果没有足够的内存空间来记录receive状态,返回“error:  insufficientresources”。
        把收到的数据段组合起来放进receive的接收缓存,返回给用户。如果在传递给用户数据的前面有紧急数据,那么通知用户进程。如果TCP负责把数据分发到用户进程,那么TCP必须给数据发送者一个确认信息。在下面的处理数据段来介绍具体的确认格式。
CLOSE-WAIT STATE
        因为对端已经停止发送数据,所以receive只能从TCP接收缓存中获取数据,如果接收缓存中没有数据,返回“error:  connection closing”。
CLOSING STATE
LAST-ACK STATE
TIME-WAIT STATE
        返回“error:  connection closing”。
        

CLOSE Call

CLOSED STATE (i.e., TCB does not exist)
        如果用户没有权限访问此链接,返回“error:  connection illegal for this process”。
        否则返回:“error:  connection does not exist”。 
LISTEN STATE        
        所有等待接收的数据都被返回“error:  closing”。删除TCB,进入CLOSED状态,返回。
SYN-SENT STATE
        删除TCB,任何等待处理的receive和send数据都被返回“error:  closing”。
SYN-RECEIVED STATE
        如果没有SENDs命令,或者没有要发送的数据,构造一个FIN数据段并发送。
ESTABLISHED STATE
        缓存这个请求,直到所有以前的发送请求都完成,然后生成一个FIN数据段发送出去。然后进入FIN-WAIT-1状态。
FIN-WAIT-1 STATE
FIN-WAIT-2 STATE
        严格讲,这是一个错误操作,应该返回“error:
      connection closing”。返回ok也是可以接收的只要第二个FIN没有发出去,因为第一个FIN有可能在进行重发。
CLOSE-WAIT STATE
        缓存这个请求,直到所有的send指令都完成,然后发送一个FIN标志出去,之后进入CLOSING状态。
CLOSING STATE
LAST-ACK STATE
TIME-WAIT STATE
        返回“error:  connection closing”。

ABORT Call

CLOSED STATE (i.e., TCB does not exist)
        如果用户没有权限访问此链接,返回“error:  connection illegal for this process”。
        否则返回:“error:  connection does not exist”。
LISTEN STATE
        所有缓存的receive都会收到“error:connection reset”。删除TCB,进入CLOSED状态然后返回。
SYN-SENT STATE
        所有的SENDs和RECEIVEs操作都应该收到“connection reset”通知,删除TCB,进入CLOSED状态然后返回。
SYN-RECEIVED STATE
ESTABLISHED STATE
FIN-WAIT-1 STATE
FIN-WAIT-2 STATE
CLOSE-WAIT STATE
        发送一个重置数据包:
            <SEQ=SND.NXT><CTL=RST>
        所有的SENDs 和 RECEIVEs操作都应该收到“connection reset”通知,所有的数据段都应该清空(rst数据段除外),所有的重发队列的数据都应该清空,删除TCB,进入CLOSED状态然后返回。
CLOSING STATE
LAST-ACK STATE
TIME-WAIT STATE  
        返回ok,删除TCB,进入CLOSED状态然后返回。
        

STATUS Call

CLOSED STATE (i.e., TCB does not exist)
      If the user should not have access to such a connection, return
      "error:  connection illegal for this process".
      Otherwise return "error:  connection does not exist".
LISTEN STATE
      Return "state = LISTEN", and the TCB pointer.
SYN-SENT STATE
      Return "state = SYN-SENT", and the TCB pointer.
SYN-RECEIVED STATE
      Return "state = SYN-RECEIVED", and the TCB pointer.
ESTABLISHED STATE
      Return "state = ESTABLISHED", and the TCB pointer.
FIN-WAIT-1 STATE
      Return "state = FIN-WAIT-1", and the TCB pointer.
FIN-WAIT-2 STATE
      Return "state = FIN-WAIT-2", and the TCB pointer.
CLOSE-WAIT STATE
      Return "state = CLOSE-WAIT", and the TCB pointer.
CLOSING STATE
      Return "state = CLOSING", and the TCB pointer.
LAST-ACK STATE
      Return "state = LAST-ACK", and the TCB pointer.
TIME-WAIT STATE
      Return "state = TIME-WAIT", and the TCB pointer.

SEGMENT ARRIVES

如果状态是CLOSED
    所有接收的数据都会被丢弃,包含RST标志的数据段也会被丢弃。不包含RST标志的数据段,会返回一个包含RST的数据段返回给发送者。
    获取数据包中的确认序列号,然后把RST数据包的序列号设置为这个序列号,返回给发送者。如果没有设置ACK标志,那么RST数据包使用序列号0:
    <SEQ=0><ACK=SEG.SEQ+SEG.LEN><CTL=RST,ACK>
    如果ACK标志被设置了:
    <SEQ=SEG.ACK><CTL=RST>
如果状态是LISTEN
    1.首先检查是不是RST数据包
        如果是RST数据包,直接忽略。
    2.然后检查是不是ACK数据包
        如果连接处于LISTEN状态,收到确认信息是不正常的。对于收到的任何确认信息都应该返回RST数据包:
        <SEQ=SEG.ACK><CTL=RST>
    3.再然后检查是不是SYN数据包
        如果收到SYN数据包,检查安全性。如果数据包的安全/隔离不满足TCB里记录的值,直接返回一个RST数据包。
        <SEQ=SEG.ACK><CTL=RST>
        如果数据包的SEG.PRC(优先级)大于TCB里记录的TCB.PRC,如果用户允许,那么设置TCB.PRC<-SEG.PRC,如果不允许,直接返回一个RST数据包。
        <SEQ=SEG.ACK><CTL=RST>
        如果数据包的SEG.PRC(优先级)小于TCB里记录的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时没有指定外部socket信息,那么现在可以通过数据包中的信息来填充相关信息了。
    4.最后关于其他控制信息和数据
        其他控制信息和数据段(没有SYN标志),都应该有反馈信息,收到反馈信息的数据会被丢弃(发送方丢弃)。一个RST包可能是非法的,因为这个数据包并不是之前发送过的任何数据的响应信息。程序流程一般到不了这里,如果走到这里,直接把数据删除。
如果状态是SYN-SENT
    1.检查ACK标志
        如果设置
            如果SEG.ACK =< ISS或者SEG.ACK > SND.NXT,发送一个RST控制包(除非接收的数据包RST标志被设置了),删除数据段,返回。
            <SEQ=SEG.ACK><CTL=RST>
            如果SND.UNA =< SEG.ACK =< SND.NXT
    2.然后检查RST标志
        如果设置
            如果ACK被标志了并且是可以接收的,那么通知用户“error:
          connection reset”,删除数据段,进入CLOSED状态,删除TCB,返回。如果没有ACK标志,丢弃RST控制包,直接返回。
    3.检查安全性和优先级
        如果数据包的安全/隔离与TCB记录的不符合,生成一个RST控制包,发送。
            如果有ACK标志
                <SEQ=SEG.ACK><CTL=RST>
            如果没有ACK标志
              <SEQ=0><ACK=SEG.SEQ+SEG.LEN><CTL=RST,ACK>
            如果有ACK标志
                数据段优先级必须与TCB优先级相符合否则:
                    <SEQ=SEG.ACK><CTL=RST>
            如果没有ACK标志
                如果数据包优先级比TCB优先级高,并且用户进程允许的话,提升TCB优先级,如果用户进程不允许提升TCB优先级,那么返回一个RST控制包
                    <SEQ=0><ACK=SEG.SEQ+SEG.LEN><CTL=RST,ACK>
                如果数据段优先级比TCB优先级低,直接忽略。
            如果RST控制包发送了,丢弃收到的数据包,然后返回。
    4.检查SYN标志
        这步骤只应该在ACK已经标记或者ACK没有标记RST也没有标记的情况下。
        如果有SYN标记,并且优先级和安全/隔离都符合,那么RCV.NXT设置为SEG.SEQ+1,IRS设置为SEG.SEQ。如果有ACK标记,那么SND.UNA应该和SEG.ACK相等。在重发队列的被确认的数据段应该被删除。
        如果SND.UNA > ISS(我们的SYN已经被确认了),连接状态变更为ESTABLISHED,生成一个ACK控制包:
            <SEQ=SND.NXT><ACK=RCV.NXT><CTL=ACK>
        发送队列中的数据段或者控制包可能会与ACK一起发送。如果数据段中还有其他控制信息,那么将在下面检查URG标志的第6步中处理,否则将会返回。
        其他情况下,进入SYN-RECEIVED状态,生成SYN,ACK数据段并发送:
            <SEQ=ISS><ACK=RCV.NXT><CTL=SYN,ACK>
        如果有其他控制信息或者数据,缓存这些数据,等到连接进入ESTABLISHED状态再去处理。
    5.如果SYN和RST都没有设置,直接丢弃数据包,返回。
其他状态
    1.检查序列号
        SYN-RECEIVED STATE
        ESTABLISHED STATE
        FIN-WAIT-1 STATE
        FIN-WAIT-2 STATE
        CLOSE-WAIT STATE
        CLOSING STATE
        LAST-ACK STATE
        TIME-WAIT STATE
            数据段按序列号顺序处理。数据段的初步判断主要用来去除重复的数据,然后按照SEG.SEQ顺序处理。如果一个数据段既有老数据又有新数据,那么新数据会得到处理。
            下面有四种情况用来检验数据段的序列号:
            Segment Receive  Test
            Length  Window
            ------- -------  -------------------------------------------
    
               0       0     SEG.SEQ = RCV.NXT
    
               0      >0     RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND
    
              >0       0     not acceptable
    
              >0      >0     RCV.NXT =< SEG.SEQ < RCV.NXT+RCV.WND
                          or RCV.NXT =< SEG.SEQ+SEG.LEN-1 < RCV.NXT+RCV.WND
                
            如果RCV.WND是0,那么不会接收新数据,但是ACK、URG、RST仍然会被处理。
            如果数据段不能被接收,那么应该返回一个响应。(如果数据段的RST被设置了,接收方就不用返回确认信息了,之后丢弃收到的数据段并返回。):
                 <SEQ=SND.NXT><ACK=RCV.NXT><CTL=ACK>
            发送确认信息后,丢弃数据包。
            接下来,讨论的数据段都是理想的数据段,这些数据段的序列号起始于RCV.NXT,没有超过接收方的窗口大小。有可能会把多余窗口的数据段剪裁成一个新的数据段(包括SYN和FIN),等待后续处理。
            比当前期望接收的序列号大的数据段有可能会被缓存以备后续处理。
    2.检查RST标志
        SYN-RECEIVED STATE
            如果设置了RST标志
                如果接收端连接状态之前是LISTEN状态,连接重置为LISTEN状态,然后返回,不用通知用户。如果用户是主动打开的(当前状态是从SYN-SENT状态转变过来的),通知用户“connection refused”,然后拒绝建立连接。上述两种情况都会把重发队列中的数据清空,在主动打开情况下,进入CLOSED状态,然后删除TCB,返回
            
        ESTABLISHED STATE
        FIN-WAIT-1 STATE
        FIN-WAIT-2 STATE
        CLOSE-WAIT STATE
            如果设置了RST标志
                所有正在处理或者缓存的RECEIVEs与SEND方法都会收到“reset”。所有队列会被清空。用户进程会收到“connection reset”通知。连接进入CLOSED状态,删除TCB,返回。
        CLOSING STATE
        LAST-ACK STATE
        TIME-WAIT
            如果设置了RST标志,直接进入CLOSED状态,删除TCB,返回。
            
    3.检查优先级、安全/隔离
        SYN-RECEIVED
            如果接收数据段的优先级、安全/隔离与TCB的不符合,直接返回reset控制包,然后返回。
        ESTABLISHED STATE
            如果接收数据段的优先级、安全/隔离与TCB的不符合,直接返回reset控制包,未处理的RECEIVEs 和 SEND都应该收到“reset”。在队列中的数据段都应该清空。用户进程收到“connection reset”通知,进入CLOSED状态,删除TCB,返回。
        这个检查放在序列号检查之后,为了防止上一个连接实例的数据段导致现在连接被关闭。
    4.检查SYN标记
        SYN-RECEIVED
        ESTABLISHED STATE
        FIN-WAIT STATE-1
        FIN-WAIT STATE-2
        CLOSE-WAIT STATE
        CLOSING STATE
        LAST-ACK STATE
        TIME-WAIT STATE
            如果SYN的序列号在接收窗口中,那么这是一个错误包,直接发送reset控制包,任何RECEIVEs 和 SEND都应该收到“reset”,所有队列中的数据都应该清空,用户进程也应该收到“connection reset”通知,然后进入CLOSED状态,删除TCB,返回。
            SYN控制包不在窗口中,流程不应该能走到这里。序列号确认那个流程中应该会返回确认信息。
        
    5.检查ACK标记
        如果没有ACK标记直接丢弃并返回。
        如果有ACK
            SYN-RECEIVED STATE
                如果SND.UNA =< SEG.ACK =< SND.NXT直接进入ESTABLISHED状态,继续处理。
                如果ACK不可接收,直接返回reset控制包
                    <SEQ=SEG.ACK><CTL=RST>
            ESTABLISHED STATE
                如果SND.UNA < SEG.ACK =< SND.NXT,那么SND.UNA 设置为 SEG.ACK。重发队列中的被确认的数据都可以删除。用户应该收到正数的确认序号,所有发送的数据都被完全确认(同步情况下,SEND函数返回ok)。如果ACK是重复确认(SEG.ACK < SND.UNA)直接忽略。如果确认了没有发送过的数据,直接把数据包丢弃,返回(因为是同步状态,这种情况下直接丢弃就行,但是非同步状态下,就需要特殊处理了)。
                如果 SND.UNA < SEG.ACK =< SND.NXT,应该更新发送窗口大小。如果SND.WL1 < SEG.SEQ or (SND.WL1 = SEG.SEQ SND.WL2 =< SEG.ACK)设置SND.WND <- SEG.WND,SND.WL1 <- SEG.SEQ,   SND.WL2 <- SEG.ACK
                SND.WND是相对于SND.UNA的偏移量,SND.WL1记录上次更新SND.WND时数据段的序列号,SND.WL2记录上次更新SND.WND时ACK中的序列号。这里检查为了防止老数据段更新窗口大小。
            FIN-WAIT-1 STATE
                如果发出去的FIN得到确认,直接进入FIN-WAIT-2状态。
            FIN-WAIT-2 STATE
                如果重发队列是空的,那么可以确认连接对端发送的FIN,但是不删除TCB。
            CLOSE-WAIT STATE
                逻辑与ESTABLISHED状态时一样。
            CLOSING STATE
                如果发送的FIN得到确认直接进入TIME-WAIT状态,其他情况直接删除数据包。    
            LAST-ACK STATE
                这个状态唯一可以接收的数据,就是对发送的FIN进行的确认信息。如果FIN得到确认,进入CLOSED状态,删除TCB,返回。
            TIME-WAIT
                唯一可以接收的数据就是对面重发过来的FIN消息,对这个IFN进行确认,然后重新等待2 MSL的时间。 
    6.检查URG标志
        ESTABLISHED STATE
        FIN-WAIT-1 STATE
        FIN-WAIT-2 STATE
            如果URG标记被设置,RCV.UP <- max(RCV.UP,SEG.UP),并且通知用户对端有紧急数据需要处理。如果用户已经在紧急模式中,不用重复通知用户。
        CLOSE-WAIT STATE
        CLOSING STATE
        LAST-ACK STATE
        TIME-WAIT
            不会到达这个流程,因为之前收到了FIN,直接忽略数据段。
    7.处理用户数据
        ESTABLISHED STATE
        FIN-WAIT-1 STATE
        FIN-WAIT-2 STATE
            一旦进入ESTABLISHED状态,收到的数据段应该分发到用户的RECEIVE缓冲中。数据段可以分配到用户缓冲中,直到用户缓冲满了或者数据段没数据了。如果数据段是空的,但是有PUSH标志,这是会把用户缓冲返回给用户进程,并且会通知用户进程,接收到了PUSH标志。
            如果TCP负责把数据传递给用户进程,那么TCP负责确认收到的数据。
            一旦TCP接收到数据,他就把RCV.NXT前进到接收的数据字节数,并把RCV.WND调整到目前TCP接收缓存可用的内存大小。RCV.NXT与RCV.WND的总和不能减少,只能前进。
            请在数据交流章节了解窗口管理。
            确认数据包格式为:
                <SEQ=SND.NXT><ACK=RCV.NXT><CTL=ACK>
            这个确认标识应该和数据包一起发送,但是延迟时间应该控制在合理的范围内。
        
        CLOSE-WAIT STATE
        CLOSING STATE
        LAST-ACK STATE
        TIME-WAIT STATE
            不应该走到这个流程,因为收到了FIN标志,直接删除数据包。
    8.检查FIN标志
        如果连接处于CLOSED, LISTEN 或者 SYN-SENT状态,不用处理FIN标志,因为无法检验SEG.SEQ是否合法,删除数据包,返回。
        如果数据段标记了FIN,提示用户“connection closing”,对所有的RECEIVEs返回同样的信息。设置RCV.NXT为FIN的数据包的序列号+1,对FIN返回一个确认信息。FIN和PUSH有相同的作用,把所有已经接收但是还没分发到用户进程的数据,分发到用户进程。
            SYN-RECEIVED STATE
            ESTABLISHED STATE
                进入CLOSE-WAIT状态。
            FIN-WAIT-1
                如果之前发送的FIN得到确认(接收的FIN包中有ACK信息),进入TIME-WAIT状态,然后启动time-wait定时器,关掉其他定时器。否则进入CLOSING状态。
            FIN-WAIT-2 STATE
                进入TIME-WAIT状态,然后启动time-wait定时器,关掉其他定时器。
            CLOSE-WAIT STATE
                继续保持CLOSE-WAIT状态
            CLOSING STATE
                继续保持CLOSING状态
            LAST-ACK STATE  
                继续保持LAST-ACK状态
            TIME-WAIT STATE
                继续保持TIME-WAIT状态,重启2 MSL定时器。

USER TIMEOUT

    USER TIMEOUT(用户指定的时间)
        不管连接处于什么状态,用户指定的超时时间超时,清空所有队列中的数据,通知用户进程“error:  connection aborted due to user timeout”,删除TCB,进入CLOSED状态,返回。
    RETRANSMISSION TIMEOUT(重发超时)
        不管连接处于什么状态,重发队列中的某个数据段超时,直接把数据段前移到队列前端,重启定时器。
    TIME-WAIT TIMEOUT
        如果超时,删除TCB,纳入CLOSED状态,返回。
  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值