在固定宽度消息计数器中检测环绕的好方法是什么?
我正在编写一个客户端应用程序来通过 UDP 与服务器程序进行通信。客户端定期发出数据请求,并需要使用最新的服务器响应。请求消息有一个由服务器回显的 16 位无符号计数器字段,因此我可以将请求与服务器响应配对。
由于它是 UDP,我必须处理服务器响应无序到达(或根本不到达)的情况。天真地,这意味着保留迄今为止看到的最高消息计数器并丢弃任何具有较低数字的传入消息。但是,一旦我们传递 65535 条消息并且计数器回滚到零,该操作就会失败。是否有一个好的方法来检测(以合理的概率)消息 5 实际上是在消息 65,000 之后出现的?
实现语言是C++。
I'm writing a client application to communicate with a server program via UDP. The client periodically makes requests for data and needs to use the most recent server response. The request message has a 16-bit unsigned counter field that is echoed by the server so I can pair requests with server responses.
Since it's UDP, I have to handle the case where server responses arrive out of order (or don't arrive at all). Naively, that means holding on to the highest message counter seen so far and dropping any incoming message with a lower number. But that will fail as soon as we pass 65535 messages and the counter wraps back to zero. Is there a good way to detect (with reasonable probability) that, for example, message 5 actually comes after message 65,000?
The implementation language is C++.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
处理这个问题的常用方法是进行模减法。如果 & 则序列号
b
位于序列号a
之后。仅当(a - b) % 65536
大于(b - a) % 65536
时。对于您的示例,
(65000 - 5) % 65536
是 64995,(5 - 65000) % 65536
是 541,因此序列号 5 在序列号 65000 之后。The usual way to handle this is do to a modulo subtraction. Sequence number
b
is after sequence numbera
if & only if(a - b) % 65536
is greater than(b - a) % 65536
.For your example,
(65000 - 5) % 65536
is 64995 and(5 - 65000) % 65536
is 541, so sequence number 5 is after sequence number 65000.如果我理解正确的话,你也许可以使用一些窗口(如果你还没有的话)。
也就是说,不要接受窗口之外的消息。例如,如果您的计数器为 1000,您可以将接受的传入计数器 ID 范围限制为 1000...1031(含)。因此,超出该范围的任何内容对于您来说都难以处理(迫使您启动一些重新发送协议)。一旦你得到 1000,你的上限将变为 1032。然后,一旦你得到 1001 的下限计数器,你的上限将是 1033,依此类推。你最终得到的是一个滑动窗口。
如果这种情况可以接受,那么您只需检查您的收货计数器 ID 是否在可接受的窗口内。这将是 16 位计数器的子集,因此您可以测试...
传入计数器 ID - lowerLimitCount < windowSize
只要您处理的是无符号变量,就应该没问题。
希望这有帮助(并且有意义)。
If I understand things correctly, you may be able to use some windowing, if you are not already.
That is, do not accept an message that is outside your window. For example, if your counter is at 1000, you may limit your range of incoming counter IDs you will accept to 1000...1031 inclusive. Thus anything outside that range is too much for you to handle (forcing you to initiate some resend protocol). Once you get 1000, your upper limit will go to 1032. Then once you get your lower limit counter of 1001, your upper limit will be 1033, and so on. What you end up with is a sliding window.
If such a scenario is acceptable, then all you have to check is that your incoming counter ID is within your accepted window. This will be a subset of the 16 bit counter, so you can test ...
incomingCounterID - lowerLimitCount < windowSize
As long as you are dealing with unsigned variables, you should be fine.
Hope this helps (and makes sense).
您的问题很久以前就在 RFC1982 中得到了解答,其中说明您是否有未签名的序列号 i1 和i2,如果 i1 <> 则适当的测试是
i1 小于 i2。 i2,并且
(或使用其反向大于测试)
对于 32 位无符号,2^(SERIAL_BITS - 1) = 2^31 = x80000000
Your problem was answered long ago in RFC1982 which says if you have unsigned serial numbers i1 and i2, the appropriate test is
i1 is less than i2 if i1 <> i2, and
(or use their reverse greater-than test)
For 32-bit unsigned, 2^(SERIAL_BITS - 1) = 2^31 = x80000000
我可以让客户端保存两件事:
对于每个请求,客户端都会递增其内部计数器并将其保存在列表中。在请求消息中,它将消息计数器设置为该数字模 65536。当响应消息传入时,客户端将遍历其待处理请求列表以查找匹配数字模 65536。如果该内部数字大于迄今为止的最高数字,该消息已被接受。这将环绕问题推迟到 40 亿左右。伪代码如下。
I could have the client save two things:
With each request, the client increments its internal counter and saves it in the list. In the request message, it sets the message counter to this number modulo 65536. When a response message comes in, the client walks its list of pending requests for a matching number modulo 65536. If this internal number is greater than the highest so far, the message is accepted. This delays the wrap-around problem to around 4 billion. Pseudocode follows below.
这主要是所说的内容的组合,所以不要选择我作为答案,但既然你知道
你所要求的内容(请求和请求编号)
您收到了什么
距离您发出请求已有多长时间
对特定请求的响应应该是什么样子
对
您应该能够让一个相当好的系统运行。
从请求提出者/响应接受者方面:
永远不要接受对您尚未提出或很久以前提出的请求的响应,以免您认为该请求已过时。应删除此类响应中的数据。仅接受预期请求的第一个响应,并在接受时将请求编号标记为非预期。 (处理程序 (*f)() 是实现此目的的好方法)
无论何时收到响应,您都应该记录与该请求编号相关的当前时间,以便在您开始回收请求编号时为您提供帮助。应对已接受和未接受的响应(在 1 中删除的响应)执行此操作。
不接受不属于所提出问题的合法数据的回复。如果收到这样的响应,请将其丢弃,并且如果您可以确定再次发出应该使用该请求代码的实际请求不会对另一台计算机造成不良副作用,那么您可能需要使用新请求重复该请求代码(如果有足够长的时间未受影响 - 请参阅 4)并将旧的请求代码标记为不期望的。
从您在最长时间内未看到回复的请求号中选择您的请求号。如果最近最少使用的请求代码太年轻,则记录事件,但尝试继续工作。将所有未使用的请求号视为很久以前使用过。
在请求发送代码之前调出您的响应侦听代码,以便它可以查看是否有任何旧响应从该程序上次运行的管道中传入。
在请求接受者/响应制定者方面:
除非您非常快地发送大量请求,否则这应该可以很好地工作。这并不假设网络上有人想做坏事。在此设置上实施拒绝服务 (DOS) 是微不足道的。
This is mostly of a combination of what's been said, so don't choose me as the answer, but since you know
what you have asked for ( the request and request number )
what you have gotten
how long it has been since you have mad a request
what a response to a particular request should look like
you should be able to get a fairly good system going.
From the request maker/response taker side:
Never accept a response for a request you haven't made or made so long ago that you would consider it old. The data in such a response should be dropped. Only accept the first response of an expected request and mark the request number as not-expecting upon acceptance. (handler (*f)() is a good way to implement this)
Anytime a response is recieved you should record a the current time in association with that request number to help you when you start to recycle request numbers. This should be done for accepted and non-accepted responses (those dropped in 1).
Do not accept responces which are not legal data for the resuest made. If such a responce is received drop it and if you can determine that making the actual request that is supposed to be using that request code again will not cause bad side effects on the other machine then you may want to repeat the request with a new request code (if any are untouched for long enough -- see 4) and mark the older request code as not expecting.
Choose your request numbers from the ones that you haven't seen responces on in the longest amount of time. If the least recently used request code is too young log the event, but try to keep working. Treat all unused request numbers as having been used a long time ago.
Bring up your response listening code before your request sending code so that it can see if there are any old responses coming down the pipe from the last run of this program.
On the request taker/response maker side:
This should work fairly well unless you are sending out a really high number of requests very fast. This does not assume that there is anyone on the network that wants to do bad things. It would be trivial to implement a denial of service (DOS) on this setup.