建立UDP server

UDP 介紹

不保證訊息傳遞到目的地。

        .---> HELLO!! -->-.
        |                 |
client -*                 *--> server --.
                                        |
                                        |
            whoops, lost! -----<--------*

TIME    DESCRIPTION

t0      client and server exist

          client                         server
          10.0.0.1                       10.0.0.2


t1      client sends a message to the server

          client      ------------>      server
          10.0.0.1         msg           10.0.0.2
                       (from:10.0.0.1)
                       (to:  10.0.0.2)


t2      server receives the message, then it takes
        the address of the sender and then prepares
        another message with the same contents and
        then writes it back to the client

          client      <------------      server
          10.0.0.1         msg2          10.0.0.2
                       (from:10.0.0.1)
                       (to:  10.0.0.2)


t3      client receives the message

          client                         server
          10.0.0.1                       10.0.0.2

           thxx!! :D :D


ps.: ports omitted for brevity

UDP Server

/ maxBufferSize specifies the size of the buffers that
// are used to temporarily hold data from the UDP packets
// that we receive.
const maxBufferSize = 1024

// server wraps all the UDP echo server functionality.
// ps.: the server is capable of answering to a single
// client at a time.
func server(ctx context.Context, address string) (err error) {
    // ListenPacket provides us a wrapper around ListenUDP so that
    // we don't need to call `net.ResolveUDPAddr` and then subsequentially
    // perform a `ListenUDP` with the UDP address.
    //
    // The returned value (PacketConn) is pretty much the same as the one
    // from ListenUDP (UDPConn) - the only difference is that `Packet*`
    // methods and interfaces are more broad, also covering `ip`.
    pc, err := net.ListenPacket("udp", address)
    if err != nil {
        return
    }

    // `Close`ing the packet "connection" means cleaning the data structures
    // allocated for holding information about the listening socket.
    defer pc.Close()

    doneChan := make(chan error, 1)
    buffer := make([]byte, maxBufferSize)

    // Given that waiting for packets to arrive is blocking by nature and we want
    // to be able of canceling such action if desired, we do that in a separate
    // go routine.
    go func() {
        for {
            // By reading from the connection into the buffer, we block until there's
            // new content in the socket that we're listening for new packets.
            //
            // Whenever new packets arrive, `buffer` gets filled and we can continue
            // the execution.
            //
            // note.: `buffer` is not being reset between runs.
            //    It's expected that only `n` reads are read from it whenever
            //    inspecting its contents.
            n, addr, err := pc.ReadFrom(buffer)
            if err != nil {
                doneChan <- err
                return
            }

            fmt.Printf("packet-received: bytes=%d from=%s\n",
                n, addr.String())

            // Setting a deadline for the `write` operation allows us to not block
            // for longer than a specific timeout.
            //
            // In the case of a write operation, that'd mean waiting for the send
            // queue to be freed enough so that we are able to proceed.
            deadline := time.Now().Add(*timeout)
            err = pc.SetWriteDeadline(deadline)
            if err != nil {
                doneChan <- err
                return
            }

            // Write the packet's contents back to the client.
            n, err = pc.WriteTo(buffer[:n], addr)
            if err != nil {
                doneChan <- err
                return
            }

            fmt.Printf("packet-written: bytes=%d to=%s\n", n, addr.String())
        }
    }()

    select {
    case <-ctx.Done():
        fmt.Println("cancelled")
        err = ctx.Err()
    case err = <-doneChan:
    }

    return
}

Client 端程式碼

// client wraps the whole functionality of a UDP client that sends
// a message and waits for a response coming back from the server
// that it initially targetted.
func client(ctx context.Context, address string, reader io.Reader) (err error) {
    // Resolve the UDP address so that we can make use of DialUDP
    // with an actual IP and port instead of a name (in case a
    // hostname is specified).
    raddr, err := net.ResolveUDPAddr("udp", address)
    if err != nil {
        return
    }

    // Although we're not in a connection-oriented transport,
    // the act of `dialing` is analogous to the act of performing
    // a `connect(2)` syscall for a socket of type SOCK_DGRAM:
    // - it forces the underlying socket to only read and write
    //   to and from a specific remote address.
    conn, err := net.DialUDP("udp", nil, raddr)
    if err != nil {
        return
    }

    // Closes the underlying file descriptor associated with the,
    // socket so that it no longer refers to any file.
    defer conn.Close()

    doneChan := make(chan error, 1)

    go func() {
        // It is possible that this action blocks, although this
        // should only occur in very resource-intensive situations:
        // - when you've filled up the socket buffer and the OS
        //   can't dequeue the queue fast enough.
        n, err := io.Copy(conn, reader)
        if err != nil {
            doneChan <- err
            return
        }

        fmt.Printf("packet-written: bytes=%d\n", n)

        buffer := make([]byte, maxBufferSize)

        // Set a deadline for the ReadOperation so that we don't
        // wait forever for a server that might not respond on
        // a resonable amount of time.
        deadline := time.Now().Add(*timeout)
        err = conn.SetReadDeadline(deadline)
        if err != nil {
            doneChan <- err
            return
        }

        nRead, addr, err := conn.ReadFrom(buffer)
        if err != nil {
            doneChan <- err
            return
        }

        fmt.Printf("packet-received: bytes=%d from=%s\n",
            nRead, addr.String())

        doneChan <- nil
    }()

    select {
    case <-ctx.Done():
        fmt.Println("cancelled")
        err = ctx.Err()
    case err = <-doneChan:
    }

    return
}