Go语言中的Socket编程

什么是Socket?

套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。

Socket分类:

为了满足不同的通信程序对通信质量和性能的要求,一般的网络系统提供了三种不同类型的套接字,以供用户在设计网络应用程序时根据不同的要求来选择。这三种套接为流式套接字(SOCK-STREAM)、数据报套接字(SOCK-DGRAM)和原始套接字(SOCK-RAW)。

1.流式套接字(TCP)

它提供了一种可靠的、面向连接的双向数据传输服务,实现了数据无差错、无重复的发送。流式套接字内设流量控制,被传输的数据看作是无记录边界的字节流。在TCP/IP协议簇中,使用TCP协议来实现字节流的传输,当用户想要发送大批量的数据或者对数据传输有较高的要求时,可以使用流式套接字。

2.数据报套接字(UDP)

它提供了一种无连接、不可靠的双向数据传输服务。数据包以独立的形式被发送,并且保留了记录边界,不提供可靠性保证。数据在传输过程中可能会丢失或重复,并且不能保证在接收端按发送顺序接收数据。在TCP/IP协议簇中,使用UDP协议来实现数据报套接字。在出现差错的可能性较小或允许部分传输出错的应用场合,可以使用数据报套接字进行数据传输,这样通信的效率较高。

3.原始套接字(IP或者ICMP)

该套接字允许对较低层协议(如IP或ICMP)进行直接访问,常用于网络协议分析,检验新的网络协议实现,也可用于测试新配置或安装的网络设备。

Go Socket编程

Go提供net网络包实现基本的tcp/udp网络编程能力。使用net包非常简单。

服务端实现思路:

  • 首先启动一个监听器,声明传输协议与主机端口信息;
  • 启动一个无限循环不断从监听器接收客户端连接;
  • 为每个客户端连接创建一个处理协程,处理协程实现对客户端的响应。

客户端实现思路:

  • 首先客户端拨号连接服务端,声明传输协议与服务端主机和端口信息;
  • 准备一个读写缓冲的字节数组
  • 启动一个无限循环不断从终端读取用户输入,通过缓冲字节数组发送给服务端并等待服务端响应。

服务端实现 Server.go:

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package main

import (
"fmt"
"net"
"strings"
"time"
)

func handlerConn(conn net.Conn) {
defer conn.Close()
// 拿到客户端地址
addr := conn.RemoteAddr().String()
fmt.Printf("[%s] connection success\n", addr)

buf := make([]byte, 1024)
for {
n, err := conn.Read(buf)
if err != nil {
// 客户端主动关闭
if err.Error() == "EOF" {
fmt.Printf("[%s] exit\n", addr)
return
}

fmt.Printf("[%s] conn read err:%s\n", addr, err)
return
}
msg := string(buf[:n])
fmt.Printf("[%v] Received a message from [%s]:\n %s\n\n", time.Now().UTC(), addr, msg)
if "exit" == msg {
fmt.Printf("[%s] exit\n", addr)
return
}
conn.Write([]byte(strings.ToUpper(msg)))
}
}

func server() {
listener, err := net.Listen("tcp", ":8888")
if err != nil {
fmt.Println("listener err", err)
return
}
defer listener.Close()

for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("conn client err", err)
return
}

go handlerConn(conn)
}

}

func main() {
server()
}

-------------------------
[2019-08-07 03:35:21.6288183 +0000 UTC] Received a message from [127.0.0.1:62777]:
hello

[2019-08-07 03:35:28.3784244 +0000 UTC] Received a message from [127.0.0.1:62777]:
你好呀

[2019-08-07 03:35:35.9991891 +0000 UTC] Received a message from [127.0.0.1:62777]:
exit

客户端实现 Client.go:

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
package main

import (
"bufio"
"fmt"
"net"
"os"
"time"
)

func received(conn net.Conn) {
defer conn.Close()
buf := make([]byte, 1024)
addr := conn.RemoteAddr()
for {
n, err := conn.Read(buf)
if err != nil {
fmt.Println("read err:", err)
continue
}
msg := string(buf[:n])
fmt.Printf("[%v] Received a message from [%s]:\n %s\n\n", time.Now().UTC(), addr, msg)
}
}

func main() {
conn, err := net.Dial("tcp", "127.0.0.1:8888")
if err != nil {
fmt.Println("[client]: Dial err")
return
}
defer conn.Close()

go received(conn)

scan := bufio.NewScanner(os.Stdin)
for scan.Scan() {
line := scan.Text()
conn.Write([]byte(line))
if "exit" == line {
break
}
}

}
---------------
hello
[2019-08-07 03:35:21.6395571 +0000 UTC] Received a message from [127.0.0.1:8888]:
HELLO

你好呀
[2019-08-07 03:35:28.3891645 +0000 UTC] Received a message from [127.0.0.1:8888]:
你好呀

exit