概要
以下、自分用のメモです。
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
ちゃんと指定したリミットでタイムアウトとなっていますね。
参考情報
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。