代码之家  ›  专栏  ›  技术社区  ›  Michael Kristofik

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

  •  2
  • Michael Kristofik  · 技术社区  · 15 年前

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

    因为它是UDP,所以我必须处理服务器响应无序到达(或者根本没有到达)的情况。天真地说,这意味着要保持到目前为止看到的最高消息计数器,并丢弃任何数量较低的传入消息。但一旦我们传递65535条消息,计数器就会回到零位,这就失败了。是否有一种好的方法来检测(以合理的概率)消息5是否真的会出现 之后 信息65000?

    实现语言是C++。

    5 回复  |  直到 12 年前
        1
  •  4
  •   Sparky    15 年前

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

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

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

    incomingCounterID-lowerLimitCount<windowsize

    只要处理无符号变量,就可以了。

    希望这能有所帮助(而且是有道理的)。

        2
  •  5
  •   caf    15 年前

    处理这个问题的通常方法是做模减法。序列号 a 在序列号之后 b 仅当 (a - b) % 65536 大于 (b - a) % 65536 .

    例如, (65000 - 5) % 65536 是64995和 (5 - 65000) % 65536 是541,所以序列号5在序列号65000之后。

        3
  •  3
  •   joe snyder    12 年前

    你的问题很久以前就在 RFC1982 如果您有未签名的序列号I1和I2,则适当的测试是

    如果I1<gt;I2,则I1小于I2,并且

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

    (或使用其反向大于测试)

    对于32位无符号,2^(串行位-1)=2^31=x8000000

        4
  •  0
  •   Michael Kristofik    15 年前

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

    • 无符号32位内部消息计数器
    • 挂起的服务器请求列表

    对于每个请求,客户机增加其内部计数器并将其保存在列表中。在请求消息中,它将消息计数器设置为模块65536。当响应消息出现时,客户机将遍历其等待的请求列表,以获取匹配的编号modulo 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;
        }
    }
    
        5
  •  0
  •   nategoose    15 年前

    这主要是一个组合的话,所以不要选择我作为答案,但既然你知道

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

    2. 你得到了什么

    3. 你发疯要多久了

    4. 对特定请求的响应应该是什么样子的

    你应该能够让一个相当好的系统运行。

    请求方/响应方:

    1. 永远不要接受对你很久以前没有提出或提出的请求的回应,因为你会认为它已经过时了。应删除此类响应中的数据。只接受预期请求的第一个响应,并在接受时将请求编号标记为不期望。(处理程序(*f)()是实现此功能的好方法)

    2. 每次收到响应时,您都应记录与该请求编号关联的当前时间,以便在开始回收请求编号时帮助您。这应该针对已接受和未接受的响应(那些落在1中的响应)进行。

    3. 不接受非合法数据的回复。如果收到这样的响应,请将其删除,如果您可以确定再次发出实际的请求不会对另一台计算机造成不良的副作用,那么您可能希望使用新的请求代码(如果有足够长的时间未被处理过,请参阅4)重复该请求,并将旧的请求代码标记为N。不期待。

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

    5. 在请求发送代码之前打开响应侦听代码,这样它就可以查看是否有任何旧的响应从该程序的最后一次运行的管道中流出。

    在请求者/响应者方面:

    1. 如果有任何请求使用的请求编号是您正在为记录事件的响应而处理的,则只为新的响应提供服务。

    除非您以非常快的速度发送大量请求,否则这应该可以相当好地工作。这并不意味着网络上有任何人想做坏事。在这个设置上实现拒绝服务(DoS)是很简单的。