【问题标题】:How do I read a UDP connection until a timeout is reached?在达到超时之前如何读取 UDP 连接?
【发布时间】:2014-11-22 17:38:00
【问题描述】:

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

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
        }
    }
}

为什么给定的程序没有终止?基于https://gobyexample.com/selecthttps://gobyexample.com/timeoutshttps://gobyexample.com/non-blocking-channel-operations,我希望上面的代码选择默认情况一秒钟,然后采用第一种情况并跳出循环。我该如何修改上面的sn-p,以达到预期的循环读取效果,直到发生超时?

【问题讨论】:

  • 如果您将break 更改为return,该功能将在持续时间完成时完成。您目前正在永远循环并每秒打印Finished
  • @chendesheng 我相信 time.After 的使用是惯用的(见golang.org/pkg/time/#example_After)感谢你们两位对break语句的catch;我已经更新了sn-p。不过,程序仍然没有终止
  • @chendesheng 我忘了说:我确实尝试使用 time.Tick 代替,但仍然出现无限循环行为
  • 使用时间我错了。打勾,也无济于事。而 OneOfOne 的答案是正确的。

标签: concurrency go network-programming udp


【解决方案1】:

如果您不担心超过n 秒的读取阻塞,则循环直到截止日期:

 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")

如果您确实想在 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() {
           // handle error, it's not a timeout
       }
       break
   }
   // do something with packet here
}

使用截止日期并不难。标准库在读取 UDP 连接时使用截止日期(请参阅dns client)。

除了使用截止日期来中断阻塞读取之外,还有其他方法:关闭连接或发送读取器识别的虚拟数据包。这些替代方案需要启动另一个 goroutine,并且比设置截止日期要复杂得多。

【讨论】:

  • 赞成针对特定问题的知识。您有标准库中使用截止日期方法的示例吗?
  • 我希望可以将多个答案标记为回答问题!我觉得 OneOfOne 的回答仍然最接近地回答了所提出的问题,但您的回答在实践中更有用。感谢您提供具体问题的详细信息。
【解决方案2】:

只需在for 循环之外从time.After 分配通道,否则每次循环时只需创建一个新计时器。

例子:

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
        }
    }
}

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

【讨论】:

  • 这行得通!但为什么有必要?是否为循环的每次迭代评估 time.After 表达式?
  • @cqcallaw 是的,您正在为每个循环创建一个新计时器。
  • 有趣。那么,为什么这段代码会按预期运行呢?注意“case gobyexample.com/timeouts
  • @cqcallaw 在该示例中它会阻塞,直到计时器用完或通道返回某些内容,将“默认”添加到 select 使其成为非阻塞。
猜你喜欢
  • 2012-11-03
  • 2011-12-05
  • 1970-01-01
  • 2021-07-20
  • 2021-12-08
  • 2014-07-07
  • 1970-01-01
  • 1970-01-01
  • 2020-02-01
相关资源
最近更新 更多