如何在C语言中清空UDP套接字的输入缓冲区?

6
如何在C中清除UDP套接字的输入缓冲区(如果这样的东西存在的话)?
我正在嵌入式Linux环境下工作,使用C语言创建一些本地应用程序。网络上有几台这样的嵌入式设备,并且当其中一台设备发生事件(假设它是“吹哨人”)时,“吹哨人”应该向网络广播地址发送网络消息,以便网络上的所有机器(包括“吹哨人”)都知道该事件并根据其执行一些操作。顺便提一句,我正在使用UDP套接字...
以下是伪代码:
main
{
    startNetworkListenerThread( networkListenerFunction );

    while( not received any SIGTERM or such )
    {
        localEventInfo = checkIfTheLocalEventOccured();
        broadcastOnNetwork( localEventInfo );
    }
}

networkListenerFunction
{
    bindSocket;

    while( not SIGTERM )
    {
// THIS IS WHERE I WANT TO FLUSH THE RECV BUFFER...
        recv_data = recvfrom( socket );
        if( validate recv data )
        {
            startExecuteLocalAction;
            sleep( 5 );
            stopExecuteLocalAction;
        }
    }
}

我希望这段代码能够按照以下方式工作:

1. LOCAL_EVENT occured
2. Broadcasted LOCAL_EVENT_INFO on network
3. All machines received EVENT_INFO, including the original broadcaster
4. All machines started executing the local action, including the original broadcaster
5. All machines' network listener(thread)s are sleeping
6. Another LOCAL_EVENT2 occured
7. Since all machines' listener are sleeping, LOCAL_EVENT2 is ignored
8. All machines' network listener(thread)s are now active again
9. GO BACK TO 1 / RESTART CYCLE
RESULT = TOTAL 2 EVENTS, 1 IGNORED

实际上,它的工作方式是:
1. LOCAL_EVENT occured
2. Broadcasted LOCAL_EVENT_INFO on network
3. All machines received EVENT_INFO, including the original broadcaster
4. All machines started executing the local action, including the original broadcaster
5. All machines' network listener(thread)s are sleeping
6. Another LOCAL_EVENT2 occured
7. Eventhough all machines' listener are sleeping; LOCAL_EVENT2 is queued  SOMEHOW
8. All machines' network listener(thread)s are now active again
9. All machines received EVENT_INFO2 and executed local actions again, slept and reactivated
10. GO BACK TO 1 / RESTART CYCLE
RESULT = TOTAL 2 EVENTS, 0 IGNORED

简述:已经绑定了socket的父线程正在休眠等待传递,发送到该socket的数据包/消息/UDP广播被以某种方式排队缓冲,并在下一次对该socket进行'recvfrom'调用时传递。

我希望忽略那些UDP广播,所以我想在调用recvfrom之前清空接收缓冲区(显然不是我作为参数提供给recvfrom方法的那个)。 我该如何做到这一点?或者我应该遵循哪条路径?

2个回答

4
请注意,“flushing” 的概念仅适用于输出。刷新会清空缓冲区并确保其中的所有内容都被发送到其目的地。关于输入缓冲区,数据已经到达其目的地。可以从输入缓冲区读取或清除数据,但不能“刷新”。
如果您只想确保已读取输入缓冲区中的所有内容,则需要进行非阻塞读取操作。如果尝试读取但没有输入,则应返回错误。

1
澄清一下:在你想要“刷新输入缓冲区”的时候,进行循环非阻塞读取。如果有数据,则忽略它并继续读取。如果没有数据(@bta所说的“返回错误”),则忽略返回的“错误”,这样你的缓冲区就被“刷新”了。 - mpez0
我的输入/接收缓冲区清除/刷新的想法和定位可能是错误的,但是我基本上想做的是在睡眠期间忽略发送的任何消息。目前我的代码使用/依赖于阻塞读取;为了进行循环非阻塞读取作为更干净的方法,我需要在非阻塞和阻塞之间来回设置套接字...对吗?因为我不希望也不能承受我的网络监听器不断轮询套接字。 - Cihan Keser
你是否有类似 select (http://linux.die.net/man/2/select) 的命令可用?如果是,您可以使用 select 判断是否有任何输入可用,并在需要时调用阻塞读取函数。如果您有这样的函数可用,我建议使用 select 和非阻塞 I/O(阻塞读取函数有时会内部实现为 select + 非阻塞读取)。这比阻塞 I/O 提供更多的灵活性和控制。 - bta
1
如果您只想在睡眠期间忽略I/O,则可以在睡眠期开始时设置条件变量,在睡眠结束时清除它。您的I/O线程可以在阻塞读取返回时检查condvar,如果设置了condvar,则可以丢弃数据并等待下一个输入。要使用您提供的代码执行此操作,您需要创建一个新线程,该线程是一个简单的I/O循环,并且在调用recvfrom时,您将调用一个函数,该函数将从I/O循环中检索数据(例如将其保留在内部队列中)。 - bta
如果您只想在睡眠期间忽略I/O操作...没错!但是,问题是我的睡眠期和I/O线程是同一个线程(请参见:OP中的networkListenerFunction)。虽然将它们分成两个线程并实现您的想法是可能的,但我很好奇这如何与Nikolai N Fetissov答案中的“在while结束时销毁套接字,在while开始时重新创建”思路相比较。 - Cihan Keser

1
一个套接字在 TCP/IP 协议栈内有一个单独的接收缓冲区。它本质上是接收到数据报的先进先出队列。但是,TCP 和 UDP 处理该队列的方式不同。当你在 UDP 套接字上调用recv(2) 时,你会从该缓冲区中出队一个数据报。而 TCP 则根据序列号将数据报排列成一个字节流。当接收缓冲区溢出时,数据报会被协议栈丢弃。在这种情况下,TCP 尝试重新发送,而 UDP 不会。接收缓冲区没有显式的“刷新”函数,除非读取套接字或关闭它。

编辑:

你的应用程序存在固有的竞态条件,看起来你试图用错误的工具(TCP/IP 协议栈)来解决它。我认为你应该定义一个清晰的状态机来处理应用程序。处理当前状态下有意义的事件,忽略无意义的事件。

另一件值得考虑的事情是使用多播而不是广播。这需要更多的操作,但是你可以通过加入/离开多播组来更好地控制“订阅”。


那么,在 while 循环结束时关闭/销毁套接字,然后在循环开始时重新创建/绑定它是否是一个好主意呢?对于一个拥有 200MHz 处理器的机器来说,在理论上无限循环中投入太多工作似乎不太划算...或者并非如此吗? - Cihan Keser
那得看情况。如果你关闭了套接字,那么在它被关闭的这段时间内,对于向该端口发送UDP数据包的其他发送者来说,他们有可能会收到ICMP错误消息。如果这不会对任何事情造成影响,可以尝试这样做。 - T.E.D.
所有机器都向网络广播地址发送消息,所以我猜它们不会收到那些错误消息,因为发送地址没有被明确定义...? - Cihan Keser
关闭和重新打开端口似乎不是解决问题的好方法。当您关闭端口时,会打开其他东西在您之前打开它的机会。此外,打开/关闭操作可能会产生很多开销。更不用说,这可能会对连接到该端口的任何远程设备造成问题。一旦您打开了端口,请在完全完成使用之前不要关闭它。 - bta

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接