前言

Go网络编程的学习代码示例:客户端/服务端(C/S)模型。


代码仓库


核心概念

包名:net

服务端的网络通信流程:

  1. 监听连接请求:Listen()
  2. 接受连接请求:Accept()
  3. 发送和接收数据:Write()、Read()
  4. 关闭连接:Close()

客户端的网络通信流程:

  1. 发送连接请求:Dial()
  2. 发送和接收数据:Write()、Read()
  3. 关闭连接:Close()

相比于C、C++和其他语言,使用Go实现网络编程的逻辑很简单


内容

  • 实现服务端和客户端的简单通信:服务端接收,并原样发送所接收的数据给客户端

  • 服务端使用多协程处理与多客户端的连接

  • 客户端使用多协程处理终端用户输入和接收服务端数据


代码示例(有详细注释)

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
73
74
75
76
77
package main

import (
"fmt"
"net" // 网络处理
)

// 处理连接请求
func handle(conn net.Conn) { // 注意:conn的类型Conn是net包中的接口,需要引用
// 四、1关闭连接
defer conn.Close() // handle()结束前关闭连接。注意:Close()有返回值为error接口,使用defer后这里不再处理错误

// 获取连接请求的地址
addr := conn.RemoteAddr().String()
// RemoteAddr()返回值为Addr接口,Addr接口实现了String(),String()返回值为string类型
fmt.Println("RemoteAddr:", addr)

// 三、接收和发送数据
data_buffer := make([]byte, 16)
// 数据缓冲区,切片类型,网络中以二进制/字节传输,后面Read()和Write()参数也是字节类型,16字节大小

// 接收并原封不动发送回所接收的数据
// 循环处理,只有在接收“quit”时,才退出handle()协程
for {
// 读取数据
read_count, read_error := conn.Read(data_buffer)
// 返回值:接收的字节数量int,error接口
if read_error != nil {
fmt.Println("Read() error:", read_error)
return // 注意:异常退出handle()协程
}
fmt.Println("Read:", string(data_buffer[:read_count]))
// 输出读取的数据,从[0,read_count-2)的切片数据,byte需要转换为string类型
// 注意:Windows会多传输\r\n两个字符,所以要-2

// 如果是“quit”,退出handle()协程
if string(data_buffer[:read_count]) == "quit" {
return // 注意:正常退出handle()协程
}

// 写入数据,直接写字节数组
_, write_error := conn.Write(data_buffer[:read_count])
// 返回值:写入的字节数量不需要,error接口
if write_error != nil {
fmt.Println("Write() error:", write_error)
return // 注意:异常退出handle()协程
}
fmt.Println("Write:", string(data_buffer[:read_count])) // 同理输出写入数据
}
}

func main() {
// 一、监听连接请求
listener, listen_error := net.Listen("tcp4", "127.0.0.1:8000")
// 参数:使用TCP,IPv4,本机IP地址127.0.0.1,端口号8000
// 返回值:Listener接口(类似监听套接字文件描述符),error接口
if listen_error != nil {
fmt.Println("Listen() error:", listen_error) // Println()有返回值,不再处理
return // 注意:退出main()
}

// 四、2关闭连接
defer listener.Close() // main()结束前关闭连接。注意:Close()有返回值为error接口,使用defer后这里不再处理错误

for {
// 二、循环接受连接请求
conn, accept_error := listener.Accept()
// 返回值:Conn接口(类似连接套接字文件描述符),error接口
if accept_error != nil {
fmt.Println("Accept() error:", accept_error)
continue // 注意:继续for{}循环接受连接请求,而不是return退出main()
}

// 开启goroutine并发处理多客户端请求,一个conn代表一个客户端
go handle(conn)
}
}

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

import (
"fmt"
"net" // 网络处理
"os" // 操作系统相关,这里用于接收用户输入
)

func main() {
// 一、发送连接请求
conn, dial_error := net.Dial("tcp4", "127.0.0.1:8000")
// 参数:连接TCP,IPv4,本机IP地址127.0.0.1,端口号8000
// 返回值:Conn接口(类似套接字文件描述符),error接口
if dial_error != nil {
fmt.Println("Dial() error:", dial_error)
return // 注意:退出main()
}

// 三、关闭连接
defer conn.Close() // main()结束前关闭连接。注意:Close()有返回值为error接口,使用defer后这里不再处理错误

// 二、发送和接收数据
// 开启goroutine并发处理用户终端输入数据,发送数据
go func() {
data_buffer := make([]byte, 16)
// 数据缓冲区,切片类型,网络中以二进制/字节传输,后面Read()和Write()参数也是字节类型,16字节大小
for {
read_count, read_error := os.Stdin.Read(data_buffer) //从标准输入读取用户输入
// 返回值:接收的字节数量int,error接口
if read_error != nil {
fmt.Println("Stdin.Read() error:", read_error)
return // 注意:退出协程
}

conn.Write(data_buffer[:read_count-2]) //写入数据
fmt.Println("Write:", string(data_buffer[:read_count-2]))
// Windows会将\r\n写入,先-2再写入
// 输出写入的数据,从[0,scan_count-2)的切片数据,byte需要转换为string类型
}
}()

// 主协程处理接收的数据
data_buffer := make([]byte, 16)
for {
read_count, read_error := conn.Read(data_buffer)
if read_error != nil {
fmt.Println("Read() error:", read_error)
return // 注意:退出main()
}

fmt.Println("Read:", string(data_buffer[:read_count])) // 同理输出读取的数据
}
}

结果

server.go:

1
2
3
4
5
6
7
8
9
10
11
PS C:\Users\DSHH\Desktop\go_test> go run server.go
RemoteAddr: 127.0.0.1:61915
Read: aaa
Write: aaa
RemoteAddr: 127.0.0.1:61939
Read: bbb
Write: bbb
Read: quit
Read: quit
exit status 0xc000013a
PS C:\Users\DSHH\Desktop\go_test>

client.go:先连接先退出

1
2
3
4
5
6
7
8
PS C:\Users\DSHH\Desktop\go_test> go run client.go
aaa
Write: aaa
Read: aaa
quit
Write: quit
Read() error: EOF
PS C:\Users\DSHH\Desktop\go_test>

client.go:后连接后退出

1
2
3
4
5
6
7
8
PS C:\Users\DSHH\Desktop\go_test> go run client.go
bbb
Write: bbb
Read: bbb
quit
Write: quit
Read() error: EOF
PS C:\Users\DSHH\Desktop\go_test>

总结

Go网络编程的学习代码示例:客户端/服务端(C/S)模型。


参考资料


作者的话

  • 感谢参考资料的作者/博主
  • 作者:夜悊
  • 版权所有,转载请注明出处,谢谢~
  • 如果文章对你有帮助,请点个赞或加个粉丝吧,你的支持就是作者的动力~
  • 文章在描述时有疑惑的地方,请留言,定会一一耐心讨论、解答
  • 文章在认识上有错误的地方, 敬请批评指正
  • 望读者们都能有所收获