在 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 %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” 字串,為它提供服務的 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: http://opensource.com/article/18/5/building-concurrent-tcp-server-go
作者: Mihalis Tsoukalos 選題: lkxed 譯者: lkxed 校對: wxy
本文由 LCTT 原創編譯,Linux中國榮譽推出
- 從深度到統信,從桌面到根社群
- 用 Curtail GUI 應用輕鬆壓縮 Linux 中的影象 | Linux 中國
- Docker Compose:搭建開發環境的好方式
- 在 Linux 上使用 WineZGUI 執行 Windows 應用和遊戲 | Linux 中國
- 使用 Flatseal 管理 Flatpak 的許可權 | Linux 中國
- 我如何利用 Xfce 桌面為舊電腦賦予新生 | Linux 中國
- 用 tmate 分享你的 Linux 終端 | Linux 中國
- 企業為何使用開源軟體,又為何推動開源軟體的發展 | Linux 中國
- 使用 LibreOffice 進行首次開源貢獻的 6 種簡單方法 | Linux 中國
- 給 Linux 初學者的 7 條建議 | Linux 中國
- 詳解在 Ubuntu 中引導到救援模式或緊急模式 | Linux 中國
- 如何通過 chroot 恢復 Arch Linux 系統 | Linux 中國
- 分裂使 Linux 超越 Windows 的夢想破滅了 | Linux 中國
- 在商業之外,為學生們教授開源知識 | Linux 中國
- 如何修復 “yay: error while loading shared libraries: libalpm.so.12” | Linux 中國
- Simula 誕生之前的面向物件程式設計 | Linux 中國
- 使用 Linux 上的開源財務工具 Skrooge 管理你的預算
- Linux 桌面刻薄版點評
- 軟體包 “被標記為手動安裝”?這是什麼意思? | Linux 中國
- 移動版 GNOME Shell:希望之始,期望滿滿 | Linux 中國