本文是 Mihalis Tsoukalos 的 Go 系列文章的一部分。阅读第 1 部分:使用 Go 创建随机安全密码。
TCP 和 UDP 服务器无处不在,通过 TCP/IP 网络为网络客户端提供服务。在本文中,我将解释如何使用 Go 编程语言开发一个并发 TCP 服务器,该服务器返回随机数。对于来自 TCP 客户端的每个传入连接,TCP 服务器将启动一个新的 goroutine 来处理该请求。
您可以在 GitHub 上找到此项目:concTCP.go。
处理 TCP 连接
该程序的逻辑可以在 handleConnection()
函数的 Go 代码中找到,该函数的实现如下:
func handleConnection(c net.Conn) {
fmt.Printf("Serving %s\n", c.RemoteAddr().String())
for {
netData, err := bufio.NewReader(c).ReadString('\n')
if err != nil {
fmt.Println(err)
return
}
temp := strings.TrimSpace(string(netData))
if temp == "STOP" {
break
}
result := strconv.Itoa(random()) + "\n"
c.Write([]byte(string(result)))
}
c.Close()
}
如果 TCP 客户端发送 "STOP" 字符串,则服务于该特定 TCP 客户端的 goroutine 将终止;否则,TCP 服务器将向 TCP 客户端发回一个随机数。for
循环确保只要 TCP 客户端需要,就会为其提供服务。for
循环中的 Go 代码使用 bufio.NewReader(c).ReadString('\n')
逐行读取来自 TCP 客户端的数据,并使用 c.Write([]byte(string(result)))
发回数据。(您可能会发现 net 标准 Go 包 文档 很有帮助。)
并发性
main()
函数的实现告诉 TCP 服务器,每次它必须为 TCP 客户端提供服务时,都启动一个新的 goroutine。
func main() {
arguments := os.Args
if len(arguments) == 1 {
fmt.Println("Please provide a port number!")
return
}
PORT := ":" + arguments[1]
l, err := net.Listen("tcp4", PORT)
if err != nil {
fmt.Println(err)
return
}
defer l.Close()
rand.Seed(time.Now().Unix())
for {
c, err := l.Accept()
if err != nil {
fmt.Println(err)
return
}
go handleConnection(c)
}
}
首先,main()
确保程序至少有一个命令行参数。请注意,现有代码不检查给定的命令行参数是否是有效的 TCP 端口号。但是,如果给定的值不是有效的 TCP 端口号,则对 net.Listen()
的调用将失败,并显示类似于以下的错误消息:
$ go run concTCP.go 12a
listen tcp4: lookup tcp4/12a: nodename nor servname provided, or not known
$ go run concTCP.go -10
listen tcp4: address -10: invalid port
net.Listen()
调用用于告知 Go 程序接受网络连接,从而充当服务器。net.Listen()
的返回值类型为 net.Conn
,它实现了 io.Reader
和 io.Writer
接口。main()
函数还调用 rand.Seed()
函数以初始化随机数生成器。最后,for
循环允许程序继续使用 Accept()
接受新的 TCP 客户端,这些客户端将由 handleConnection()
函数的实例处理,这些实例作为 goroutine 执行。
net.Listen() 的第一个参数
net.Listen()
函数的第一个参数定义了将要使用的网络类型,而第二个参数定义了服务器地址以及服务器将监听的端口号。第一个参数的有效值包括 tcp、tcp4(仅限 IPv4)、tcp6(仅限 IPv6)、udp、udp4(仅限 IPv4)、udp6(仅限 IPv6)、ip、ip4(仅限 IPv4)、ip6(仅限 IPv6)、Unix(Unix 套接字)、Unixgram 和 Unixpacket。
并发 TCP 服务器的实际应用
concTCP.go 需要一个命令行参数,即它将监听的端口号。当 concTCP.go 为 TCP 客户端提供服务时,您将获得的输出类似于以下内容:
$ go run concTCP.go 8001
Serving 127.0.0.1:62554
Serving 127.0.0.1:62556
netstat(1)
的输出可以验证 concTCP.go 在监听更多连接的同时为多个 TCP 客户端提供服务。
$ netstat -anp TCP | grep 8001
tcp4 0 0 127.0.0.1.8001 127.0.0.1.62556 ESTABLISHED
tcp4 0 0 127.0.0.1.62556 127.0.0.1.8001 ESTABLISHED
tcp4 0 0 127.0.0.1.8001 127.0.0.1.62554 ESTABLISHED
tcp4 0 0 127.0.0.1.62554 127.0.0.1.8001 ESTABLISHED
tcp4 0 0 *.8001 *.* LISTEN
前面命令输出的最后一行告诉我们,有一个进程正在监听端口 8001,这意味着您仍然可以连接到 TCP 端口 8001。前两行验证存在一个已建立的 TCP 网络连接,该连接使用端口号 8001 和 62556。类似地,第三行和第四行验证存在另一个已建立的 TCP 连接,该连接使用端口号 8001 和 62554。
此图像显示了 concTCP.go 在为多个 TCP 客户端提供服务时的输出

类似地,下图显示了使用 nc(1)
实用程序实现的两个 TCP 客户端的输出

使用 nc(1)
实用程序作为 concTCP.go 的 TCP 客户端。
您可以在 维基百科 上找到有关 nc(1)
(也称为 netcat(1)
)的更多信息。
总结
因此,您刚刚学习了如何开发一个并发 TCP 服务器,该服务器使用大约 65 行 Go 代码生成随机数,这非常令人印象深刻!如果您希望您的 TCP 服务器执行不同的工作,只需更改 handleConnection()
函数的实现即可。
评论已关闭。