在固定宽度的消息计数器中检测环绕的好方法是什么?

时间:2010-04-07 17:04:52

标签: c++ c udp integer overflow

我正在编写一个客户端应用程序,通过UDP与服务器程序进行通信。客户端定期发出数据请求,并需要使用最新的服务器响应。请求消息有一个16位无符号计数器字段,由服务器回显,因此我可以将请求与服务器响应配对。

由于它是UDP,我必须处理服务器响应无序到达(或根本没有到达)的情况。天真地,这意味着保持到目前为止看到的最高消息计数器并丢弃任何具有较低数字的传入消息。但是,只要我们传递65535条消息并且计数器回零,那就会失败。有没有一种很好的方法来检测(有合理的可能性),例如,消息5实际上是消息65,000之后的

实现语言是C ++。

5 个答案:

答案 0 :(得分:5)

处理此问题的常用方法是进行模减法。序列号a位于序列号b之后,如果&仅当(a - b) % 65536大于(b - a) % 65536

对于您的示例,(65000 - 5) % 65536为64995,(5 - 65000) % 65536为541,因此序列号5位于序列号65000之后。

答案 1 :(得分:4)

如果我理解正确,你可以使用一些窗口,如果你还没有。

也就是说,不要接受窗外的消息。例如,如果您的计数器为1000,则可以将您接受的传入计数器ID的范围限制为1000 ... 1031(含)。因此,超出该范围的任何内容都是您无法处理的(强制您启动某些重新发送协议)。一旦你得到1000,你的上限将变为1032.然后一旦你得到1001的下限计数器,你的上限将是1033,依此类推。你最终得到的是一个滑动窗口。

如果这种情况可以接受,那么您需要检查的是您的传入计数器ID是否在您接受的窗口内。这将是16位计数器的子集,因此您可以测试...

incomingCounterID - lowerLimitCount< windowSize

只要您处理无符号变量,就应该没问题。

希望这有帮助(并且有意义)。

答案 2 :(得分:3)

很久以前在RFC1982中回答了您的问题,如果您有未签名的序列号i1和i2,那么相应的测试就是

如果i1<>,则i1小于i2。 i2,和

    (i1 < i2 and (i2 - i1) < 2^(SERIAL_BITS - 1)) or
    (i1 > i2 and (i1 - i2) > 2^(SERIAL_BITS - 1))

(或使用他们的反向大于测试)

对于32位无符号,2 ^(SERIAL_BITS - 1)= 2 ^ 31 = x80000000

答案 3 :(得分:0)

我可以让客户端保存两件事:

  • 无符号32位内部消息计数器
  • 待处理服务器请求列表

对于每个请求,客户端递增其内部计数器并将其保存在列表中。在请求消息中,它将消息计数器设置为此数字模数65536.当响应消息进入时,客户端遍历其匹配数量模65536的待处理请求列表。如果此内部数字大于目前为止的最大值,消息被接受。这将环绕问题延迟到大约40亿。伪代码如下所示。

unsigned long internalCounter = 0;
unsigned long highestSoFar = 0;

// On message send...
++internalCounter;
message.counter = internalCounter % 65536;
pendingList.push_back(internalCounter);

// On message receive...
for (i = pendingList.begin(); i != pendingList.end(); ++i)
{
    if (*i % 65536 == message.counter && *i > highestSoFar)
    {
        highestSoFar = *i;
        pendingList.remove(i);
        process(message);
        break;
    }
}

答案 4 :(得分:0)

这主要是所说内容的组合,所以不要选择我作为答案,但是因为你知道

  1. 您要求的内容(请求和请求编号)

  2. 你得到了什么

  3. 自你发出请求以来已经过了多长时间

  4. 对特定请求的回复应该是什么样的

  5. 你应该能够获得一个相当不错的系统。

    来自请求者/响应者:

    1. 永远不要接受您很久以前未提出或提出的请求的回复。应该删除这种响应中的数据。只接受预期请求的第一个响应,并将请求号标记为接受时不期望。 (handler(* f)()是实现这个的好方法)

    2. 无论何时收到回复,您都应记录与该请求号关联的当前时间,以便在您开始回收请求号时为您提供帮助。这应该针对已接受和未接受的答复(1中的答案)进行。

    3. 不接受对所作出的不是合法数据的回复。如果接收到这样的响应,则将其丢弃并且如果您可以确定再次使用该请求代码的实际请求不会对另一台计算机造成不良副作用,那么您可能希望使用新请求重复该请求代码(如果有任何未触及的时间足够长 - 请参阅4)并将旧请求代码标记为不期望。

    4. 从您在最长时间内没有看到的请求中选择您的请求编号。如果最近最少使用的请求代码太小,则记录该事件,但尝试继续工作。将所有未使用的请求编号视为很久以前使用过的。

    5. 在您的请求发送代码之前打开您的响应侦听代码,以便它可以查看上次运行此程序时是否有任何旧响应。

    6. 在请求者/响应制造者方面:

      1. 如果使用请求编号发出任何请求,您正在处理记录事件的响应,并仅为新响应提供服务。
      2. 除非您非常快速地发送大量请求,否则这应该可以正常工作。这并不假设网络上有任何人想要做坏事。在这种设置上实现拒绝服务(DOS)将是微不足道的。