関連記事
Flutterメモ-40 (dart pub unpack)(Dart 3.4で追加) - いろいろ備忘録日記
GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ
概要
以下、自分用のメモです。忘れないうちにメモメモ。。。
最近、ひょんなことで、golang.org/x/sys/unix を使ってソケット通信する処理をちょっと書いたので、ついでにここにメモ残しておこうと思いました。
syscall パッケージを使っても同じことが出来るのですが、少し呼び出すAPIが異なるかもしれません。
今回は、正規解放(Graceful Shutdown)のサンプル。
ソケットの正規解放って何?って方は、クライアント側のソースコードにコメントがありますので、良かったらご参考ください。
実務でも、ちゃんとこの正規解放していないコードって結構あったりします。通信処理は何が起こるか分からないものなので、出来るだけきっちり処理を書いた方が良いですね。一度動けばオッケイなサンプルなどで正規解放などは面倒なのでしなくても良いと思っています。
サンプル
サーバとクライアントを用意します。サンプルなので、1度のリクエスト・レスポンスで終わりです。
Client
//go:build linux package main import ( "errors" "log" "net" "golang.org/x/sys/unix" ) func init() { log.SetFlags(log.Lmicroseconds) } func main() { if err := run(); err != nil { log.Fatal(err) } } func run() error { var ( sfd int err error ) sfd, err = unix.Socket(unix.AF_INET, unix.SOCK_STREAM, unix.IPPROTO_TCP) if err != nil { return err } defer func() { log.Println("[CLIENT] ソケットクローズ") unix.Close(sfd) }() var ( ip = net.ParseIP("127.0.0.1") ipv4 [4]byte sAddr unix.Sockaddr ) copy(ipv4[:], ip.To4()) sAddr = &unix.SockaddrInet4{Port: 8888, Addr: ipv4} err = unix.Connect(sfd, sAddr) if err != nil { return err } log.Println("[CLIENT] Connect") // // Send // var ( buf = make([]byte, 2048) msg = "helloworld" ) copy(buf, []byte(msg)) err = unix.Send(sfd, buf[:len(msg)], 0) if err != nil { return err } log.Printf("[CLIENT] SEND %s", msg) // // Recv // var ( n int ) clear(buf) n, err = unix.Read(sfd, buf) if err != nil { return err } log.Printf("[CLIENT] RECV %s", buf[:n]) // // 正規解放 (Graceful Shutdown or Orderly Release) // // ソケットの正規解放とは、ソケット通信を適切に終了させ、リソースを解放するプロセスのことを指します。 // これには通常、shutdownとcloseの2つの操作が含まれます。 // // 1. Shutdown // shutdownは通信相手に対して接続終了の意思を伝えます。 // 例えば、SHUT_WRを使用すると、相手側にEOF(End of File)を送信します。 // // 2. close // closeはソケットのファイルディスクリプタを閉じ、関連するリソースを解放します。 // 最後の参照が閉じられたときにのみ、ネットワークの端点を完全に解放します。 // // 正規解放の手順 // 1. shutdown(SHUT_WR) の呼び出し。これにより相手に送信停止を通知する。 // 2. 必要に応じて、残りのデータを受信する。 // 3. 最後に close を呼び出して、ソケットのリソースを完全に解放する。 // // 正規解放を行うことで、ネットワーク通信を適切に終了し、リソースを効率的に管理することができます。 // 特に信頼性の高い通信が必要な場合や、大規模なシステムでリソース管理が重要な場合に、この方法は有効です。 // // 1. shutdown(SHUT_WR) の呼び出し。これにより相手に送信停止を通知する。 // つまり、相手側にEOFが送信される。「もうデータは送りません」という意思表示。 err = unix.Shutdown(sfd, unix.SHUT_WR) if err != nil { return err } log.Println("[CLIENT] shutdown(SHUT_WR)") // 2. 必要に応じて、残りのデータを受信する。 LOOP: for { clear(buf) n, err = unix.Read(sfd, buf) switch { case n == 0: log.Println("[CLIENT] 切断検知 (0 byte read)") break LOOP case err != nil: var sysErr unix.Errno if errors.As(err, &sysErr); sysErr == unix.ECONNRESET { log.Printf("[CLIENT] 切断検知 (%s)", sysErr) break LOOP } return err default: log.Printf("[CLIENT] RECV REMAIN [%s]", buf[:n]) } } // 3. 最後に close を呼び出して、ソケットのリソースを完全に解放する。 // これは上の defer で行われている。 return nil }
Server
//go:build linux package main import ( "errors" "log" "net" "golang.org/x/sys/unix" ) func init() { log.SetFlags(log.Lmicroseconds) } func main() { if err := run(); err != nil { log.Fatal(err) } } func run() error { // // Create // var ( sfd int err error ) sfd, err = unix.Socket(unix.AF_INET, unix.SOCK_STREAM, unix.IPPROTO_TCP) if err != nil { return err } defer func() { log.Println("[SERVER] サーバーソケットクローズ") unix.Close(sfd) }() // // SO_REUSEADDR // err = unix.SetsockoptInt(sfd, unix.SOL_SOCKET, unix.SO_REUSEADDR, 1) if err != nil { return err } // // Bind and Listen // var ( ip = net.ParseIP("127.0.0.1") ipv4 [4]byte sAddr unix.Sockaddr backLog = 2 ) copy(ipv4[:], ip.To4()) sAddr = &unix.SockaddrInet4{Port: 8888, Addr: ipv4} err = unix.Bind(sfd, sAddr) if err != nil { return err } err = unix.Listen(sfd, backLog) if err != nil { return err } // // Accept // var ( cfd int cAddr unix.Sockaddr ) cfd, cAddr, err = unix.Accept(sfd) if err != nil { return err } defer func() { log.Println("[SERVER] パケット送受信用ソケットクローズ") unix.Close(cfd) }() cAddrInet4 := cAddr.(*unix.SockaddrInet4) log.Printf("[SERVER] Connect from %v:%v", cAddrInet4.Addr, cAddrInet4.Port) // // Recv // var ( buf = make([]byte, 2048) n int ) n, err = unix.Read(cfd, buf) if err != nil { return err } log.Printf("[SERVER] RECV %s", string(buf[:n])) // // Send // var ( msg = "HELLOWORLD " ) for range 5 { clear(buf) copy(buf, []byte(msg)) err = unix.Send(cfd, buf[:len(msg)], 0) if err != nil { return err } log.Printf("[SERVER] SEND %s", buf[:len(msg)]) } // 1. shutdown(SHUT_WR) の呼び出し。これにより相手に送信停止を通知する。 err = unix.Shutdown(cfd, unix.SHUT_WR) if err != nil { return err } log.Println("[SERVER] shutdown(SHUT_WR)") // 2. 必要に応じて、残りのデータを受信する。 LOOP: for { clear(buf) n, err = unix.Read(cfd, buf) switch { case n == 0: log.Println("[SERVER] 切断検知 (0 byte read)") break LOOP case err != nil: var sysErr unix.Errno if errors.As(err, &sysErr); sysErr == unix.ECONNRESET { log.Printf("[SERVER] 切断検知 (%s)", sysErr) break LOOP } return err default: log.Printf("[SERVER] RECV %s", buf[:n]) } } // 3. 最後に close を呼び出して、ソケットのリソースを完全に解放する。 // これは上の defer で行われている。 return nil }
Taskfile
実行用に以下のようなタスクファイルを用意
# https://taskfile.dev version: '3' tasks: default: cmds: - task: run fmt: cmds: - goimports -w . prepare: cmds: - mkdir -p bin build: deps: [ fmt ] cmds: - go build -o bin/server server/server.go - go build -o bin/client client/client.go run: deps: [ build ] cmds: - ./bin/server & - sleep 1 - ./bin/client - sleep 1 - pgrep server && pkill server ignore_error: true clean: cmds: - rm -rf ./bin
実行
task: [fmt] goimports -w . task: [build] go build -o bin/server server/server.go task: [build] go build -o bin/client client/client.go task: [run] ./bin/server & task: [run] sleep 1 task: [run] ./bin/client 17:17:14.152034 [CLIENT] Connect 17:17:14.152369 [CLIENT] SEND helloworld 17:17:14.152221 [SERVER] Connect from [127 0 0 1]:43708 17:17:14.152644 [SERVER] RECV helloworld 17:17:14.152686 [SERVER] SEND HELLOWORLD 17:17:14.152689 [CLIENT] RECV HELLOWORLD 17:17:14.152717 [SERVER] SEND HELLOWORLD 17:17:14.152725 [SERVER] SEND HELLOWORLD 17:17:14.152733 [SERVER] SEND HELLOWORLD 17:17:14.152741 [SERVER] SEND HELLOWORLD 17:17:14.152751 [CLIENT] shutdown(SHUT_WR) 17:17:14.152771 [CLIENT] RECV REMAIN [HELLOWORLD HELLOWORLD HELLOWORLD HELLOWORLD ] 17:17:14.152775 [SERVER] shutdown(SHUT_WR) 17:17:14.152784 [CLIENT] 切断検知 (0 byte read) 17:17:14.152787 [SERVER] 切断検知 (0 byte read) 17:17:14.152788 [CLIENT] ソケットクローズ 17:17:14.152796 [SERVER] パケット送受信用ソケットクローズ 17:17:14.152818 [SERVER] サーバーソケットクローズ task: [run] sleep 1 task: [run] pgrep server && pkill server
参考情報
Goメモ-428 (flaggyメモ)(01) - いろいろ備忘録日記
Goメモ-429 (flaggyメモ)(02) - いろいろ備忘録日記
Goメモ-430 (flaggyメモ)(03) - いろいろ備忘録日記
Goのおすすめ書籍
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。