使用 Go 构建并发 TCP 服务器

开发一个并发 TCP 服务器,使用大约 65 行 Go 代码生成随机数。
350 位读者喜欢这篇文章。
gopher illustrations

Renee French。CC BY 3.0

本文是 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.Readerio.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 客户端提供服务时的输出

The concTCP.go TCP server in action.

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

Using the nc(1) utility as the TCP client to concTCP.go.

使用 nc(1) 实用程序作为 concTCP.go 的 TCP 客户端。

您可以在 维基百科 上找到有关 nc(1)(也称为 netcat(1))的更多信息。

总结

因此,您刚刚学习了如何开发一个并发 TCP 服务器,该服务器使用大约 65 行 Go 代码生成随机数,这非常令人印象深刻!如果您希望您的 TCP 服务器执行不同的工作,只需更改 handleConnection() 函数的实现即可。

User profile image.
Mihalis Tsoukalos 是一位技术作家、UNIX 管理员和开发人员、DBA 和数学家。他是《Go 系统编程》和《精通 Go》的作者。您可以通过 http://www.mtsoukalos.eu/ 和 https://twitter.com/mactsouk 联系他。

评论已关闭。

© . All rights reserved.