4. 消息传输 (Message Transmission)
CoAP 消息在 CoAP 端点之间异步交换. 它们用于传输 CoAP 请求和响应, 其语义在 Section 5 中定义.
由于 CoAP 绑定到 UDP 等不可靠传输, CoAP 消息可能乱序到达, 重复出现, 或在没有通知的情况下丢失. 因此, CoAP 实现了一种轻量级可靠性机制, 但不试图重新创建 TCP 等传输协议的完整功能集. 它具有以下特性:
o 对 Confirmable 消息使用带指数退避的简单 stop-and-wait 重传可靠性.
o 对 Confirmable 和 Non-confirmable 消息都进行重复检测.
4.1. 消息和端点 (Messages and Endpoints)
CoAP 端点是 CoAP 消息的源或目标. 端点的具体定义取决于 CoAP 所使用的传输. 对本规范定义的传输, 端点按所用安全模式标识 (见 Section 9): 在无安全性时, 端点仅由 IP 地址和 UDP 端口号标识. 在其他安全模式下, 端点按该安全模式的定义标识.
消息有不同类型. 消息的类型由 CoAP Header 的 Type 字段指定.
独立于消息类型, 消息可以携带请求, 响应, 或者为空 (Empty). 这由 CoAP Header 中的 Request/Response Code 字段指示, 并与请求/响应模型相关. 该字段的可能值维护在 CoAP Code Registries (Section 12.1) 中.
Empty 消息的 Code 字段设置为 0.00. Token Length 字段必须设置为 0, 并且 Message ID 字段之后不得存在数据字节. 如果存在任何字节, 它们必须作为消息格式错误处理.
4.2. 可靠传输的消息 (Messages Transmitted Reliably)
消息的可靠传输通过在 CoAP header 中将消息标记为 Confirmable 来发起. Confirmable 消息始终携带请求或响应, 除非它仅用于引出 Reset 消息, 在这种情况下它是 Empty. 接收方必须要么 (a) 用 Acknowledgement 消息确认 Confirmable 消息, 要么 (b) 在缺少正确处理消息所需上下文时拒绝该消息, 包括消息为 Empty, 使用保留 class (1, 6, 或 7) 的 code, 或存在消息格式错误的情况. 拒绝 Confirmable 消息通过发送匹配的 Reset 消息并在其他方面忽略该消息来完成. Acknowledgement 消息必须回显 Confirmable 消息的 Message ID, 并且必须携带响应或为 Empty (见 Sections 5.2.1 和 5.2.2). Reset 消息必须回显 Confirmable 消息的 Message ID, 并且必须为 Empty. 拒绝 Acknowledgement 或 Reset 消息 (包括 Acknowledgement 携带请求或保留 class 的 code, 或 Reset 消息不为空的情况) 通过静默忽略完成. 更一般地, Acknowledgement 和 Reset 消息的接收方不得以 Acknowledgement 或 Reset 消息响应.
发送方以指数递增的间隔重传 Confirmable 消息, 直到收到确认 (或 Reset 消息), 或耗尽尝试次数.
重传由 CoAP 端点在等待确认 (或 reset) 时必须为其发送的每个 Confirmable 消息跟踪的两项内容控制: 超时和重传计数器. 对新的 Confirmable 消息, 初始超时设置为 ACK_TIMEOUT 与 (ACK_TIMEOUT * ACK_RANDOM_FACTOR) 之间的随机持续时间 (通常不是整数秒) (见 Section 4.8), 重传计数器设置为 0. 当超时触发且重传计数器小于 MAX_RETRANSMIT 时, 重传消息, 递增重传计数器, 并将超时加倍. 如果重传计数器在超时时达到 MAX_RETRANSMIT, 或端点收到 Reset 消息, 则取消传输该消息的尝试, 并通知应用进程失败. 另一方面, 如果端点及时收到确认, 则认为传输成功.
本规范不强制要求实现上述二进制指数退避算法所用时钟的精度. 特别是, 端点可能因休眠调度而错过某次特定重传, 并在下一次补上. 但是, 另一次重传之前的最小间隔是 ACK_TIMEOUT, 并且整个 (re-)transmission 序列必须保持在 MAX_TRANSMIT_SPAN 的范围内 (见 Section 4.8.2), 即使这意味着发送方可能错过一次传输机会.
发送 Confirmable 消息的 CoAP 端点可以在达到 MAX_RETRANSMIT 计数器值之前放弃获取 ACK. 例如, 应用已取消请求, 因为它不再需要响应; 或者有其他迹象表明 CON 消息已到达. 特别是, CoAP 请求消息可能已引出 separate response, 此时请求方清楚只有 ACK 丢失, 重传请求没有意义. 但是, 响应方不得反过来依赖请求方的这种跨层行为, 即如有需要, 即使 Confirmable 响应已由请求方确认, 它也必须保留用于创建该请求的 ACK 的状态.
放弃重传的另一个原因可以是收到 ICMP error. 如果希望考虑 ICMP error, 为缓解潜在欺骗攻击, 实现应当注意检查 ICMP 消息中关于原始数据报的信息, 包括端口号和 CoAP header 信息, 如消息类型和 code, Message ID 以及 Token; 如果因 UDP service API 限制无法做到, ICMP error 应当被忽略. 如果遵循 Section 4.6 的实现说明, Packet Too Big error [RFC4443] (IPv4 中的 "fragmentation needed and DF set" [RFC0792]) 不会正确发生, 应当被忽略; 否则, 它们应当输入 path MTU discovery algorithm [RFC4821]. Source Quench 和 Time Exceeded ICMP 消息应当被忽略. host, network, port 或 protocol unreachable error, 或 parameter problem error 可以在适当审查后用于通知应用发送失败.
4.3. 非可靠传输的消息 (Messages Transmitted without Reliability)
一些消息不需要确认. 这尤其适用于因应用需求而定期重复的消息, 例如来自 sensor 的重复读数, 只要最终成功即可.
作为更轻量的替代方式, 可通过将消息标记为 Non-confirmable 来以较低可靠性传输. Non-confirmable 消息始终携带请求或响应, 并且不得为 Empty. Non-confirmable 消息不得由接收方确认. 接收方在缺少正确处理消息所需上下文时必须拒绝该消息, 包括消息为 Empty, 使用保留 class (1, 6, 或 7) 的 code, 或存在消息格式错误的情况. 拒绝 Non-confirmable 消息可以包括发送匹配的 Reset 消息, 除 Reset 消息外, 被拒绝的消息必须被静默忽略.
在 CoAP 层, 发送方无法检测 Non-confirmable 消息是否已被接收. 发送方可以选择在 MAX_TRANSMIT_SPAN 内发送 Non-confirmable 消息的多个副本 (受 Section 4.7 的规定限制, 特别是在未收到响应时受 PROBING_RATE 限制), 或者网络可能在传输中复制消息. 为使接收方只对该消息执行一次操作, Non-confirmable 消息也指定 Message ID. (该 Message ID 与 Confirmable 消息的 Message ID 来自同一编号空间.)
总结 Sections 4.2 和 4.3, 四种消息类型可按 Table 1 使用. "*" 表示该组合不在正常操作中使用, 只用于引出 Reset 消息 ("CoAP ping").
+----------+-----+-----+-----+-----+
| | CON | NON | ACK | RST |
+----------+-----+-----+-----+-----+
| Request | X | X | - | - |
| Response | X | X | X | - |
| Empty | * | - | X | X |
+----------+-----+-----+-----+-----+
Table 1: Usage of Message Types
4.4. 消息关联 (Message Correlation)
Acknowledgement 或 Reset 消息通过 Message ID 以及对应端点的附加地址信息, 与 Confirmable 消息或 Non-confirmable 消息相关联. Message ID 是 16-bit unsigned integer, 由 Confirmable 或 Non-confirmable 消息的发送方生成并包含在 CoAP header 中. Message ID 必须由接收方在 Acknowledgement 或 Reset 消息中回显.
在 EXCHANGE_LIFETIME (Section 4.8.2) 内, 与同一端点通信时, 相同 Message ID 不得被重用.
实现说明: 可采用多种实现策略生成 Message ID. 最简单情况下, CoAP 端点通过维护单个 Message ID 变量生成 Message ID, 每发送新的 Confirmable 或 Non-confirmable 消息就改变该变量, 不考虑目标地址或端口. 处理大量事务的端点可维护多个 Message ID 变量, 例如按前缀或目标地址维护. (注意, 某些接收端点可能无法区分发往自己的单播和多播数据包, 因此生成 Message ID 的端点需要确保它们不重叠.) 强烈建议将变量的初始值 (例如启动时) 随机化, 以降低针对协议的成功 off-path 攻击可能性.
要使 Acknowledgement 或 Reset 消息匹配 Confirmable 或 Non-confirmable 消息, Acknowledgement 或 Reset 消息的 Message ID 和源端点必须匹配 Confirmable 或 Non-confirmable 消息的 Message ID 和目标端点.
4.5. 消息去重 (Message Deduplication)
接收方可能在 EXCHANGE_LIFETIME (Section 4.8.2) 内多次收到同一 Confirmable 消息 (由 Message ID 和源端点指示), 例如其 Acknowledgement 丢失或未在第一次超时前到达原发送方. 接收方应当使用同一 Acknowledgement 或 Reset 消息确认每个 Confirmable 消息的重复副本, 但应当只处理该消息中的任何请求或响应一次. 如果 Confirmable 消息传输的请求是 idempotent (见 Section 5.1) 或可按 idempotent 方式处理, 该规则可以放宽. 放宽消息去重的示例:
o 服务器可放宽要求, 不必用同一响应回答 idempotent 请求的所有重传 (Section 4.2), 从而无需为 Message ID 维护状态. 例如, 如果重复处理的成本低于跟踪先前响应的成本, 实现可能希望将 GET, PUT 或 DELETE 请求的重复传输作为独立请求处理.
o 如果应用语义使这种权衡有利, 受限服务器甚至可能希望对某些 non-idempotent 请求放宽此要求. 例如, 如果 POST 请求的结果只是服务器上某些短寿命状态的创建, 则多次承担该工作可能比跟踪同一请求的先前传输是否已处理更便宜.
接收方可能在 NON_LIFETIME (Section 4.8.2) 内多次收到同一 Non-confirmable 消息 (由 Message ID 和源端点指示). 作为可基于消息具体语义放宽的一般规则, 接收方应当静默忽略任何重复的 Non-confirmable 消息, 并应当只处理该消息中的任何请求或响应一次.
4.6. 消息大小 (Message Size)
虽然特定链路层会使 CoAP 消息足够小以装入其链路层数据包变得有益 (见 Section 1), 但这是实现质量问题. CoAP 规范本身只给出消息大小的上界. 大于一个 IP 数据包的消息会导致不理想的数据包分片. 适当封装的 CoAP 消息应当装入单个 IP 数据包 (即避免 IP 分片), 并且显然需要 (通过装入一个 UDP 负载) 装入单个 IP 数据报. 如果某目标的 Path MTU 未知, 应当假定 IP MTU 为 1280 bytes; 如果 header 大小完全未知, 对消息大小和负载大小的良好上界分别是 1152 bytes 和 1024 bytes.
实现说明: CoAP 对消息大小参数的选择适用于 IPv6 以及当今大多数 IPv4 path. (但是, 对 IPv4 来说, 绝对确保没有 IP 分片更困难. 如果需要考虑在非常规网络上的 IPv4 支持, 实现可能希望限制为更保守的 IPv4 数据报大小, 如 576 bytes; 根据 [RFC0791], IPv4 的 IP MTU 绝对最小值低至 68 bytes, 这只会给 UDP 负载留下 40 bytes 减去安全开销. 极度关注此问题集的实现也可设置 IPv4 DF bit 并执行某种 path MTU discovery [RFC4821]; 但在 CoAP 的现实用例中通常不需要.) 在许多受限网络中, 更重要的一类分片发生在 adaptation layer 上 (例如 6LoWPAN L2 数据包限制为 127 bytes, 且包括各种开销); 这可能促使实现节省数据包大小, 并在接近三位数消息大小时转向 block-wise transfer [BLOCK].
消息大小对 constrained node 上的实现也非常重要. 许多实现需要为传入消息分配缓冲区. 如果实现过于受限, 无法分配上述上界, 对未使用 DTLS security 的消息可采用以下实现策略: 实现接收数据报到过小缓冲区时, 通常能够判断数据报尾部是否被丢弃, 并取回起始部分. 因此, 至少 CoAP header 和选项, 即便不是全部负载, 很可能能装入缓冲区. 服务器因而可完整解释请求, 并在负载被截断时返回 4.13 (Request Entity Too Large; 见 Section 5.9.2.9) Response Code. 客户端发送 idempotent 请求并收到大于缓冲区可容纳大小的响应时, 可使用合适的 Block Option [BLOCK] 值重复请求.
4.7. 拥塞控制 (Congestion Control)
CoAP 的基本拥塞控制由 Section 4.2 的指数退避机制提供.
为避免造成拥塞, 客户端 (包括代理) 必须严格限制其对给定服务器 (包括代理) 维护的 simultaneous outstanding interaction 数量为 NSTART. outstanding interaction 是 ACK 尚未收到但仍被期待的 CON (消息层), 或响应与 Acknowledgment 消息都尚未收到但仍被期待的请求 (二者可能同时发生, 计为一个 outstanding interaction). 本规范中 NSTART 的默认值为 1.
未来预计会有进一步的拥塞控制优化和考虑, 例如可提供 Section 4.8 中定义的 CoAP transmission parameter 的自动初始化, 因而可能允许 NSTART 大于一.
在 EXCHANGE_LIFETIME 之后, 客户端停止期待对未收到 Acknowledgement 消息的 Confirmable 请求的响应.
客户端停止 "expect" 已确认 Confirmable 请求或 Non-confirmable 请求的响应的具体算法未定义. 除非被额外拥塞控制优化修改, 否则该算法必须选择为: 对另一个不响应的端点发送时, 端点不超过 PROBING_RATE 的平均数据速率.
注意: CoAP 将拥塞控制的负担主要放在客户端上. 但是, 客户端可能故障, 也可能实际是攻击者, 例如执行 amplification attack (Section 11.3). 为限制损害 (对网络和自身能源资源), 服务器应当基于对应用需求的合理假设, 为其响应传输实现某种速率限制. 如果速率限制只对行为不当的端点生效, 最有帮助.
4.8. 传输参数 (Transmission Parameters)
消息传输由以下参数控制:
+-------------------+---------------+
| name | default value |
+-------------------+---------------+
| ACK_TIMEOUT | 2 seconds |
| ACK_RANDOM_FACTOR | 1.5 |
| MAX_RETRANSMIT | 4 |
| NSTART | 1 |
| DEFAULT_LEISURE | 5 seconds |
| PROBING_RATE | 1 byte/second |
+-------------------+---------------+
Table 2: CoAP Protocol Parameters
4.8.1. 修改参数 (Changing the Parameters)
ACK_TIMEOUT, ACK_RANDOM_FACTOR, MAX_RETRANSMIT, NSTART, DEFAULT_LEISURE (Section 8.2) 和 PROBING_RATE 的值可以配置为特定于应用环境的值 (包括动态调整值); 但是, 配置方法超出本文档范围. 推荐应用环境对这些参数使用一致值; 在应用环境中使用不一致值的具体影响超出本规范范围.
传输参数的选择旨在使协议在存在拥塞时仍能对 Internet 安全. 如果某配置希望使用不同值, 该配置负责确保这些拥塞控制属性不被违反. 特别是, 将 ACK_TIMEOUT 降低到 1 second 以下会违反 [RFC5405] 的指南. ([RTO-CONSIDER] 提供了一些额外背景.) CoAP 被设计为支持不维护 round-trip-time (RTT) measurement 的实现. 但是, 若希望显著降低 ACK_TIMEOUT 或提高 NSTART, 只有在维护此类 measurement 时才能安全完成. 配置不得在未使用确保拥塞控制安全性的机制时降低 ACK_TIMEOUT 或提高 NSTART, 这些机制可在配置中定义或在未来标准文档中定义.
ACK_RANDOM_FACTOR 不得降低到 1.0 以下, 并且应当具有与 1.0 足够不同的值, 以提供某种同步效应保护.
MAX_RETRANSMIT 可自由调整, 但过小的值会降低 Confirmable 消息实际被接收的概率, 而大于此处给出值的值需要进一步调整时间值 (见 Section 4.8.2).
如果传输参数的选择导致派生时间值增加 (见 Section 4.8.2), 配置机制必须确保调整后的值也可用于所有将使用这些调整值进行通信的端点.
4.8.2. 由传输参数派生的时间值 (Time Values Derived from Transmission Parameters)
ACK_TIMEOUT, ACK_RANDOM_FACTOR 和 MAX_RETRANSMIT 的组合影响重传的时序, 进而影响实现需要保留某些信息项的时间长度. 为能无歧义地引用这些派生时间值, 我们为其命名如下:
o MAX_TRANSMIT_SPAN 是从 Confirmable 消息第一次传输到其最后一次重传的最大时间. 对默认传输参数, 该值为 (2+4+8+16)*1.5 = 45 seconds, 更一般地:
ACK_TIMEOUT * ((2 ** MAX_RETRANSMIT) - 1) * ACK_RANDOM_FACTOR
o MAX_TRANSMIT_WAIT 是从 Confirmable 消息第一次传输到发送方放弃接收确认或 reset 的最大时间. 对默认传输参数, 该值为 (2+4+8+16+32)*1.5 = 93 seconds, 更一般地:
ACK_TIMEOUT * ((2 ** (MAX_RETRANSMIT + 1)) - 1) *
ACK_RANDOM_FACTOR
此外, 需要对网络和节点特性作出一些假设.
o MAX_LATENCY 是数据报从开始传输到完成接收预计可能花费的最大时间. 该常量与 [RFC0793] 的 MSL (Maximum Segment Lifetime) 相关, 后者 "arbitrarily defined to be 2 minutes" ([RFC0793] glossary, page 81). 注意, 这不一定小于 MAX_TRANSMIT_WAIT, 因为 MAX_LATENCY 不是用于描述协议运行良好的情况, 而是协议必须防护的最坏情况. 我们同样任意地将 MAX_LATENCY 定义为 100 seconds. 除了对大多数配置来说相当现实并接近 TCP 的历史选择外, 该值还允许 Message ID lifetime timer 以 8 bits 表示 (以 seconds 度量时). 在这些计算中, 不假定传输方向无关 (即不假定网络对称); 只假定同一值可合理地用作两个方向的最大值. 如果不是这样, 以下计算只会稍微更复杂.
o PROCESSING_DELAY 是节点将 Confirmable 消息转换为确认所需的时间. 我们假定节点会尝试在发送方超时之前发送 ACK, 因而作为保守假设, 将其设置为等于 ACK_TIMEOUT.
o MAX_RTT 是最大往返时间, 即:
(2 * MAX_LATENCY) + PROCESSING_DELAY
根据这些值, 可推导出以下与协议操作相关的值:
o EXCHANGE_LIFETIME 是从开始发送 Confirmable 消息到不再期待确认的时间, 即关于该消息交换的消息层信息可以清除. EXCHANGE_LIFETIME 包括 MAX_TRANSMIT_SPAN, 向前方向的 MAX_LATENCY, PROCESSING_DELAY 以及返回方向的 MAX_LATENCY. 注意, 如果配置选择使最后一个等待周期 (ACK_TIMEOUT * (2 ** MAX_RETRANSMIT), 或 MAX_TRANSMIT_SPAN 与 MAX_TRANSMIT_WAIT 之间的差) 小于 MAX_LATENCY, 就不需要考虑 MAX_TRANSMIT_WAIT -- 这是一种可能的选择, 因为 MAX_LATENCY 是现实世界中不太可能遇到的最坏情况值. 在这种情况下, EXCHANGE_LIFETIME 简化为:
MAX_TRANSMIT_SPAN + (2 * MAX_LATENCY) + PROCESSING_DELAY
使用默认传输参数时为 247 seconds.
o NON_LIFETIME 是从发送 Non-confirmable 消息到其 Message ID 可安全重用的时间. 如果不使用 NON 消息的多次传输, 其值为 MAX_LATENCY, 即 100 seconds. 但是, CoAP 发送方可能多次发送 NON 消息, 特别是用于 multicast application. 虽然重用周期不受规范约束, 但对接收方可靠检测重复的期待处于 MAX_TRANSMIT_SPAN 时间尺度. 因此, 为此目的, 使用以下值更安全:
MAX_TRANSMIT_SPAN + MAX_LATENCY
使用默认传输参数时为 145 seconds; 但是, 只想使用单个超时值回收 Message ID 的实现可安全使用更大的 EXCHANGE_LIFETIME 值.
Table 3 列出本小节引入的派生参数及其默认值.
+-------------------+---------------+
| name | default value |
+-------------------+---------------+
| MAX_TRANSMIT_SPAN | 45 s |
| MAX_TRANSMIT_WAIT | 93 s |
| MAX_LATENCY | 100 s |
| PROCESSING_DELAY | 2 s |
| MAX_RTT | 202 s |
| EXCHANGE_LIFETIME | 247 s |
| NON_LIFETIME | 145 s |
+-------------------+---------------+
Table 3: Derived Protocol Parameters