Skip to content

Commit 5410b8e

Browse files
authored
Merge pull request #834 from devlights/add-socket-nonblocking
2 parents 63c6512 + f1e9fec commit 5410b8e

File tree

3 files changed

+406
-0
lines changed

3 files changed

+406
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# https://taskfile.dev
2+
3+
version: '3'
4+
5+
tasks:
6+
default:
7+
cmds:
8+
- task: run
9+
fmt:
10+
cmds:
11+
- goimports -w .
12+
prepare:
13+
cmds:
14+
- mkdir -p bin
15+
build:
16+
deps: [ fmt ]
17+
cmds:
18+
- go build -o bin/server server/server.go
19+
- go build -o bin/client client/client.go
20+
run:
21+
deps: [ build ]
22+
cmds:
23+
- ./bin/server &
24+
- sleep 1
25+
- ./bin/client
26+
- sleep 1
27+
- pgrep server && pkill server
28+
ignore_error: true
29+
clean:
30+
cmds:
31+
- rm -rf ./bin
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
//go:build linux
2+
3+
package main
4+
5+
import (
6+
"errors"
7+
"log"
8+
"net"
9+
"time"
10+
11+
"golang.org/x/sys/unix"
12+
)
13+
14+
func init() {
15+
log.SetFlags(log.Lmicroseconds)
16+
}
17+
18+
func main() {
19+
if err := run(); err != nil {
20+
log.Fatal(err)
21+
}
22+
}
23+
24+
func run() error {
25+
var (
26+
sfd int
27+
err error
28+
)
29+
30+
sfd, err = unix.Socket(unix.AF_INET, unix.SOCK_STREAM, unix.IPPROTO_TCP)
31+
if err != nil {
32+
return err
33+
}
34+
defer func() {
35+
log.Println("[CLIENT] ソケットクローズ")
36+
unix.Close(sfd)
37+
}()
38+
39+
var (
40+
ip = net.ParseIP("127.0.0.1")
41+
ipv4 [4]byte
42+
43+
sAddr unix.Sockaddr
44+
)
45+
copy(ipv4[:], ip.To4())
46+
47+
sAddr = &unix.SockaddrInet4{Port: 8888, Addr: ipv4}
48+
err = unix.Connect(sfd, sAddr)
49+
if err != nil {
50+
return err
51+
}
52+
53+
log.Println("[CLIENT] Connect")
54+
55+
//
56+
// ソケットをノンブロッキングモードに設定
57+
// クライアントソケットの場合は必ず「接続した後」に設定する必要がある.
58+
// (接続する前にノンブロッキングモード設定しても、ソケットが接続されていないため効果がない)
59+
//
60+
err = unix.SetNonblock(sfd, true)
61+
if err != nil {
62+
return err
63+
}
64+
log.Println("[CLIENT] set O_NONBLOCK")
65+
66+
//
67+
// Send
68+
//
69+
var (
70+
buf = make([]byte, 2048)
71+
msg = "helloworld"
72+
)
73+
for {
74+
copy(buf, []byte(msg))
75+
76+
_, err = unix.Write(sfd, buf[:len(msg)])
77+
if err != nil {
78+
if errors.Is(err, unix.EAGAIN) {
79+
log.Println("[CLIENT][SEND] --> unix.EAGAIN")
80+
81+
time.Sleep(100 * time.Millisecond)
82+
continue
83+
}
84+
85+
return err
86+
}
87+
88+
log.Printf("[CLIENT] SEND %s", msg)
89+
90+
break
91+
}
92+
93+
//
94+
// Recv
95+
//
96+
var (
97+
n int
98+
)
99+
for {
100+
clear(buf)
101+
102+
n, err = unix.Read(sfd, buf)
103+
if err != nil {
104+
if errors.Is(err, unix.EAGAIN) {
105+
log.Println("[CLIENT][RECV] --> unix.EAGAIN")
106+
107+
time.Sleep(50 * time.Millisecond)
108+
continue
109+
}
110+
111+
return err
112+
}
113+
114+
log.Printf("[CLIENT] RECV %s", buf[:n])
115+
116+
break
117+
}
118+
119+
// ブロッキングモードに戻す
120+
err = unix.SetNonblock(sfd, false)
121+
if err != nil {
122+
return err
123+
}
124+
log.Println("[CLIENT] reset O_NONBLOCK")
125+
126+
//
127+
// 正規解放 (Graceful Shutdown or Orderly Release)
128+
//
129+
// ソケットの正規解放とは、ソケット通信を適切に終了させ、リソースを解放するプロセスのことを指します。
130+
// これには通常、shutdownとcloseの2つの操作が含まれます。
131+
//
132+
// 1. Shutdown
133+
// shutdownは通信相手に対して接続終了の意思を伝えます。
134+
// 例えば、SHUT_WRを使用すると、相手側にEOF(End of File)を送信します。
135+
//
136+
// 2. close
137+
// closeはソケットのファイルディスクリプタを閉じ、関連するリソースを解放します。
138+
// 最後の参照が閉じられたときにのみ、ネットワークの端点を完全に解放します。
139+
//
140+
// 正規解放の手順
141+
// 1. shutdown(SHUT_WR) の呼び出し。これにより相手に送信停止を通知する。
142+
// 2. 必要に応じて、残りのデータを受信する。
143+
// 3. 最後に close を呼び出して、ソケットのリソースを完全に解放する。
144+
//
145+
// 正規解放を行うことで、ネットワーク通信を適切に終了し、リソースを効率的に管理することができます。
146+
// 特に信頼性の高い通信が必要な場合や、大規模なシステムでリソース管理が重要な場合に、この方法は有効です。
147+
//
148+
149+
// 1. shutdown(SHUT_WR) の呼び出し。これにより相手に送信停止を通知する。
150+
// つまり、相手側にEOFが送信される。「もうデータは送りません」という意思表示。
151+
err = unix.Shutdown(sfd, unix.SHUT_WR)
152+
if err != nil {
153+
return err
154+
}
155+
156+
log.Println("[CLIENT] shutdown(SHUT_WR)")
157+
158+
// 2. 必要に応じて、残りのデータを受信する。
159+
LOOP:
160+
for {
161+
clear(buf)
162+
163+
n, err = unix.Read(sfd, buf)
164+
switch {
165+
case n == 0:
166+
log.Println("[CLIENT] 切断検知 (0 byte read)")
167+
break LOOP
168+
case err != nil:
169+
var sysErr unix.Errno
170+
if errors.As(err, &sysErr); sysErr == unix.ECONNRESET {
171+
log.Printf("[CLIENT] 切断検知 (%s)", sysErr)
172+
break LOOP
173+
}
174+
default:
175+
log.Printf("[CLIENT] RECV REMAIN [%s]", buf[:n])
176+
}
177+
}
178+
179+
// 3. 最後に close を呼び出して、ソケットのリソースを完全に解放する。
180+
// これは上の defer で行われている。
181+
182+
return nil
183+
}

0 commit comments

Comments
 (0)