英文:
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 (
"fmt"
"time"
)
func main() {
for {
select {
case <-time.After(time.Second * 1):
fmt.Printf("Finished listening.\n")
return
default:
fmt.Printf("Listening...\n")
//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("Listening...\n")
//read from UDPConn here
}
fmt.Printf("Finished listening.\n")
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'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 <-ch:
fmt.Printf("Finished listening.\n")
break L // have to use a label or it will just break select
default:
fmt.Printf("Listening...\n")
//read from UDPConn here
}
}
}
Note that this doesn't work on the playground.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论