Appearance
3.9. 接口
这里涉及两个需要关注的接口:用户/TCP 接口和 TCP/下层接口。本文为用户/TCP 接口给出较为详细的模型;TCP 与下层协议模块之间的接口,则由相应下层协议的规范详细定义。对于下层为 IP 的情形,本文还会指出 TCP 实现可能用到的一些参数值。
3.9.1. 用户/TCP 接口
下面对用户命令的功能描述,充其量只是一种构想,因为每种操作系统提供的设施各不相同。因此必须提醒读者:不同 TCP 实现的用户接口可能并不相同。不过,为了保证所有 TCP 实现都能支撑相同的协议层次,所有实现都必须提供一组最低限度的服务。本节规定所有 TCP 实现都必须具备的功能接口。
RFC 8303 第 3.1 节同样列出了 TCP 提供的原语,可供实现者参考。
以下各节从功能上刻画用户/TCP 接口。所采用的记法类似高级语言中的过程或函数调用,但这并不排除陷入(trap)式服务调用。
下述用户命令规定了 TCP 实现为支持进程间通信所必须执行的基本功能。各实现必须自行定义其确切格式,也可以将若干基本功能组合到一次调用中,或只提供其中的一个子集。特别地,有些实现可能希望在用户首次对某条连接发起 SEND 或 RECEIVE 时自动 OPEN 该连接。
在提供进程间通信设施时,TCP 实现不仅要接受命令,还要向所服务的进程返回信息。返回的信息包括:
- 连接的一般性信息,例如中断、远端关闭、未指定远端套接字的绑定;
- 针对具体用户命令的应答,指出成功或各种失败类型。
3.9.1.1. OPEN
text
OPEN (local port, remote socket, active/passive [, timeout]
[, Diffserv field] [, security/compartment]
[, local IP address] [, options]) -> local connection name若 active/passive 标志置为 passive,此调用即监听传入连接。被动 OPEN 既可以指定完整的远端套接字、等待某一特定连接,也可以不指定远端套接字、等待任意连接请求。已指定完整远端套接字的被动调用,可通过随后执行 SEND 转为主动调用。
TCP 实现会创建一个传输控制块(TCB),并用 OPEN 命令的参数填入其中的部分数据。
每个被动 OPEN 调用要么新建一条处于 LISTEN 状态的连接记录,要么返回错误;它不得影响此前已创建的连接记录(MUST-41)。
支持多个并发连接的 TCP 实现,必须提供这样一种 OPEN 调用:当使用同一本地端口的某个连接块正处于 SYN-SENT 或 SYN-RECEIVED 状态时,应用仍能在该端口上执行监听(MUST-42)。
执行主动 OPEN 命令时,TCP 端点会立即开始同步(即建立)连接。
如果提供了 timeout 参数,调用方可以为提交给 TCP 的所有数据设置超时。若数据未能在超时期限内成功交付到目的地,TCP 端点将中止连接。目前全局默认值为 5 分钟。
TCP 实现或操作系统的某个组件会校验用户是否有权使用所指定的 Diffserv 字段值或安全级别/隔离域。OPEN 调用中未指定 Diffserv 字段值或安全级别/隔离域,即表示必须使用默认值。
仅当传入请求的安全级别/隔离域与 OPEN 调用所请求的值完全一致时,TCP 才将其视为匹配。
用户指定的 Diffserv 字段值仅作用于外发分组;它在网络传输途中可能被修改,与接收到的分组没有直接关系。
TCP 实现会向用户返回一个本地连接名,此后即可用它作为由 <local socket, remote socket> 对所定义连接的简称。
实现必须支持可选的“本地 IP 地址”参数,以便指定本地 IP 地址(MUST-43)。在主机多宿的情况下,应用可借此选择连接所用的本地 IP 地址。
指定了“本地 IP 地址”参数的被动 OPEN,只等待发往该地址的传入连接请求;未指定该参数时,被动 OPEN 会等待发往任一本地 IP 地址的连接请求,并将连接的本地 IP 地址绑定到实际使用的地址。
对于主动 OPEN,若指定了“本地 IP 地址”,则用其打开连接;未指定时,由主机选择合适的本地 IP 地址(参见 RFC 1122 第 3.3.4.2 节)。
多宿主机上的应用主动打开 TCP 连接而未指定本地 IP 地址时,TCP 实现必须在发出第一个 SYN 之前,请 IP 层选定本地 IP 地址(MUST-44)。参见 RFC 1122 第 3.4 节中的 GET_SRCADDR() 函数。
在其他所有情形下,该连接此前已经收发过报文段,TCP 实现必须沿用此前报文段所用的同一本地地址(MUST-45)。
如果远端 IP 地址无效(例如广播地址或组播地址),TCP 实现必须拒绝本地 OPEN 调用并返回错误(MUST-46)。
3.9.1.2. SEND
text
SEND (local connection name, buffer address, byte count,
URGENT flag [, PUSH flag] [, timeout])该调用把指定用户缓冲区中的数据经由指定连接发送出去。若连接尚未打开,SEND 视为错误。有些实现允许用户先发起 SEND,此时自动执行 OPEN——例如,这可以作为让应用数据随 SYN 报文段一并发送的一种方式。若调用进程无权使用该连接,则返回错误。
TCP 端点可以在 SEND 调用中实现 PUSH 标志(MAY-15)。若未实现 PUSH 标志,发送方 TCP 对等端必须做到:(1)不得无限期缓存数据(MUST-60);(2)在最后一个被缓存的报文段(即不再有排队数据待发送时)中设置 PSH 位(MUST-61)。下文描述假定 SEND 调用支持 PUSH 标志。
设置 PUSH 标志表示应用希望数据及时送达接收方;此时由该缓冲区生成的最后一个 TCP 报文段会设置 PSH 位。
PSH 位不是记录标记,与报文段边界无关。发送方在封装数据时应将连续的 PSH 位合并,以便发送尽可能大的报文段(SHLD-27)。
未设置 PUSH 标志时,为提高传输效率,数据可与后续 SEND 调用的数据合并。应用连续发起一系列不带 PUSH 标志的 SEND 调用时,TCP 实现可以在内部聚合这些数据而暂不发送(MAY-16)。注意,启用 Nagle 算法时,TCP 实现可能会先缓存数据再发送,而不考虑 PUSH 标志(参见第 3.7.4 节)。
从逻辑上讲,当应用需要强制交付数据以避免通信死锁时,应在 SEND 调用中设置 PUSH 标志。不过,只要条件允许,TCP 实现应发送最大尺寸的报文段以提高性能(SHLD-28;参见发送方算法)。
由于实现差异和中间盒问题,新应用不应设置 URGENT 标志(SHLD-13)。
设置了 URGENT 标志时,发往目的 TCP 对等端的报文段会携带紧急指针。如果紧急指针指示其之前的数据尚未被接收进程消费,接收方 TCP 对等端会向接收进程发出紧急条件信号。URGENT 标志的作用是促使接收方处理紧急数据,并在当前已知的紧急数据全部收齐后通知接收方。发送方用户的 TCP 实现发出紧急信号的次数,不一定等于接收方用户收到紧急数据通知的次数。
如果 OPEN 时未指定远端套接字,但连接已经建立(例如,处于监听状态的连接因远端报文段到达本地套接字而具体化),指定的缓冲区将被发往隐含的远端套接字。这样一来,使用未指定远端套接字的 OPEN 的用户,无需显式获知远端套接字地址即可使用 SEND。
不过,在远端套接字具体化之前尝试 SEND 会返回错误。用户可以通过 STATUS 调用查询连接状态。有些 TCP 实现会在未指定的套接字完成绑定时通知用户。
如果指定了超时参数,该连接当前的用户超时即被改为新值。
最简单的实现是:SEND 调用直到传输完成或超时到期才把控制权交还发送进程。但这种方法既容易死锁(例如连接双方可能都先做 SEND 而没有执行任何 RECEIVE),性能也差,因此并不推荐。更完善的实现会立即返回,使进程与网络 I/O 并发执行,并允许多个 SEND 同时进行。多个 SEND 按先来先服务的顺序处理,TCP 端点会把暂时无法处理的请求排队。
上面的描述隐含地假定了一种异步用户接口:SEND 之后会由提供服务的 TCP 端点引发某种 SIGNAL 或伪中断。另一种做法是立即返回响应。例如,SEND 可以立即返回本地确认——即使所发报文段尚未得到远端 TCP 端点的确认——乐观地假定最终一定成功;如果这一假定落空,连接反正也会因超时关闭。这类同步实现仍会产生一些异步信号,但它们针对的是连接本身,而不是具体的报文段或缓冲区。
为使进程能够区分不同 SEND 的成功或错误指示,可以在对 SEND 请求的编码响应中一并返回缓冲区地址。下文关于 TCP 到用户信号的说明,会介绍应返回给调用进程的信息。
3.9.1.3. RECEIVE
text
RECEIVE (local connection name, buffer address, byte count)
-> byte count, URGENT flag [, PUSH flag]该命令为指定连接分配一个接收缓冲区。若此前未执行 OPEN,或调用进程无权使用该连接,则返回错误。
最简单的实现是:直到缓冲区填满或发生错误,控制权才返回调用程序,但这种方案极易死锁。更完善的实现允许同时有多个未完成的 RECEIVE,并随报文段的到达依次填充。这种策略能够提高吞吐量,代价是需要一套更复杂的机制(可能是异步的),用于通知调用程序收到了 PUSH 或缓冲区已填满。
TCP 接收方可以通过接口中的 PUSH 标志,把收到的 PSH 位传递给应用层(MAY-17),但并非必须如此(RFC 1122 第 4.2.2.2 节对此作了澄清)。下文关于 RECEIVE 调用的其余描述,均假定支持传递 PUSH 指示。
如果尚未看到 PUSH 就到了足以填满缓冲区的数据,RECEIVE 的响应不会设置 PUSH 标志,缓冲区将尽量填满;如果在缓冲区填满之前看到 PUSH,则返回只填充了一部分的缓冲区,并指示 PUSH。
如果存在紧急数据,用户在数据到达时即已通过 TCP 到用户信号获得通知,因此接收用户此时应处于“紧急模式”。若 URGENT 标志为开,表示仍有紧急数据未交付;若为关,表示本次 RECEIVE 已返回全部紧急数据,用户可以退出“紧急模式”。注意,除非为用户明确标出边界,否则不能把紧急指针之后的数据(非紧急数据)与其之前的紧急数据放在同一缓冲区中交付。
为了区分多个未完成的 RECEIVE,并应对缓冲区未完全填满的情况,返回码需同时附带缓冲区指针和字节数,其中字节数表示实际收到的数据长度。
RECEIVE 还可以有别的实现方式:由 TCP 端点分配缓冲区,或由 TCP 端点与用户共享一个环形缓冲区。
3.9.1.4. CLOSE
text
CLOSE (local connection name)该命令关闭指定连接。若连接未打开,或调用进程无权使用该连接,则返回错误。关闭连接是一种优雅操作:只要流量控制允许,所有未完成的 SEND 都会继续发送(必要时重传),直至全部处理完毕。因此,用户可以先发起多次 SEND 再执行 CLOSE,并预期所有数据都会送达目的地。同样应当明确:用户应继续对正在关闭的连接执行 RECEIVE,因为远端对等端可能正在发送最后的数据。也就是说,CLOSE 的含义是“我没有更多数据要发送”,而不是“我不再接收数据”。如果用户层协议设计不周,关闭方可能在超时之前仍无法清空其全部数据;此时 CLOSE 会转为 ABORT,关闭方 TCP 对等端放弃。
用户可以随时主动 CLOSE 连接,也可以应 TCP 实现的各种提示(例如远端已执行关闭、发送超时、目的地不可达)而 CLOSE。
由于关闭连接需要与远端 TCP 对等端通信,连接可能会在关闭状态停留一小段时间。在 TCP 对等端响应 CLOSE 命令之前尝试重新打开该连接,会收到错误响应。
CLOSE 同时还隐含推送功能。
3.9.1.5. STATUS
text
STATUS (local connection name) -> status data这是一个与实现相关的用户命令,即使省略也不会产生不良影响。返回的信息通常取自与该连接关联的 TCB。
该命令返回包含下列信息的数据块:
- 本地套接字;
- 远端套接字;
- 本地连接名;
- 接收窗口;
- 发送窗口;
- 连接状态;
- 等待确认的缓冲区数量;
- 等待接收的缓冲区数量;
- 紧急状态;
- Diffserv 字段值;
- 安全级别/隔离域;
- 发送超时。
视连接状态或实现本身而定,其中某些信息可能不可用或没有意义。若调用进程无权使用该连接,则返回错误——这可防止未授权进程获取连接信息。
3.9.1.6. ABORT
text
ABORT (local connection name)该命令中止所有未完成的 SEND 和 RECEIVE,删除 TCB,并向连接的远端 TCP 对等端发送一个特殊的 RST 消息。视实现而定,用户可能针对每个未完成的 SEND 或 RECEIVE 分别收到中止指示,也可能只收到一个 ABORT 确认。
3.9.1.7. FLUSH
有些 TCP 实现包含 FLUSH 调用,用于清空 TCP 发送队列中那些用户已通过 SEND 提交、但仍位于当前发送窗口右侧的数据。换言之,它会在不丢失序列号同步的前提下,尽可能冲刷已排队的发送数据。FLUSH 调用可以实现(MAY-14)。
3.9.1.8. 异步报告
必须提供一种向应用报告 TCP 软错误条件的机制(MUST-47)。一般假定该机制表现为一个由应用提供的 ERROR_REPORT 例程,传输层可以异步地向上调用它:
text
ERROR_REPORT(local connection name, reason, subreason)这里不规定 reason 和 subreason 参数的具体编码。不过,必须异步报告给应用的条件包括:
- 收到 ICMP 错误消息(各 ICMP 消息类型的处理见第 3.9.2.2 节,因为某些类型不应触发向应用报告);
- 过度重传(参见第 3.8.3 节);
- 紧急指针前进(参见第 3.8.5 节)。
不希望接收此类 ERROR_REPORT 调用的应用,应当能够切实禁用这些调用(SHLD-20)。
3.9.1.9. 设置差分服务字段(IPv4 TOS 或 IPv6 Traffic Class)
应用层必须能够为连接上发送的报文段指定 Differentiated Services(差分服务)字段(MUST-48)。差分服务字段包含 6 位的 Differentiated Services Codepoint(DSCP)值。虽然不作强制要求,但应用应能够在连接存续期间修改差分服务字段(SHLD-21)。TCP 实现在连接上发送报文段时,应将当前的差分服务字段值原样传递给 IP 层(SHLD-22)。
差分服务字段在连接的两个方向上分别独立指定,因此接收方应用可以指定 ACK 报文段所用的差分服务字段。
TCP 实现可以把最近收到的差分服务字段值传递给应用层(MAY-9)。
3.9.2. TCP/下层接口
TCP 端点调用下层协议模块来完成信息在网络上的实际收发。目前,位于 TCP 之下的互联网协议有两个标准版本:IPv4 和 IPv6。
如果下层协议是 IPv4,它会提供服务类型(用于差分服务字段)和生存时间这两个参数。TCP 对这两个参数的设置如下:
Diffserv 字段: IP 首部中 Diffserv 字段的值由用户指定,其中包括差分服务代码点(DSCP)的各个位。
Time to Live(TTL): 发送 TCP 报文段所用的 TTL 值必须可配置(MUST-49)。注意,RFC 793 曾将 TTL 规定为一分钟(60 秒)的常量,因为它假定报文段的最大生存期为两分钟,其意图是明确要求:如果互联网系统无法在一分钟内交付某个报文段,就应将其销毁。RFC 1122 更新了 RFC 793,要求 TTL 必须可配置。
注意,RFC 1122 允许在连接期间改变 Diffserv 字段(第 4.2.4.2 节)。不过,应用接口可能不支持这一能力,而且应用并不了解单个 TCP 报文段的情况,因此即便能改,也只能以较粗的粒度进行。RFC 7657 第 5.1、5.3 和 6 节进一步讨论了这一限制。一般而言,应用不应在连接存续期间改变 Diffserv 字段值(SHLD-23)。
任何下层协议都必须提供源地址、目的地址和协议字段,并提供某种确定“TCP 长度”的方法;这些既是为了在功能上等价于 IP 所提供的服务,也用于计算 TCP 校验和。
当 IP 层向 TCP 上传收到的选项时,TCP 实现必须忽略自己不理解的选项(MUST-50)。
TCP 实现可以支持时间戳(Timestamp)选项(MAY-10)和记录路由(Record Route)选项(MAY-11)。
3.9.2.1. 源路由
如果下层是 IP(或其他提供此功能的协议)并且使用了源路由,接口必须允许传递路由信息。这一点尤为重要,因为 TCP 校验和所用的源地址与目的地址必须是起始源地址和最终目的地址;同时还必须保留返回路由,以便应答连接请求。
应用必须能够在主动打开 TCP 连接时指定源路由(MUST-51),而且该源路由必须优先于数据报中收到的源路由(MUST-52)。
被动 OPEN 一条 TCP 连接后,如果到达的报文段携带了已完整的 IP Source Route 选项(其中包含返回路由),TCP 实现必须保存该返回路由,并将其用于此连接上发送的所有报文段(MUST-53)。如果后续报文段带来了不同的源路由,应以较晚的定义覆盖较早的定义(SHLD-24)。
3.9.2.2. ICMP 消息
TCP 实现必须对 IP 层上传的 ICMP 错误消息做出处理,并将其分派给引发该错误的连接(MUST-54)。分用所需的信息可从 ICMP 消息中内嵌的 IP 首部获得。
这一规则除适用于 IPv4 ICMP 外,同样适用于 ICMPv6。
RFC 5461 讨论了若干 ICMP 和 ICMPv6 消息,将其分为“软”错误与“硬”错误两类,二者可能需要不同的响应。各类消息的具体处理如下:
Source Quench(源端抑制): TCP 实现必须静默丢弃收到的任何 ICMP Source Quench 消息(MUST-55)。
软错误: 对 IPv4 ICMP 而言,软错误包括 Destination Unreachable(代码 0、1、5)、Time Exceeded(代码 0、1)以及 Parameter Problem;对 ICMPv6 而言,包括 Destination Unreachable(代码 0、3)、Time Exceeded(代码 0、1)以及 Parameter Problem(代码 0、1、2)。由于这些 Unreachable 消息表示软错误条件,TCP 实现绝不能中止连接(MUST-56),并应将该信息提供给应用(SHLD-25)。
硬错误: 对 ICMP 而言,硬错误包括 Destination Unreachable(代码 2–4)。这些属于硬错误条件,因此 TCP 实现应中止连接(SHLD-26)。RFC 5461 指出,有些实现在连接处于任一同步状态时收到 ICMP 硬错误,并不会中止连接。
注意,RFC 5461 第 4 节描述了一种普遍存在的实现行为:在建立连接期间将软错误当作硬错误处理。
3.9.2.3. 源地址验证
RFC 1122 要求对传入 SYN 报文段中的地址执行检查:
- 源地址无效的传入
SYN,必须由 TCP 或 IP 层予以忽略(MUST-63)。 - 发往广播或组播地址的传入
SYN报文段,必须由 TCP 实现静默丢弃(MUST-57)。
这可避免错误地生成连接状态和响应。实现者还应注意,RFC 1122 明确指出,上述要求适用于所有传入报文段,而不仅仅是 SYN。