Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 631
cqcallaw Avatar asked Sep 28 '14 02:09

cqcallaw


2 Answers

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.

like image 54
Simon Fox Avatar answered Sep 19 '22 02:09

Simon Fox


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.

like image 38
OneOfOne Avatar answered Sep 21 '22 02:09

OneOfOne