go實現tcp 服務器


我們將使用 TCP 協議和協程范式編寫一個簡單的客戶端-服務器應用,一個(web)服務器應用需要響應眾多客戶端的並發請求:Go 會為每一個客戶端產生一個協程用來處理請求。我們需要使用 net 包中網絡通信的功能。它包含了處理 TCP/IP 以及 UDP 協議、域名解析等方法。

服務器端代碼是一個單獨的文件:

示例 1 server.go

package main

import (
    "fmt"
    "net"
)

func main() {
    fmt.Println("Starting the server ...")
    // 創建 listener
    listener, err := net.Listen("tcp", "localhost:50000")
    if err != nil {
        fmt.Println("Error listening", err.Error())
        return //終止程序
    }
    // 監聽並接受來自客戶端的連接
    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("Error accepting", err.Error())
            return // 終止程序
        }
        go doServerStuff(conn)
    }
}

func doServerStuff(conn net.Conn) {
    for {
        buf := make([]byte, 512)
        len, err := conn.Read(buf)
        if err != nil {
            fmt.Println("Error reading", err.Error())
            return //終止程序
        }
        fmt.Printf("Received data: %v\n", string(buf[:len]))
    }
}

在 main() 中創建了一個 net.Listener 類型的變量 listener,他實現了服務器的基本功能:用來監聽和接收來自客戶端的請求(在 localhost 即 IP 地址為 127.0.0.1 端口為 50000 基於TCP協議)。Listen() 函數可以返回一個 error 類型的錯誤變量。用一個無限 for 循環的 listener.Accept() 來等待客戶端的請求。客戶端的請求將產生一個 net.Conn 類型的連接變量。然后一個獨立的協程使用這個連接執行 doServerStuff(),開始使用一個 512 字節的緩沖 data 來讀取客戶端發送來的數據,並且把它們打印到服務器的終端,len 獲取客戶端發送的數據字節數;當客戶端發送的所有數據都被讀取完成時,協程就結束了。這段程序會為每一個客戶端連接創建一個獨立的協程。必須先運行服務器代碼,再運行客戶端代碼。

客戶端代碼寫在另一個文件 client.go 中:

示例 2 client.go

package main

import (
    "bufio"
    "fmt"
    "net"
    "os"
    "strings"
)

func main() {
    //打開連接:
    conn, err := net.Dial("tcp", "localhost:50000")
    if err != nil {
        //由於目標計算機積極拒絕而無法創建連接
        fmt.Println("Error dialing", err.Error())
        return // 終止程序
    }

    inputReader := bufio.NewReader(os.Stdin)
    fmt.Println("First, what is your name?")
    clientName, _ := inputReader.ReadString('\n')
    // fmt.Printf("CLIENTNAME %s", clientName)
    trimmedClient := strings.Trim(clientName, "\r\n") // Windows 平台下用 "\r\n",Linux平台下使用 "\n"
    // 給服務器發送信息直到程序退出:
    for {
        fmt.Println("What to send to the server? Type Q to quit.")
        input, _ := inputReader.ReadString('\n')
        trimmedInput := strings.Trim(input, "\r\n")
        // fmt.Printf("input:--%s--", input)
        // fmt.Printf("trimmedInput:--%s--", trimmedInput)
        if trimmedInput == "Q" {
            return
        }
        _, err = conn.Write([]byte(trimmedClient + " says: " + trimmedInput))
    }
}

客戶端通過 net.Dial 創建了一個和服務器之間的連接。

它通過無限循環從 os.Stdin 接收來自鍵盤的輸入,直到輸入了“Q”。注意裁剪 \r 和 \n 字符(僅 Windows 平台需要)。裁剪后的輸入被 connection 的 Write 方法發送到服務器。

當然,服務器必須先啟動好,如果服務器並未開始監聽,客戶端是無法成功連接的。

如果在服務器沒有開始監聽的情況下運行客戶端程序,客戶端會停止並打印出以下錯誤信息:對tcp 127.0.0.1:50000發起連接時產生錯誤:由於目標計算機的積極拒絕而無法創建連接

可以同時啟動多個客戶端程序。

一下是服務器的輸出:

PS C:\Users\20928\go\src\go_code> go run server.g   erveo
Starting the server ...
Received data: 張三 says: 你好,我叫張三
Received data: 李四 says: 你好,我叫李四,很高興認識你
Received data: 張三 says: 你來自哪里?
Received data: 李四 says: 我來自中國
Received data: 李四 says: 北京

在網絡編程中 net.Dial 函數是非常重要的,一旦你連接到遠程系統,函數就會返回一個 Conn 類型的接口,我們可以用它發送和接收數據。Dial 函數簡潔地抽象了網絡層和傳輸層。所以不管是 IPv4 還是 IPv6,TCP 或者 UDP 都可以使用這個公用接口。

 

 

參考鏈接:https://github.com/unknwon/the-way-to-go_ZH_CN/blob/master/eBook/15.1.md


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM