関連記事
GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ
概要
以下、自分用のメモです。
通信処理にて、リスナを起動する際、C言語とかでは SO_REUSEADDR をセットすることが多いです。
で、Goではソケットが抽象化されているため、実際にセットされているのかどうかをちょっと調べてみました。(ついでに SO_REUSEPORTも)
環境は Linux (Ubuntu Linux 22.04.5) です。
$ cat /etc/os-release PRETTY_NAME="Ubuntu 22.04.5 LTS" NAME="Ubuntu" VERSION_ID="22.04" VERSION="22.04.5 LTS (Jammy Jellyfish)" VERSION_CODENAME=jammy ID=ubuntu ID_LIKE=debian HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" UBUNTU_CODENAME=jammy
サンプル
main.go
package main import ( "context" "flag" "fmt" "log" "net" "syscall" ) type ( Args struct { UseListenConfig bool } ) const ( SO_REUSEADDR = syscall.SO_REUSEADDR SO_REUSEPORT = 0xf ) var ( args Args ) func main() { log.SetFlags(log.Lmicroseconds) flag.BoolVar(&args.UseListenConfig, "listenconfig", false, "Use net.ListenConfig") flag.Parse() if err := run(); err != nil { panic(err) } } func run() error { var err error switch args.UseListenConfig { case true: err = runListenConfig() default: err = runNormal() } if err != nil { return err } return nil } func runListenConfig() error { lc := net.ListenConfig{ Control: func(network, address string, c syscall.RawConn) error { var ( reuseAddr, reusePort int opErr1, opErr2 error err error ) err = c.Control(func(fd uintptr) { reuseAddr, opErr1 = syscall.GetsockoptInt(int(fd), syscall.SOL_SOCKET, SO_REUSEADDR) reusePort, opErr2 = syscall.GetsockoptInt(int(fd), syscall.SOL_SOCKET, SO_REUSEPORT) }) if err != nil { return err } log.Printf("SO_REUSEADDR=%d", reuseAddr) log.Printf("SO_REUSEPORT=%d", reusePort) if opErr1 != nil { return opErr1 } if opErr2 != nil { return opErr2 } return nil }, } ln, err := lc.Listen(context.Background(), "tcp", ":8888") if err != nil { return err } defer ln.Close() return nil } func runNormal() error { ln, err := net.Listen("tcp", ":8888") if err != nil { return err } defer ln.Close() tcpLn, ok := ln.(*net.TCPListener) if !ok { return fmt.Errorf("not a TCP Listener") } file, err := tcpLn.File() if err != nil { return err } defer file.Close() var ( fd = file.Fd() v int ) v, err = syscall.GetsockoptInt(int(fd), syscall.SOL_SOCKET, SO_REUSEADDR) if err != nil { return err } log.Printf("SO_REUSEADDR=%d", v) v, err = syscall.GetsockoptInt(int(fd), syscall.SOL_SOCKET, SO_REUSEPORT) if err != nil { return err } log.Printf("SO_REUSEPORT=%d", v) return nil }
Taskfile.yml
# https://taskfile.dev version: '3' tasks: default: cmds: - cat /etc/os-release | head -n 1 - task: run run: cmds: - go run . -listenconfig - go run .
実行
$ task task: [default] cat /etc/os-release | head -n 1 PRETTY_NAME="Ubuntu 22.04.5 LTS" task: [run] go run . -listenconfig 09:02:50.949184 SO_REUSEADDR=1 09:02:50.949316 SO_REUSEPORT=0 task: [run] go run . 09:02:51.148105 SO_REUSEADDR=1 09:02:51.148219 SO_REUSEPORT=0
SO_REUSEADDR でデフォルトで設定されてて、SO_REUSEPORTについて設定されていないという結果でした。
なので、GoでLinuxでリスナを作って処理する場合、リスナ終了後にTIME_WAITとなっているポートを再利用出来る状態となっています。
参考情報
- man socket (7): Linux のソケットインターフェース
- ソケットオプションの使い方(SO_REUSEADDR編) - hana_shinのLinux技術ブログ
- TCPソケットにおけるTIME_WAITとSO_REUSEADDRの関係 #Windows - Qiita
Goのおすすめ書籍
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。