いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-284 (net.Dialでcontext.Contextを渡してタイムアウト付きで接続する)

概要

以下、自分用のメモです。

TCP通信やUDP通信を行う際に net.Dial を使って接続すると思います。

この関数をそのまま使うと接続タイムアウトが指定出来ません。

んで、タイムアウト付きで接続処理をしたい場合は以下の選択肢があります。

  • net.Dialerを使ってDialContextを呼び出し
  • net.DefaultResolver().Dialを呼び出し
  • net.DialTimeoutを呼び出し
  • net.DialerのTimeoutを指定して呼び出し

実際にソース見たほうが分かりやすいと思いますので、以下に自分のメモ代わりに記載します。

サンプル

package main

import (
    "context"
    "fmt"
    "log"
    "net"
    "os"
    "syscall"
    "time"
)

func main() {
    var (
        proto   = "tcp"
        addr    = net.JoinHostPort("localhost", "33333")
        backlog = 1
    )

    listener, err := listen(addr, backlog)
    if err != nil {
        panic(err)
    }
    defer listener.Close()

    // タイムアウトさせるために、わざと詰まらせる
    // (バックログをフルにする)
    net.Dial(proto, addr)
    net.Dial(proto, addr)

    //
    // Contextを指定したい場合は net.Dialer or net.DefaultResolver().Dial を使う
    //
    var (
        connLog  = log.New(os.Stdout, "[With Context] ", log.Ltime)
        ctx, cxl = context.WithTimeout(context.Background(), 3*time.Second)
        dialer   = net.Dialer{}
    )
    defer cxl()

    connLog.Println("start")
    {
        _, err = dialer.DialContext(ctx, proto, addr)
        if err != nil {
            connLog.Println(err)
        }
    }
    connLog.Println("done")

    //
    // net.DialTimeout を使うという方法もある
    //
    connLog = log.New(os.Stdout, "[DialTimeout] ", log.Ltime)

    connLog.Println("start")
    {
        _, err = net.DialTimeout(proto, addr, 2*time.Second)
        if err != nil {
            connLog.Println(err)
        }
    }
    connLog.Println("done")
}

// listen は、指定された情報で net.Listener を生成して返します.
//
// Go の net.Listen は、バックログが指定できないため
// テスト目的でバックログを極端に少ない状態にすることが出来ない。
// 利用される値は linux の場合は net.core.somaxconn の値であり
// 存在しない場合は、 syscall.SOMAXCONN の定数値が利用される。
//
// なので、バックログを指定したい場合はソケットを直接生成して
// net.FileListener に紐づけて、リスナーとして利用する。
//
// # REFERENCES
//   - https://github.com/golang/go/issues/39000
//   - https://github.com/golang/go/issues/41470
//   - https://github.com/valyala/tcplisten/blob/master/tcplisten.go
//   - https://stackoverflow.com/a/49593356
//   - https://stackoverflow.com/a/46279623
//   - https://meetup-jp.toast.com/1509
func listen(addr string, backLog int) (net.Listener, error) {
    // make tcp addr
    var (
        tcpAddr *net.TCPAddr
        err     error
    )

    tcpAddr, err = net.ResolveTCPAddr("tcp4", addr)
    if err != nil {
        return nil, err
    }

    // make socket addr
    var (
        sockAddr syscall.SockaddrInet4
    )

    sockAddr.Port = tcpAddr.Port
    copy(sockAddr.Addr[:], tcpAddr.IP.To4())

    // make socket file descriptor
    var (
        sockFd     int
        sockDomain = syscall.AF_INET
        sockType   = syscall.SOCK_STREAM | syscall.SOCK_NONBLOCK | syscall.SOCK_CLOEXEC
        sockProto  = syscall.IPPROTO_TCP
    )

    sockFd, err = syscall.Socket(sockDomain, sockType, sockProto)
    if err != nil {
        return nil, err
    }

    // bind
    err = syscall.Bind(sockFd, &sockAddr)
    if err != nil {
        return nil, err
    }

    // listen
    err = syscall.Listen(sockFd, backLog)
    if err != nil {
        return nil, err
    }

    // make net.Listener
    var (
        fname    = fmt.Sprintf("backlog.%d.%s.%s", os.Getpid(), "tcp4", addr)
        file     = os.NewFile(uintptr(sockFd), fname)
        listener net.Listener
    )
    defer file.Close()

    listener, err = net.FileListener(file)
    if err != nil {
        return nil, err
    }

    return listener, nil
}

実行すると以下のようになります。

gitpod /workspace/try-golang (master) $ task -d examples/socket/tcp_03_dial_with_context/
task: [default] go run main.go
[With Context] 16:42:32 start
[With Context] 16:42:35 dial tcp [::1]:33333: connect: connection refused
[With Context] 16:42:35 done
[DialTimeout] 16:42:35 start
[DialTimeout] 16:42:37 dial tcp [::1]:33333: connect: connection refused
[DialTimeout] 16:42:37 done

ちゃんと指定したリミットでタイムアウトとなっていますね。

参考情報

stackoverflow.com

stackoverflow.com

github.com

Go言語による並行処理

Go言語による並行処理

Amazon


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。