4. Message Transmission (消息传输)
CoAP消息在CoAP端点之间异步交换. 它们用于传输CoAP请求和响应, 其语义在第5节中定义.
由于CoAP绑定到诸如UDP之类的不可靠传输, CoAP消息可能会乱序到达, 重复出现, 或在没有通知的情况下丢失. 因此, CoAP实现了一个轻量级的可靠性机制, 而不试图重新创建像TCP这样的传输的完整功能集. 它具有以下特性:
-
具有指数退避的简单停止等待重传可靠性, 用于可确认消息.
-
对可确认和不可确认消息的重复检测.
4.1. Messages and Endpoints (消息和端点)
CoAP端点是CoAP消息的源或目的地. 端点的具体定义取决于CoAP使用的传输. 对于本规范中定义的传输, 端点的识别取决于使用的安全模式 (见第9节): 在没有安全性的情况下, 端点仅由IP地址和UDP端口号标识. 在其他安全模式下, 端点按安全模式定义的方式标识.
消息有不同的类型. 消息的类型由CoAP头部的类型字段指定.
与消息类型分离, 消息可以携带请求, 响应或为空. 这由CoAP头部中的请求/响应码字段表示, 并且与请求/响应模型相关. 该字段的可能值在CoAP代码注册表 (第12.1节) 中维护.
空消息的代码字段设置为0.00. Token长度字段必须设置为0, 并且消息ID字段之后不得有任何数据字节. 如果有任何字节, 则必须作为消息格式错误处理.
4.2. Messages Transmitted Reliably (可靠传输的消息)
消息的可靠传输通过在CoAP头部中将消息标记为可确认 (CON) 来启动. 可确认消息始终携带请求或响应, 除非它仅用于引出重置消息, 在这种情况下它是空的. 接收者必须 (a) 使用确认消息 (ACK) 确认可确认消息, 或 (b) 如果接收者缺乏正确处理消息的上下文 (包括消息为空, 使用保留类别的代码 (1, 6或7), 或具有消息格式错误的情况), 则拒绝该消息. 拒绝可确认消息通过发送匹配的重置消息并忽略它来实现. 确认消息必须回显可确认消息的消息ID, 并且必须携带响应或为空 (见第5.2.1节和第5.2.2节). 重置消息必须回显可确认消息的消息ID, 并且必须为空. 拒绝确认或重置消息 (包括确认携带请求或具有保留类别的代码, 或重置消息不为空的情况) 通过静默忽略来实现. 更一般地说, 确认和重置消息的接收者不得使用确认或重置消息进行响应.
发送者以指数增长的间隔重传可确认消息, 直到它收到确认 (或重置消息) 或用尽尝试.
重传由CoAP端点必须为其发送的每个可确认消息在等待确认 (或重置) 时跟踪的两件事控制: 超时和重传计数器. 对于新的可确认消息, 初始超时设置为ACK_TIMEOUT和 (ACK_TIMEOUT * ACK_RANDOM_FACTOR) 之间的随机持续时间 (通常不是整数秒) (见第4.8节), 并且重传计数器设置为0. 当触发超时且重传计数器小于MAX_RETRANSMIT时, 消息被重传, 重传计数器递增, 并且超时加倍. 如果重传计数器在超时时达到MAX_RETRANSMIT, 或者端点收到重置消息, 则取消传输消息的尝试并通知应用程序进程失败. 另一方面, 如果端点及时收到确认, 则传输被认为是成功的.
本规范对用于实现上述二进制指数退避算法的时钟的准确性没有强要求. 特别是, 由于其睡眠调度, 端点可能会错过特定的重传, 并可能在下一次重传时赶上. 但是, 另一次重传之前的最小间隔是ACK_TIMEOUT, 并且整个 (重新) 传输序列必须保持在MAX_TRANSMIT_SPAN的包络内 (见第4.8.2节), 即使这意味着发送者可能会错过传输机会.
发送可确认消息的CoAP端点可以在达到MAX_RETRANSMIT计数器值之前放弃尝试获取ACK. 例如, 应用程序已取消请求, 因为它不再需要响应, 或者有一些其他迹象表明CON消息确实到达. 特别是, CoAP请求消息可能已经引出了分离响应, 在这种情况下, 请求者清楚地知道只有ACK丢失, 重传请求将毫无用处. 但是, 响应者不得反过来依赖请求者的这种跨层行为, 即, 如果需要, 它必须保留状态以创建请求的ACK, 即使可确认响应已被请求者确认.
放弃重传的另一个原因可能是收到ICMP错误. 如果希望考虑ICMP错误, 为了缓解潜在的欺骗攻击, 实现应该注意检查ICMP消息中关于原始数据报的信息, 包括端口号和CoAP头部信息 (例如消息类型和代码, 消息ID和Token); 如果由于UDP服务API的限制而无法做到这一点, 则应该忽略ICMP错误. 数据包太大错误 [RFC4443] (IPv4的"需要分片且设置了DF" [RFC0792]) 不能正常发生, 如果遵循第4.6节中的实现注意, 则应该忽略; 否则, 它们应该馈入路径MTU发现算法 [RFC4821]. 应该忽略源抑制 (Source Quench) 和超时 (Time Exceeded) ICMP消息. 主机, 网络, 端口或协议不可达错误或参数问题错误可以在适当审查后用于通知应用程序发送失败.
4.3. Messages Transmitted without Reliability (非可靠传输的消息)
某些消息不需要确认. 对于出于应用要求而定期重复的消息尤其如此, 例如来自传感器的重复读数, 其中最终成功就足够了.
作为更轻量级的替代方案, 可以通过将消息标记为不可确认来以较低的可靠性传输消息. 不可确认消息始终携带请求或响应, 并且不得为空. 接收者不得确认不可确认消息. 如果接收者缺乏正确处理消息的上下文 (包括消息为空, 使用保留类别的代码 (1, 6或7), 或具有消息格式错误的情况), 则接收者必须拒绝该消息. 拒绝不可确认消息可能涉及发送匹配的重置消息, 除了重置消息之外, 被拒绝的消息必须被静默忽略.
在CoAP级别, 发送者无法检测不可确认消息是否被接收. 发送者可以选择在MAX_TRANSMIT_SPAN内传输不可确认消息的多个副本 (受第4.7节规定的限制, 特别是如果没有收到响应则受PROBING_RATE限制), 或者网络可能在传输过程中复制消息. 为了使接收者能够仅对消息执行一次操作, 不可确认消息也指定消息ID. (此消息ID从与可确认消息的消息ID相同的数字空间中提取.)
总结第4.2节和第4.3节, 四种消息类型可以如表1所示使用. "*"表示该组合不在正常操作中使用, 而仅用于引出重置消息 ("CoAP ping").
+----------+-----+-----+-----+-----+
| | CON | NON | ACK | RST |
+----------+-----+-----+-----+-----+
| Request | X | X | - | - |
| Response | X | X | X | - |
| Empty | * | - | X | X |
+----------+-----+-----+-----+-----+
表 1: 消息类型的使用
4.4. Message Correlation (消息关联)
确认或重置消息通过消息ID以及相应端点的附加地址信息与可确认消息或不可确认消息相关. 消息ID是一个16位无符号整数, 由可确认或不可确认消息的发送者生成并包含在CoAP头部中. 接收者必须在确认或重置消息中回显消息ID.
相同的消息ID不得在EXCHANGE_LIFETIME内重复使用 (与同一端点通信时) (第4.8.2节).
实现注意: 可以采用几种实现策略来生成消息ID. 在最简单的情况下, CoAP端点通过保持单个消息ID变量来生成消息ID, 每次发送新的可确认或不可确认消息时都会更改该变量, 而不管目标地址或端口如何. 处理大量事务的端点可以保持多个消息ID变量, 例如, 每个前缀或目标地址一个. (请注意, 某些接收端点可能无法区分发送给它的单播和组播数据包, 因此生成消息ID的端点需要确保这些不重叠.) 强烈建议变量的初始值 (例如, 在启动时) 是随机的, 以使对协议的成功路径外攻击的可能性降低.
为了使确认或重置消息与可确认或不可确认消息匹配, 确认或重置消息的消息ID和源端点必须与可确认或不可确认消息的消息ID和目标端点匹配.
4.5. Message Deduplication (消息去重)
接收者可能会在EXCHANGE_LIFETIME (第4.8.2节) 内多次接收相同的可确认消息 (由消息ID和源端点指示), 例如, 当其确认丢失或在第一次超时之前未到达原始发送者时. 接收者应该使用相同的确认或重置消息确认可确认消息的每个重复副本, 但应该仅处理消息中的任何请求或响应一次. 如果可确认消息传输的请求是幂等的 (见第5.1节) 或可以以幂等方式处理, 则可以放宽此规则. 放宽消息去重的示例:
-
服务器可能会放宽要求, 即用相同的响应回答幂等请求的所有重传 (第4.2节), 以便它不必维护消息ID的状态. 例如, 如果重复处理所产生的工作量比跟踪先前响应所需的工作量便宜, 则实现可能希望将GET, PUT或DELETE请求的重复传输作为单独的请求处理.
-
受限服务器甚至可能希望为某些非幂等请求放宽此要求, 如果应用程序语义使此权衡有利. 例如, 如果POST请求的结果只是在服务器上创建一些短期状态, 则多次对请求产生此工作量可能比跟踪先前传输的相同请求是否已经被处理更便宜.
接收者可能会在NON_LIFETIME (第4.8.2节) 内多次接收相同的不可确认消息 (由消息ID和源端点指示). 作为一般规则 (可以根据消息的特定语义放宽), 接收者应该静默忽略任何重复的不可确认消息, 并且应该仅处理消息中的任何请求或响应一次.
4.6. Message Size (消息大小)
虽然特定的链路层使CoAP消息保持足够小以适合其链路层数据包是有益的 (见第1节), 但这是实现质量的问题. CoAP规范本身仅提供消息大小的上限. 大于IP数据包的消息会导致不希望的数据包分片. CoAP消息, 适当封装, 应该适合单个IP数据包 (即, 避免IP分片), 并且 (通过适合一个UDP有效载荷) 显然需要适合单个IP数据报. 如果目标的路径MTU未知, 则应该假设IP MTU为1280字节; 如果对头部大小一无所知, 则消息大小的良好上限为1152字节, 有效载荷大小为1024字节.
实现注意: CoAP的消息大小参数选择与IPv6和当今大多数IPv4路径配合良好. (但是, 对于IPv4, 很难绝对确保没有IP分片. 如果在不寻常的网络上支持IPv4是一个考虑因素, 实现可能希望将自己限制在更保守的IPv4数据报大小, 例如576字节; 根据 [RFC0791], IPv4的IP MTU的绝对最小值低至68字节, 这将仅留下40字节减去UDP有效载荷的安全开销. 极度专注于此问题集的实现也可能设置IPv4 DF位并执行某种形式的路径MTU发现 [RFC4821]; 但是, 这在CoAP的现实用例中通常是不必要的.) 在许多受限网络中更重要的一种分片是适配层上的分片 (例如, 6LoWPAN L2数据包限制为127字节, 包括各种开销); 这可能促使实现在数据包大小上节约, 并在接近三位数的消息大小时转移到块传输 [BLOCK].
消息大小对于受限节点上的实现也非常重要. 许多实现将需要为传入消息分配缓冲区. 如果实现太受限而无法分配上述上限, 它可以对不使用DTLS安全性的消息应用以下实现策略: 接收到缓冲区太小的数据报的实现通常能够确定数据报的尾部是否被丢弃并检索初始部分. 因此, 至少CoAP头部和选项 (如果不是所有有效载荷) 可能适合缓冲区. 因此, 如果有效载荷被截断, 服务器可以完全解释请求并返回4.13 (请求实体太大; 见第5.9.2.9节) 响应码. 发送幂等请求并接收大于缓冲区容纳的响应的客户端可以使用块选项 [BLOCK] 的适当值重复请求.
4.7. Congestion Control (拥塞控制)
CoAP的基本拥塞控制由第4.2节中的指数退避机制提供.
为了不引起拥塞, 客户端 (包括代理) 必须严格限制它们维护到给定服务器 (包括代理) 的同时未完成交互的数量为NSTART. 未完成的交互是尚未收到ACK但仍在等待的CON (消息层), 或尚未收到响应或确认消息但仍在等待的请求 (这两者可能同时发生, 算作一次未完成的交互). 本规范的NSTART的默认值为1.
未来预计会有进一步的拥塞控制优化和考虑, 例如可能为第4.8节中定义的CoAP传输参数提供自动初始化, 从而可能允许NSTART的值大于1.
在EXCHANGE_LIFETIME之后, 客户端停止期望对未收到确认消息的可确认请求的响应. 客户端停止"期望"对已确认的可确认请求或对不可确认请求的响应的特定算法未定义. 除非通过其他拥塞控制优化进行修改, 否则必须选择这样的方式, 使得端点在发送到不响应的另一个端点时不超过PROBING_RATE的平均数据速率.
注意: CoAP将拥塞控制的责任主要放在客户端上. 但是, 客户端可能会出现故障或实际上是攻击者, 例如, 执行放大攻击 (第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 |
+-------------------+---------------+
表 2: CoAP协议参数
4.8.1. Changing the Parameters (更改参数)
ACK_TIMEOUT, ACK_RANDOM_FACTOR, MAX_RETRANSMIT, NSTART, DEFAULT_LEISURE (第8.2节) 和PROBING_RATE的值可以配置为特定于应用程序环境的值 (包括动态调整的值); 但是, 配置方法超出了本文档的范围. 建议应用程序环境对这些参数使用一致的值; 在应用程序环境中使用不一致值的具体效果超出了本规范的范围.
传输参数被选择为在存在拥塞的情况下实现在互联网中安全的行为. 如果配置希望使用不同的值, 则配置有责任确保不违反这些拥塞控制属性. 特别是, 将ACK_TIMEOUT降低到1秒以下将违反 [RFC5405] 的指导方针. ([RTO-CONSIDER] 提供了一些额外的背景.) CoAP旨在使不维护往返时间 (RTT) 测量的实现成为可能. 但是, 如果希望显著降低ACK_TIMEOUT或增加NSTART, 则只有在维护此类测量时才能安全地完成. 配置不得在不使用确保拥塞控制安全性的机制的情况下降低ACK_TIMEOUT或增加NSTART, 这些机制可以在配置中定义或在未来的标准文档中定义.
ACK_RANDOM_FACTOR不得降低到1.0以下, 并且它应该具有与1.0充分不同的值, 以提供一些对同步效应的保护.
MAX_RETRANSMIT可以自由调整, 但太小的值将降低实际接收可确认消息的概率, 而大于此处给出的值将需要对时间值进行进一步调整 (见第4.8.2节).
如果传输参数的选择导致派生时间值增加 (见第4.8.2节), 则配置机制必须确保调整后的值也可用于要使用这些调整后的值进行通信的所有端点.
4.8.2. Time Values Derived from Transmission Parameters (从传输参数派生的时间值)
ACK_TIMEOUT, ACK_RANDOM_FACTOR和MAX_RETRANSMIT的组合影响重传的时间, 这反过来又影响实现需要保留某些信息项的时间. 为了能够明确引用这些派生的时间值, 我们给它们命名如下:
MAX_TRANSMIT_SPAN: 从可确认消息的第一次传输到其最后一次重传的最大时间. 对于默认传输参数, 该值为 (2+4+8+16)*1.5 = 45秒, 或更一般地:
ACK_TIMEOUT * ((2 ** MAX_RETRANSMIT) - 1) * ACK_RANDOM_FACTOR
MAX_TRANSMIT_WAIT: 从可确认消息的第一次传输到发送者放弃接收确认或重置的时间的最大时间. 对于默认传输参数, 该值为 (2+4+8+16+32)*1.5 = 93秒, 或更一般地:
ACK_TIMEOUT * ((2 ** (MAX_RETRANSMIT + 1)) - 1) * ACK_RANDOM_FACTOR
此外, 需要对网络和节点的特性做出一些假设.
MAX_LATENCY: 数据报从开始传输到完成接收预计花费的最大时间. 此常量与 [RFC0793] 的MSL (最大段生存时间) 相关, 后者"任意定义为2分钟" ([RFC0793] 术语表, 第81页). 请注意, 这不一定小于MAX_TRANSMIT_WAIT, 因为MAX_LATENCY不是为了描述协议正常工作的情况, 而是协议必须防范的最坏情况. 我们也任意地将MAX_LATENCY定义为100秒. 除了对大多数配置来说相当现实以及接近TCP的历史选择之外, 该值还允许消息ID生存时间定时器以8位表示 (以秒为单位测量时). 在这些计算中, 没有假设传输方向是无关的 (即, 网络是对称的); 只是假设相同的值可以合理地用作两个方向的最大值. 如果不是这种情况, 以下计算只会稍微复杂一些.
PROCESSING_DELAY: 节点将可确认消息转换为确认所需的时间. 我们假设节点将在发送者超时之前尝试发送ACK, 因此作为保守假设, 我们将其设置为等于ACK_TIMEOUT.
MAX_RTT: 最大往返时间, 或:
(2 * MAX_LATENCY) + PROCESSING_DELAY
从这些值, 我们可以派生出与协议操作相关的以下值:
EXCHANGE_LIFETIME: 从开始发送可确认消息到不再期望确认的时间, 即, 可以清除有关消息交换的消息层信息. 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秒.
NON_LIFETIME: 从发送不可确认消息到其消息ID可以安全重用的时间. 如果不使用NON消息的多次传输, 其值为MAX_LATENCY, 或100秒. 但是, CoAP发送者可能会多次发送NON消息, 特别是对于组播应用程序. 虽然规范未限制重用期, 但接收者可靠检测重复的期望是在MAX_TRANSMIT_SPAN的时间尺度上. 因此, 出于此目的, 使用以下值更安全:
MAX_TRANSMIT_SPAN + MAX_LATENCY
或使用默认传输参数为145秒; 但是, 只想使用单个超时值来废弃消息ID的实现可以安全地使用EXCHANGE_LIFETIME的较大值.
表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 |
+-------------------+---------------+
表 3: 派生的协议参数