関連記事
GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ
概要
以下、自分用のメモです。
ここまで、unix絡みで Syscall 関数を使って何個かメモしてきました。
最後に同じ処理を
- unix.Syscall()を利用
- unixパッケージのラッパー関数を利用
- 標準ライブラリを利用
の3つで書いてみて、どんな感じになるのかをメモしておこうと思います。
サンプル
main.go (unix.Syscall()利用)
package main import ( "encoding/binary" "log" "net" "time" "unsafe" "golang.org/x/sys/unix" ) func main() { log.SetFlags(0) if err := run(); err != nil { log.Fatal(err) } } func run() error { // // TCPリスナーの起動までを Syscall() で実装 // なお、利用する関数は SyscallNoError (エラーを返さないバージョン) を意図的に利用 // const ( zero = uintptr(0) // 不要な引数値を表す ) // socket(2) var ( domain = uintptr(unix.AF_INET) sockType = uintptr(unix.SOCK_STREAM) protocol = uintptr(0) sFd, _ = unix.SyscallNoError(unix.SYS_SOCKET, domain, sockType, protocol) ) defer func(fd uintptr) { _, _ = unix.SyscallNoError(unix.SYS_CLOSE, fd, zero, zero) }(sFd) // setsockopt(2) (SO_REUSEADDR) var ( level = uintptr(unix.SOL_SOCKET) optname = uintptr(unix.SO_REUSEADDR) optval = 1 optvalPtr = uintptr(unsafe.Pointer(&optval)) optlen = uintptr(unsafe.Sizeof(optval)) ) _, _, _ = unix.Syscall6(unix.SYS_SETSOCKOPT, sFd, level, optname, optvalPtr, optlen, zero) // ソケットアドレス生成 // アドレスを表現する構造体として unix.SockaddrInet4 と unix.RawSockaddrInet4 の2つがあるが // unix.Syscall()を利用して直接システムコールを呼び出す場合は、カーネルが期待するメモリレイアウトを // そのまま表現する unix.RawSockaddrInet4 を利用する。(要はCの構造体と同じ形の方を使う) // unix.RawSockaddrInet4の方は、ネットワークバイトオーダーで値を保持する。 var ( sAddr unix.RawSockaddrInet4 sAddrPtr uintptr sAddrLen uintptr ) sAddr = unix.RawSockaddrInet4{ Family: unix.AF_INET, Port: htons(8888), Addr: [4]byte{127, 0, 0, 1}, } sAddrPtr = uintptr(unsafe.Pointer(&sAddr)) sAddrLen = uintptr(unix.SizeofSockaddrInet4) // bind(2) _, _ = unix.SyscallNoError(unix.SYS_BIND, sFd, sAddrPtr, sAddrLen) // listen(2) _, _ = unix.SyscallNoError(unix.SYS_LISTEN, sFd, uintptr(5), zero) for { // accept(2) var ( cAddr unix.RawSockaddrInet4 cAddrPtr = uintptr(unsafe.Pointer(&cAddr)) cAddrLen uint32 = unix.SizeofSockaddrInet4 cAddrLenPtr = uintptr(unsafe.Pointer(&cAddrLen)) cFd, _ = unix.SyscallNoError(unix.SYS_ACCEPT, sFd, cAddrPtr, cAddrLenPtr) ) log.Printf("[accept] EP: %v:%d", net.IP(cAddr.Addr[:]), ntohs(cAddr.Port)) go func(fd uintptr) { time.Sleep(1 * time.Second) _, _ = unix.SyscallNoError(unix.SYS_CLOSE, fd, zero, zero) }(cFd) } } // ホストバイトオーダーからネットワークバイトオーダーに変換(Host to Network Short) func htons(port uint16) uint16 { bytes := make([]byte, 2) binary.LittleEndian.PutUint16(bytes, port) return binary.BigEndian.Uint16(bytes) } // ネットワークバイトオーダーからホストバイトオーダーに変換(Network to Host Short) func ntohs(port uint16) uint16 { bytes := make([]byte, 2) binary.BigEndian.PutUint16(bytes, port) return binary.LittleEndian.Uint16(bytes) }
unix/main.go (unixパッケージのラッパー関数利用)
package main import ( "log" "net" "time" "golang.org/x/sys/unix" ) func main() { log.SetFlags(0) if err := run(); err != nil { log.Fatal(err) } } func run() error { // // unix.SyscallNoError()を用いたサンプルと同じものを // unixパッケージで提供されている各ラッパー関数を使って実装 // // 元のサンプルと同様にエラー処理は割愛する // // socket(2) var ( fd int ) fd, _ = unix.Socket(unix.AF_INET, unix.SOCK_STREAM, 0) defer unix.Close(fd) // setsockopt(2) (SO_REUSEADDR) _ = unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_REUSEADDR, 1) // ソケットアドレス生成 var ( sAddr = unix.SockaddrInet4{ Port: 8888, Addr: [4]byte{127, 0, 0, 1}, } ) // bind(2) _ = unix.Bind(fd, &sAddr) // listen(2) _ = unix.Listen(fd, 5) for { // accept(2) var ( cfd int sa unix.Sockaddr ca *unix.SockaddrInet4 ) cfd, sa, _ = unix.Accept(fd) ca = sa.(*unix.SockaddrInet4) log.Printf("[accept] EP: %v:%d", net.IP(ca.Addr[:]), ca.Port) go func(fd int) { time.Sleep(1 * time.Second) _ = unix.Close(fd) }(cfd) } }
stdlib/main.go (標準ライブラリ利用)
package main import ( "log" "net" "time" ) func main() { log.SetFlags(0) if err := run(); err != nil { log.Fatal(err) } } func run() error { // // unix.SyscallNoError()を用いたサンプルと同じものを // 標準ライブライブラリで提供されている関数を使って実装 // // 元のサンプルと同様にエラー処理は割愛する // var ( ln net.Listener ) ln, _ = net.Listen("tcp", ":8888") defer ln.Close() for { var ( conn net.Conn addr *net.TCPAddr ) conn, _ = ln.Accept() addr = conn.RemoteAddr().(*net.TCPAddr) log.Printf("[accept] EP: %v:%d", addr.IP, addr.Port) go func(conn net.Conn) { time.Sleep(1 * time.Second) conn.Close() }(conn) } }
Taskfile.yml
# https://taskfile.dev version: '3' tasks: default: cmds: - task: run_noerror - task: hr - task: run_unix - task: hr - task: run_stdlib hr: cmds: - echo '-------------------------------------------------' silent: true run_noerror: cmds: - rm -f ./app_noerror - go build -o app_noerror main.go - go build -o app_client client/main.go - ./app_noerror & - sleep 1 - ./app_client - pkill app_noerror ignore_error: true run_unix: cmds: - rm -f ./app_unix - go build -o app_unix unix/main.go - go build -o app_client client/main.go - ./app_unix & - sleep 1 - ./app_client - pkill app_unix ignore_error: true run_stdlib: cmds: - rm -f ./app_stdlib - go build -o app_stdlib stdlib/main.go - go build -o app_client client/main.go - ./app_stdlib & - sleep 1 - ./app_client - pkill app_stdlib ignore_error: true
実行結果
$ task task: [run_noerror] rm -f ./app_noerror task: [run_noerror] go build -o app_noerror main.go task: [run_noerror] go build -o app_client client/main.go task: [run_noerror] ./app_noerror & task: [run_noerror] sleep 1 task: [run_noerror] ./app_client [accept] EP: 127.0.0.1:53418 task: [run_noerror] pkill app_noerror ------------------------------------------------- task: [run_unix] rm -f ./app_unix task: [run_unix] go build -o app_unix unix/main.go task: [run_unix] go build -o app_client client/main.go task: [run_unix] ./app_unix & task: [run_unix] sleep 1 task: [run_unix] ./app_client [accept] EP: 127.0.0.1:34092 task: [run_unix] pkill app_unix ------------------------------------------------- task: [run_stdlib] rm -f ./app_stdlib task: [run_stdlib] go build -o app_stdlib stdlib/main.go task: [run_stdlib] go build -o app_client client/main.go task: [run_stdlib] ./app_stdlib & task: [run_stdlib] sleep 1 task: [run_stdlib] ./app_client [accept] EP: 127.0.0.1:34094 task: [run_stdlib] pkill app_stdlib
標準ライブラリに近づくにつれて抽象化レイヤーが増えていくので、コードが短くシンプルになっていっていますね。
やはり、標準ライブラリを利用するのが楽です。
参考情報
try-golang/examples/syscall/syscallnoerror at main · devlights/try-golang · GitHub
Goのおすすめ書籍
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。