我們將使用 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