golang 網路通訊
title: golang 網路程式設計
golang 網路程式設計
go-shadowsocks是一個非常好的學習golang網路程式設計的例子。在本篇博文中重點
闡述golang網路通訊幾個各種不同協議下服務端和客戶端的實現。
網路通訊協議主要有以下兩種外加一種增加的協議
- golang tcp
- golang upd
- golang kcp:在upd上進行增強的協議,KCP 是一個快速可靠協議,能以比 TCP浪費10%-20%的頻寬的代價,換取平均延遲降低 30%-40%,且最大延遲降低三倍的傳輸效果。
golang的一大優勢就是多核、並行、網路程式設計。通過goroutine與channel可以很方便地協程,協程比執行緒更輕量級,佔用資源更小,可以更好地適用與平行計算。
下面對這三種分別進行說明。
golang tcp
下面根據兩種golang tcp server和client的例子進行說明
單connection的tcp server與client 端
tcp server
package main import ( "net" "fmt" ) func main(){ // tcp 監聽並接受埠 l, err := net.Listen("tcp", "127.0.0.1:65535") if err != nil { fmt.Println(err) return } //最後關閉 defer l.Close() fmt.Println("tcp服務端開始監聽65535埠...") // 使用迴圈一直接受連線 for { fmt.Println("loop test") //Listener.Accept() 接受連線 //conn 是雙方的。和長度為1的channel有些類似。 c, err := l.Accept() if err!= nil { return } //處理tcp請求 go handleConnection(c) } } func handleConnection(c net.Conn) { //一些程式碼邏輯... fmt.Println("tcp服務端開始處理請求...") //讀取 buffer := make([]byte, 1024) //如果客戶端無資料則會阻塞,服務端阻塞,直到等待客戶端傳遞資料。 c.Read(buffer) //服務端成功從阻塞狀態走出,讀取客戶端的資料,並根據自身的介面輸出buffer c.Write(buffer) fmt.Println("tcp服務端開始處理請求完畢...") }
tcp client
package main import ( "net" "fmt" ) func main(){ //net.dial 撥號 獲取tcp連線 conn, err := net.Dial("tcp", "127.0.0.1:65535") if err != nil { fmt.Println(err) return } fmt.Println("獲取127.0.0.1:65535的tcp連線成功...") defer conn.Close() //客戶端這裡不用使用協程。使用協程的話main函式退出,所有go 協程全部死掉。 conn.Write([]byte("echo data to server ,then to client!!!")) fmt.Println("test server") //讀取到buffer buffer := make([]byte, 1024) //如果服務端沒有把資料傳遞過來,那麼客戶端阻塞,直到服務端向其中寫入了資料。 conn.Read(buffer) fmt.Println(string(buffer)) }
net utils
- net.Dial(客戶端呼叫,撥號)
- net.Listen(服務端呼叫,監聽介面)
- TCPListener.Accept(服務端呼叫,接受,建立連線。)
- conn.Read(客戶端服務端都會呼叫,讀取conn中資料)
- conn.write(客戶端服務端都會呼叫,讀取conn中資料)
其它net 提供的函式可以檢視API
為了能夠讓服務端處理多個連線,使用了協程來處理來自多個客戶端的連線請求。
客戶端的實現沒有用到協程。
另外tcp server的for函式非常有趣,在上面的測試例子中不會無限列印loop test
,說明存在阻塞。只有每次有新連線過來才會放開。
雙connection的tcp server與client 端
在上面的例子中,存在著以下的問題:
- server與client之間只存在一條連線。可能會出現這樣的情況:伺服器需要向客戶端推送一些資料。而客戶端建立的連線正處於block狀態。
- server與client建立的連線是短連線。
存在的解決方法如下
- 在client的實現上增加 server模組,監聽來自於伺服器的請求,並進行處理。
- 在server與client另外新建立一條長連線。通過setKeepLive來保活。
- 使用channel,保證客戶端的協程不會死掉。
golang udp
udp 的通訊無需建立lister,直接進行資料的傳輸。
udp server
package main import ( "fmt" "net" ) func main() { // 建立監聽 socket, err := net.ListenUDP("udp4", &net.UDPAddr{ IP:net.IPv4(127,0,0,1), Port: 23452, }) if err != nil { fmt.Println("監聽失敗!", err) return } fmt.Println("監聽成功") defer socket.Close() for { // 讀取資料 data := make([]byte, 4096) read, remoteAddr, err := socket.ReadFromUDP(data) if err != nil { fmt.Println("讀取資料失敗!", err) continue } fmt.Println(read, remoteAddr) fmt.Printf("%s\n\n", data) // 傳送資料 senddata := []byte("hello client!") _, err = socket.WriteToUDP(senddata, remoteAddr) if err != nil { return fmt.Println("傳送資料失敗!", err) } } }
udp client
package main import ( "fmt" "net" ) func main() { // 建立連線 socket, err := net.DialUDP("udp4", nil, &net.UDPAddr{ IP:net.IPv4(127,0,0,1), Port: 23452, }) if err != nil { fmt.Println("連線失敗!", err) return } defer socket.Close() // 傳送資料 senddata := []byte("hello server!") _, err = socket.Write(senddata) if err != nil { fmt.Println("傳送資料失敗!", err) return } // 接收資料 data := make([]byte, 4096) read, remoteAddr, err := socket.ReadFromUDP(data) if err != nil { fmt.Println("讀取資料失敗!", err) return } fmt.Println(read, remoteAddr) fmt.Printf("%s\n", data) }
上面的server和client實現比較簡單了,基本上沒有什麼要說的了。