いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-461 (golang.org/x/sys/unixを使ってソケット通信)(01-基本パターン)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。忘れないうちにメモメモ。。。

最近、ひょんなことで、golang.org/x/sys/unix を使ってソケット通信する処理をちょっと書いたので、ついでにここにメモ残しておこうと思いました。

syscall パッケージを使っても同じことが出来るのですが、少し呼び出すAPIが異なるかもしれません。

今回は、基本パターンみたいなもの。C言語でソケット使ったことある人にはお馴染みな感じですね。

サンプル

サーバとクライアントを用意します。サンプルなので、1度のリクエスト・レスポンスで終わりです。

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)
    }()

    //
    // 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] %s", string(buf[:n]))

    //
    // Send
    //
    var (
        msg = "HELLOWORLD"
    )

    clear(buf)
    copy(buf, []byte(msg))

    err = unix.Send(cfd, buf[:len(msg)], 0)
    if err != nil {
        return err
    }

    //
    // Disconnect detection
    //
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 nil
}

Client

//go:build linux

package main

import (
    "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
    }

    //
    // 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
    }

    //
    // Recv
    //
    var (
        n int
    )
    clear(buf)

    n, err = unix.Read(sfd, buf)
    if err != nil {
        return err
    }

    log.Printf("[CLIENT] %s", buf[:n])

    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:05:29.140452 [SERVER] Connect from [127 0 0 1]:56374
17:05:29.140712 [SERVER] helloworld
17:05:29.140754 [CLIENT] HELLOWORLD
17:05:29.140915 [CLIENT] ソケットクローズ
17:05:29.140988 [SERVER] 切断検知 (0 byte read)
17:05:29.141024 [SERVER] パケット送受信用ソケットクローズ
17:05:29.141089 [SERVER] サーバーソケットクローズ
task: [run] sleep 1
task: [run] pgrep server && pkill server

参考情報

Goメモ-428 (flaggyメモ)(01) - いろいろ備忘録日記

Goメモ-429 (flaggyメモ)(02) - いろいろ備忘録日記

Goメモ-430 (flaggyメモ)(03) - いろいろ備忘録日記

Goのおすすめ書籍


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

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