yikegaya’s blog

仕事関連(Webエンジニア)と資産運用について書いてます

「Goならわかるシステムプログラミング」の7章を読んだ(UDP)

引き続き「Goならわかるシステムプログラミング」の7章を写経しつつ読んでみる。

前章はTCPだったけどこの章はUDPTCPより機能が少なくシンプルなプロトコル

UDPTCPと同じトランスポート層プロトコルですが、TCPと違ってコネクションレスであり、誰とつながっているかは管理しません。 プロトコルとしてデータロスの検知をすることも、通信速度の制限をすることもなく、一方的にデータを送りつけるのに使われます。 パケットの到着順序も管理しません

サーバ側実装例

package main

import (
    "fmt"
    "net"
)

func main() {
    fmt.Println("Server is running at localhost:8888")
    conn, err := net.ListenPacket("udp", "localhost:8888")
    if err != nil {
        panic(err)
    }
    defer conn.Close()

    buffer := make([]byte, 1500)
    for {
        length, remoteAddress, err := conn.ReadFrom(buffer)
        if err != nil {
            panic(err)
        }
        fmt.Printf("Received from %v: %v\n", remoteAddress, string(buffer[:length]))
        _, err = conn.WriteTo([]byte("Hello from Server"), remoteAddress)
        if err != nil {
            panic(err)
        }
    }
}

net.ListenPacket()でUDP接続の待ち受けができる。

net.Listen()やnet.ListenPacket()、net.Dial()は、 プロトコルの種類を文字列で指定するだけで具体的なインタフェースを隠して通信を 抽象的に書くためのインタフェースです。

クライアント側

package main

import (
    "fmt"
    "net"
)

func main() {
    conn, err := net.Dial("udp4", "localhost:8888")
    if err != nil {
        panic(err)
    }
    defer conn.Close()
    fmt.Println("Sending to server")
    _, err = conn.Write([]byte("Hello From Client"))
    if err != nil {
        panic(err)
    }
    fmt.Println("Receiving from server")
    buffer := make([]byte, 1500)
    length, err := conn.Read(buffer)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Received: %s\n", string(buffer[:length]))
}

マルチキャストの実装

マルチキャストは、リクエスト側の負担を増やすことなく多くのクライアントに同 時にデータを送信できる仕組みです。マルチキャストUDPならではの機能なので、 次はGo言語でマルチキャストサーバーとクライアントを作ってみましょう。

それでは Go でマルチキャストを利用するコードを見てみましょう。例題として 117の時報のようなサービスを実装しました

サーバサイド

package main

import (
    "fmt"
    "net"
    "time"
)

const interval = 10 * time.Second

func main() {
    fmt.Println("Start tick server at 224.0.0.1:9999")
    conn, err := net.Dial("udp", "224.0.0.1:9999")
    if err != nil {
        panic(err)
    }
    defer conn.Close()
    start := time.Now()
    wait := start.Truncate(interval).Add(interval).Sub(start)
    time.Sleep(wait)
    ticker := time.Tick(interval)
    for now := range ticker {
        conn.Write([]byte(now.String()))
        fmt.Println("Tick: ", now.String())
    }
}

クライアントサイド

net.ResolveUDPAddr関数でパースしてnet.ListenMulticastUDP()でソケットを開いてサーバのソケットを待ち受ける。

package main

import (
    "fmt"
    "net"
)

func main() {
    fmt.Println("Listen tick server at 224.0.0.1:9999")
    address, err := net.ResolveUDPAddr("udp", "224.0.0.1:9999")
    if err != nil {
        panic(err)
    }
    listener, err := net.ListenMulticastUDP("udp", nil, address)
    defer listener.Close()

    buffer := make([]byte, 1500)

    for {
        length, remoteAddress, err := listener.ReadFromUDP(buffer)
        if err != nil {
            panic(err)
        }
        fmt.Printf("Server %v\n", remoteAddress)
        fmt.Printf("Now %s\n", string(buffer[:length]))
    }
}