Skip to content

Commit c1c84e9

Browse files
authored
Merge pull request #908 from devlights/add-tcp-select-syscall
2 parents 403ebf8 + 0d2a62f commit c1c84e9

File tree

5 files changed

+263
-0
lines changed

5 files changed

+263
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
app
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# これは何?
2+
3+
[golang.org/sys/unix](https://pkg.go.dev/golang.org/x/sys/unix) を使って、[select(2)](https://ja.manpages.org/select/2)を呼び出し
4+
5+
対象となるソケットファイルディスクリプタが読み取り可能な状態であるかを確認して処理するサンプルです。
6+
7+
8+
```sh
9+
$ task
10+
task: [build] go build -o app .
11+
task: [run] ./app -server &
12+
task: [run] sleep 0.5
13+
task: [run] ./app
14+
10:01:00.206680 [C] select(2) -- not readable(fd=6)
15+
10:01:00.216917 [C] select(2) -- not readable(fd=6)
16+
10:01:00.227016 [C] select(2) -- not readable(fd=6)
17+
10:01:00.237166 [C] select(2) -- not readable(fd=6)
18+
10:01:00.247262 [C] select(2) -- not readable(fd=6)
19+
10:01:00.257365 [C] select(2) -- not readable(fd=6)
20+
10:01:00.267527 [C] select(2) -- not readable(fd=6)
21+
10:01:00.277664 [C] select(2) -- not readable(fd=6)
22+
10:01:00.287777 [C] select(2) -- not readable(fd=6)
23+
10:01:00.296891 [S] send data
24+
10:01:00.297322 [C] recv hello
25+
10:01:00.297369 [C] shutdown(SHUT_WR)
26+
10:01:00.297405 [C] close
27+
10:01:00.297458 [S] disconnect
28+
10:01:00.297523 [S] close
29+
task: [run] sleep 0.5
30+
task: [run] pkill app
31+
```
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# https://taskfile.dev
2+
3+
version: '3'
4+
5+
tasks:
6+
default:
7+
cmds:
8+
- task: build
9+
- task: run
10+
build:
11+
cmds:
12+
- go build -o app .
13+
run:
14+
cmds:
15+
- ./app -server &
16+
- sleep 0.5
17+
- ./app
18+
- sleep 0.5
19+
- pkill app
20+
ignore_error: true
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"time"
7+
8+
"golang.org/x/sys/unix"
9+
)
10+
11+
// SocketFd は、ソケットファイルディスクリプタを表します。
12+
type SocketFd int
13+
14+
// Readable は、select(2)を呼び出し読み込み可能かどうかを判定します。
15+
func (me SocketFd) Readable(sec, usec time.Duration) (bool, error) {
16+
fd := int(me)
17+
if fd < 0 || fd >= unix.FD_SETSIZE {
18+
return false, fmt.Errorf("invalid file descriptor: out of range %d (FD_SETSIZE = %d)", fd, unix.FD_SETSIZE)
19+
}
20+
21+
rfds := &unix.FdSet{}
22+
rfds.Zero()
23+
rfds.Set(fd)
24+
25+
timeout := &unix.Timeval{
26+
Sec: int64(sec.Seconds()),
27+
Usec: usec.Microseconds(),
28+
}
29+
30+
n, err := unix.Select(fd+1, rfds, nil, nil, timeout)
31+
if err != nil {
32+
if errors.Is(err, unix.EINTR) {
33+
return false, nil
34+
}
35+
return false, err
36+
}
37+
38+
return n > 0 && rfds.IsSet(fd), nil
39+
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"flag"
6+
"io"
7+
"log"
8+
"net"
9+
"time"
10+
)
11+
12+
type (
13+
Args struct {
14+
IsServer bool
15+
}
16+
)
17+
18+
var (
19+
args Args
20+
)
21+
22+
func init() {
23+
flag.BoolVar(&args.IsServer, "server", false, "server mode")
24+
}
25+
26+
func main() {
27+
log.SetFlags(log.Lmicroseconds)
28+
flag.Parse()
29+
30+
if err := run(); err != nil {
31+
panic(err)
32+
}
33+
}
34+
35+
func run() error {
36+
var err error
37+
switch args.IsServer {
38+
case true:
39+
err = runServer()
40+
default:
41+
err = runClient()
42+
}
43+
44+
if err != nil {
45+
return err
46+
}
47+
48+
return nil
49+
}
50+
51+
func runServer() error {
52+
ln, err := net.Listen("tcp", ":8888")
53+
if err != nil {
54+
return err
55+
}
56+
defer ln.Close()
57+
58+
errCh := make(chan error)
59+
defer close(errCh)
60+
61+
for {
62+
select {
63+
case e := <-errCh:
64+
return e
65+
default:
66+
}
67+
68+
err = ln.(*net.TCPListener).SetDeadline(time.Now().Add(1 * time.Second))
69+
if err != nil {
70+
return err
71+
}
72+
73+
conn, err := ln.Accept()
74+
if err != nil {
75+
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
76+
continue
77+
}
78+
79+
return err
80+
}
81+
82+
go func(conn net.Conn) {
83+
defer func() {
84+
conn.Close()
85+
log.Println("[S] close")
86+
}()
87+
88+
time.Sleep(100 * time.Millisecond)
89+
{
90+
log.Println("[S] send data")
91+
if _, err := conn.Write([]byte("hello")); err != nil {
92+
errCh <- err
93+
}
94+
95+
buf := make([]byte, 1)
96+
for {
97+
_, err := conn.Read(buf)
98+
if err != nil {
99+
if errors.Is(err, io.EOF) {
100+
log.Println("[S] disconnect")
101+
break
102+
}
103+
104+
errCh <- err
105+
}
106+
}
107+
}
108+
}(conn)
109+
}
110+
}
111+
112+
func runClient() error {
113+
conn, err := net.Dial("tcp", ":8888")
114+
if err != nil {
115+
return err
116+
}
117+
defer func() {
118+
conn.Close()
119+
log.Println("[C] close")
120+
}()
121+
122+
//
123+
// select(2) を使って読み込み可能かを判定
124+
// select(2)に指定するFDは、net.TCPConn.File() から取得する
125+
//
126+
tcpConn, _ := conn.(*net.TCPConn)
127+
file, err := tcpConn.File()
128+
if err != nil {
129+
return err
130+
}
131+
defer file.Close()
132+
133+
var (
134+
fd = SocketFd(file.Fd())
135+
buf = make([]byte, 10)
136+
sec = 0 * time.Second
137+
usec = 10 * time.Millisecond
138+
)
139+
for {
140+
readable, err := fd.Readable(sec, usec)
141+
if err != nil {
142+
return err
143+
}
144+
145+
if !readable {
146+
log.Printf("[C] select(2) -- not readable(fd=%d)", int(fd))
147+
continue
148+
}
149+
150+
clear(buf)
151+
n, err := conn.Read(buf)
152+
if err != nil {
153+
if errors.Is(err, io.EOF) {
154+
log.Println("[C] disconnect")
155+
break
156+
}
157+
158+
return err
159+
}
160+
log.Printf("[C] recv %s", buf[:n])
161+
162+
err = conn.(*net.TCPConn).CloseWrite()
163+
if err != nil {
164+
return err
165+
}
166+
log.Println("[C] shutdown(SHUT_WR)")
167+
168+
break
169+
}
170+
171+
return nil
172+
}

0 commit comments

Comments
 (0)