在 Go 中實現一個支持並發的 TCP 服務端
僅用大約 65 行代碼,開發一個用於生成隨機數、支持並發的 TCP 服務端。
TCP 和 UDP 服務端隨處可見,它們基於 TCP/IP 協議棧,通過網路為客戶端提供服務。在這篇文章中,我將介紹如何使用 Go 語言 開發一個用於返回隨機數、支持並發的 TCP 服務端。對於每一個來自 TCP 客戶端的連接,它都會啟動一個新的 goroutine(輕量級線程)來處理相應的請求。
你可以在 GitHub 上找到本項目的源碼:concTcp.go。
處理 TCP 連接
這個程序的主要邏輯在 handleConnection()
函數中,具體實現如下:
func handleConnection(c net.Conn) {
fmt.Printf("Serving %sn", 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」 字元串,為它提供服務的 goroutine 就會終止;否則,TCP 服務端就會返回一個隨機數給它。只要客戶端不主動終止,服務端就會一直提供服務,這是由 for
循環保證的。具體來說,for
循環中的代碼使用了 bufio.NewReader(c).ReadString('n')
來逐行讀取客戶端發來的數據,並使用 c.Write([]byte(string(result)))
來返回數據(生成的隨機數)。你可以在 Go 的 net 標準包 文檔 中了解更多。
支持並發
在 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.Conn
,後者實現了 io.Reader
和 io.Writer
介面。此外,main()
函數中還調用了 rand.Seed()
函數,用於初始化隨機數生成器。最後,for
循環允許程序一直使用 Accept()
函數來接受 TCP 客戶端的連接請求,並以 goroutine 的方式來運行 handleConnection(c)
函數,處理客戶端的後續請求。
net.Listen() 的第一個參數
net.Listen()
函數的第一個參數定義了使用的網路類型,而第二個參數定義了服務端監聽的地址和埠號。第一個參數的有效值為 tcp
、tcp4
、tcp6
、udp
、udp4
、udp6
、ip
、ip4
、ip6
、Unix
(Unix 套接字)、Unixgram
和 Unixpacket
,其中:tcp4
、udp4
和 ip4
只接受 IPv4 地址,而 tcp6
、udp6
和 ip6
只接受 IPv6 地址。
服務端並發測試
concTCP.go
需要一個命令行參數,來指定監聽的埠號。當它開始服務 TCP 客戶端時,你會得到類似下面的輸出:
$ go run concTCP.go 8001
Serving 127.0.0.1:62554
Serving 127.0.0.1:62556
netstat
的輸出可以確認 congTCP.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 客戶端時的輸出:
類似地,下面這張圖片顯示了兩個 TCP 客戶端的輸出(使用了 nc
工具):
你可以在 維基百科 上找到更多關於 nc
(即 netcat
)的信息。
總結
現在,你學會了如何用大約 65 行 Go 代碼來開發一個生成隨機數、支持並發的 TCP 服務端,這真是太棒了!如果你想要讓你的 TCP 服務端執行別的任務,只需要修改 handleConnection()
函數即可。
via: https://opensource.com/article/18/5/building-concurrent-tcp-server-go
作者:Mihalis Tsoukalos 選題:lkxed 譯者:lkxed 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive