golang TCP Socket编程


golang版本:1.14.1
主要是对golang net包的Conn接口函数进行测试,这里只是分析常见的几个错误,如果要了解详细的错误可以查看man手册。

Dial

Dial主要实现了TCP三次握手的环节。握手环节中有很多种情况:网络不可达,服务器backlog满了,网络超时等。

network is unreachable

会返回connect: network is unreachable

connection refused

目标服务器的指定端口未被监听。
TCP层发送完第一次握手后就会收到目标主机返回的RST包,golang会返回connect: connection refused
示例:

1
2
3
4
5
6
7
8
func main() {
conn, err := net.Dial("tcp", ":8080")
if err != nil {
fmt.Println(err.Error())
return
}
defer conn.Close()
}

对应控制台输出

1
dial tcp :8080: connect: connection refused

connection timed out

Dial是阻塞的,如果不设置超时,协程会长时间阻塞(golang tcp超时是3分钟),这样很影响程序运行。
如果网络环境不好情况下经常有丢包发生,我们也可以手动设置超时时间来控制超时时间。
TCP会一直重传第一次握手的包,直到设置的超时时间后还没有收到第二次握手,网络状态一直是SYN_SENT,golang返回i/o timeout。还有连接时超时返回connect: connection timed out错误
示例:

1
2
3
4
5
6
7
8
func main() {
conn, err := net.DialTimeout("tcp", "google.com:443", 1*time.Second)
if err != nil {
fmt.Println(err.Error())
return
}
defer conn.Close()
}

对应控制台输出

1
dial tcp 172.217.160.78:443: i/o timeout

cannot assign requested address

无法申请端口建立socket连接。
当前机器没有可用端口哦,由系统返回该错误。
可以修改系统配置,增加可用端口范围,来缓解端口不足的问题

1
2
# vi /etc/sysctl.conf
net.ipv4.ip_local_port_range = 1024 65535

Read

EOF

当前连接处于CLOSE_WAIT是调用Read会返回EOF

i/o timeout

调用Read函数时间超过设置SetReadDeadline时会返回i/o timeout

connection reset by peer

Read函数阻塞期间收到对端发送的RST包时会返回read: connection reset by peer
向一个对端已关闭本端未关闭的连接(即本端处于CLOSE_WAIT)调用Write函数后再调用Read函数可以模拟这一个场景。

use of closed network connection

对已关闭的连接进行Read时会返回use of closed network connection

connection timed out

试图读取连接时超时

network is down

读取一个未连接socket

Write

无错误

当前连接处于对端已关闭本端未关闭(即本端处于CLOSE_WAIT)状态时调用Write函数,golang不会返回error,在TCP层面会收到RST包。当调用Close函数时,系统会标记连接为全关闭,禁止在该连接上读写,所以会返回RST包,如果要进入半关闭需要调用Shutdown函数。

use of closed network connection

当前连接处于FIN_WAIT_2状态和已经完全关闭的连接调用Write会返回use of closed network connection

broken pipe

当连接收到RST包后,连接已断开,此时调用Write会返回write: broken pipe

connection timed out

Close

use of closed network connection

当前连接关闭多次

socket is not connected

多次关闭socket

总结

  1. 本端关闭的连接进行ReadWriteClose都会返回use of closed network connection
  2. 对端关闭本端未关闭的连接进行Write时对端会返回RST包(重置连接但golang不会返回错误),进行Read时会返回EOF错误
  3. 对**重置连接(收到RST包的连接)**进行Write会返回write: broken pipe错误,进行Read会返回read: connection reset by peer
  4. 未在规定的时间完成ReadWrite会返回i/o timeout错误

测试代码

syscall server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
func socketServer() {
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
if err != nil {
fmt.Println(err.Error())
return
}
defer func() {
err = syscall.Close(fd)
if err != nil {
fmt.Println(err.Error())
return
}
}()
sa := &syscall.SockaddrInet4{
Port: 8080,
Addr: [4]byte{127, 0, 0, 1},
}
err = syscall.Bind(fd, sa)
if err != nil {
fmt.Println(err.Error())
return
}
err = syscall.Listen(fd, 2)
if err != nil {
fmt.Println(err.Error())
return
}
for {
nfd, csa, err := syscall.Accept(fd)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Printf("fd %d sa %#v\n", nfd, csa)

buf := make([]byte, 1024)
n, err := syscall.Read(nfd, buf)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println("read data:", string(buf[:n]))
n, err = syscall.Write(fd, buf)
if err != nil {
fmt.Println(err.Error())
return
}
err = syscall.Close(nfd)
if err != nil {
fmt.Println(err.Error())
return
}
break
}
}

syscall client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
func socketClient() {
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
if err != nil {
fmt.Println(err.Error())
return
}
defer func() {
err = syscall.Close(fd)
if err != nil {
fmt.Println(err.Error())
return
}
}()
sa := &syscall.SockaddrInet4{
Port: 8080,
Addr: [4]byte{127, 0, 0, 1},
}
err = syscall.Connect(fd, sa)
if err != nil {
fmt.Println(err.Error())
return
}
buf := []byte("1234567890")
n, err := syscall.Write(fd, buf)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println("send data", n, string(buf))
}

net server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
func netServer() {
l, err := net.Listen("tcp", ":8080")
if err != nil {
fmt.Println(err.Error())
return
}
conn, err := l.Accept()
if err != nil {
fmt.Println(err.Error())
return
}

buf := make([]byte, 1024)
n, err := conn.Write(buf)
if err != nil {
fmt.Println(err.Error())
return
}
n, err = conn.Read(buf)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println(n)

err = conn.Close()
if err != nil {
fmt.Println(err.Error())
return
}
}

net client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func netClient() {
conn, err := net.Dial("tcp", "127.0.0.1:8080")
if err != nil {
fmt.Println(err.Error())
return
}
defer func() {
err = conn.Close()
if err != nil {
fmt.Println(err.Error())
return
}
}()

buf := make([]byte, 1024)
conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
n, err := conn.Write(buf)
if err != nil {
fmt.Println(err.Error())
return
}
conn.SetReadDeadline(time.Now().Add(5 * time.Second))
n, err = conn.Read(buf)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println(string(buf[:n]))
}

参考文献

Go语言TCP Socket编程
TCP连接异常:broken pipe 和EOF


文章作者: djaigo
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 djaigo !
评论
  目录