Linux中國

在 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.Readerio.Writer 介面。此外,main() 函數中還調用了 rand.Seed() 函數,用於初始化隨機數生成器。最後,for 循環允許程序一直使用 Accept() 函數來接受 TCP 客戶端的連接請求,並以 goroutine 的方式來運行 handleConnection(c) 函數,處理客戶端的後續請求。

net.Listen() 的第一個參數

net.Listen() 函數的第一個參數定義了使用的網路類型,而第二個參數定義了服務端監聽的地址和埠號。第一個參數的有效值為 tcptcp4tcp6udpudp4udp6ipip4ip6Unix(Unix 套接字)、UnixgramUnixpacket,其中:tcp4udp4ip4 只接受 IPv4 地址,而 tcp6udp6ip6 只接受 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 客戶端時的輸出:

concTCP.go TCP 服務端測試

類似地,下面這張圖片顯示了兩個 TCP 客戶端的輸出(使用了 nc 工具):

是用 nc 工具作為 concTCP.go 的 TCP 客戶端

你可以在 維基百科 上找到更多關於 nc(即 netcat)的信息。

總結

現在,你學會了如何用大約 65 行 Go 代碼來開發一個生成隨機數、支持並發的 TCP 服務端,這真是太棒了!如果你想要讓你的 TCP 服務端執行別的任務,只需要修改 handleConnection() 函數即可。

via: https://opensource.com/article/18/5/building-concurrent-tcp-server-go

作者:Mihalis Tsoukalos 選題:lkxed 譯者:lkxed 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國