以下の内容はhttps://devlights.hatenablog.com/entry/2025/05/28/073000より取得しました。


Goメモ-582 (unix.Syscall(), unixパッケージの関数, 標準ライブラリで同じ処理書いた場合の比較)

関連記事

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のおすすめ書籍


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

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




以上の内容はhttps://devlights.hatenablog.com/entry/2025/05/28/073000より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14