Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions examples/singleapp/unix/socket/03.nonblocking/Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 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
183 changes: 183 additions & 0 deletions examples/singleapp/unix/socket/03.nonblocking/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
//go:build linux

package main

import (
"errors"
"log"
"net"
"time"

"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")

//
// ソケットをノンブロッキングモードに設定
// クライアントソケットの場合は必ず「接続した後」に設定する必要がある.
// (接続する前にノンブロッキングモード設定しても、ソケットが接続されていないため効果がない)
//
err = unix.SetNonblock(sfd, true)
if err != nil {
return err
}
log.Println("[CLIENT] set O_NONBLOCK")

//
// Send
//
var (
buf = make([]byte, 2048)
msg = "helloworld"
)
for {
copy(buf, []byte(msg))

_, err = unix.Write(sfd, buf[:len(msg)])
if err != nil {
if errors.Is(err, unix.EAGAIN) {
log.Println("[CLIENT][SEND] --> unix.EAGAIN")

time.Sleep(100 * time.Millisecond)
continue
}

return err
}

log.Printf("[CLIENT] SEND %s", msg)

break
}

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

n, err = unix.Read(sfd, buf)
if err != nil {
if errors.Is(err, unix.EAGAIN) {
log.Println("[CLIENT][RECV] --> unix.EAGAIN")

time.Sleep(50 * time.Millisecond)
continue
}

return err
}

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

break
}

// ブロッキングモードに戻す
err = unix.SetNonblock(sfd, false)
if err != nil {
return err
}
log.Println("[CLIENT] reset O_NONBLOCK")

//
// 正規解放 (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
}
default:
log.Printf("[CLIENT] RECV REMAIN [%s]", buf[:n])
}
}

// 3. 最後に close を呼び出して、ソケットのリソースを完全に解放する。
// これは上の defer で行われている。

return nil
}
Loading