如何在达到超时之前读取 UDP 连接?

huangapple go评论86阅读模式
英文:

How do I read a UDP connection until a timeout is reached?

问题

我需要读取UDP流量直到达到超时时间。我可以通过在UDPConn上调用SetDeadline并循环直到出现I/O超时错误来实现这一点,但这似乎有些hack-ish(基于错误条件的流控制)。下面的代码片段似乎更正确,但不会终止。在生产环境中,这显然会在goroutine中执行;为了简单起见,它被写成一个主函数。

package main

import (
	"fmt"
	"time"
)

func main() {
	for {
		select {
		case <-time.After(time.Second * 1):
			fmt.Printf("Finished listening.\n")
			return
		default:
			fmt.Printf("Listening...\n")
			// 在这里从UDPConn读取
		}
	}
}

为什么给定的程序不会终止?根据https://gobyexample.com/select、https://gobyexample.com/timeouts和https://gobyexample.com/non-blocking-channel-operations,我期望上述代码在一秒钟内选择默认情况,然后执行第一个case并跳出循环。我应该如何修改上面的代码片段以实现循环读取直到超时的效果?

英文:

I need to read UDP traffic until a timeout is reached. I can do this by calling SetDeadline on the UDPConn and looping until I get an I/O timeout error, but this seems hack-ish (flow control based on error conditions). The following code snippet seems more correct, but does not terminate. In production, this would obviously be executed in a goroutine; it's written as a main function for simplicity's sake.

package main

import (
	&quot;fmt&quot;
	&quot;time&quot;
)

func main() {
	for {
		select {
		case &lt;-time.After(time.Second * 1):
			fmt.Printf(&quot;Finished listening.\n&quot;)
			return
		default:
			fmt.Printf(&quot;Listening...\n&quot;)
			//read from UDPConn here
		}
	}
}

Why doesn't the given program terminate? Based on https://gobyexample.com/select, https://gobyexample.com/timeouts, and https://gobyexample.com/non-blocking-channel-operations, I would expect the above code to select the default case for one second, then take the first case and break out of the loop. How might I modify the above snippet to achieve the desired effect of looping and reading until a timeout occurs?

答案1

得分: 12

如果你不关心在过去的n秒内是否发生阻塞读取,那么可以循环直到截止时间:

deadline := time.Now().Add(n * time.Second)
for time.Now().Before(deadline) {
    fmt.Printf("Listening...\n")
    // 在这里从UDPConn读取
}
fmt.Printf("Finished listening.\n")

如果你想在n秒后中断阻塞读取,那么可以设置一个截止时间并一直读取直到出现错误:

conn.SetReadDeadline(time.Now().Add(n * time.Second))
for {
    n, err := conn.Read(buf)
    if err != nil {
        if e, ok := err.(net.Error); !ok || !e.Timeout() {
            // 处理错误,这不是超时
        }
        break
    }
    // 在这里处理数据包
}

使用截止时间并不是一种巧妙的方法。标准库在读取UDP连接时使用截止时间(参见dns client)。

还有其他方法可以中断阻塞读取:关闭连接或发送一个读取器可以识别的虚拟数据包。这些方法需要启动另一个goroutine,并且比设置截止时间要复杂得多。

英文:

If you are not concerned about read blocking past n seconds, then loop until the deadline:

 deadline := time.Now().Add(n * time.Second)
 for time.Now().Before(deadline) {
    fmt.Printf(&quot;Listening...\n&quot;)
    //read from UDPConn here
 }
 fmt.Printf(&quot;Finished listening.\n&quot;)

If you do want to break out of a blocking read after n seconds, then set a deadline and read until there's an error:

conn.SetReadDeadline(time.Now().Add(n * time.Second)
for {
   n, err := conn.Read(buf)
   if err != nil {
       if e, ok := err.(net.Error); !ok || !e.Timeout() {
           // handle error, it&#39;s not a timeout
       }
       break
   }
   // do something with packet here
}

Using a deadline is not hacky. The standard library uses deadlines while reading UDP connections (see the dns client).

There are alternatives to using a deadline to break a blocking read: close the connection or send a dummy packet that the reader recognizes. These alternatives require starting another goroutine and are much more complicated than setting a deadline.

答案2

得分: 4

只需将time.After的通道分配给for循环外部,否则每次循环都会创建一个新的计时器。

示例:

func main() {
    ch := time.After(time.Second * 1)
L:
    for {
        select {
        case <-ch:
            fmt.Printf("Finished listening.\n")
            break L // 必须使用标签,否则只会中断 select
        default:
            fmt.Printf("Listening...\n")
            // 在这里从 UDPConn 读取
        }
    }
}

请注意,这在 playground 上不起作用。

英文:

Simply assign the channel from time.After outside the for loop, otherwise you will just create a new timer each time you loop.

Example:

func main() {
	ch := time.After(time.Second * 1)
L:
	for {
		select {
		case &lt;-ch:
			fmt.Printf(&quot;Finished listening.\n&quot;)
			break L // have to use a label or it will just break select
		default:
			fmt.Printf(&quot;Listening...\n&quot;)
			//read from UDPConn here
		}
	}
}

Note that this doesn't work on the playground.

huangapple
  • 本文由 发表于 2014年9月28日 10:31:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/26081073.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定