tcp/ip大協議中,tcp編程你們應該比較熟,應用的場景也不少,可是udp在現實中,應用也很多,而在大部分博文中,都不多對udp的編程進行研究,最近研究了一下udp編程,正好作個記錄。
sheepbao 2017.06.15golang
tcp和udp都是著名的傳輸協議,他們都是基於ip協議,都在OSI模型中傳輸層。tcp咱們都很清楚,它提供了可靠的數據傳輸,而udp咱們也知道,它不提供數據傳輸的可靠性,只是盡力傳輸。 他們的特性決定了它們很大的不一樣,tcp提供可靠性傳輸,有三次握手,4次分手,至關於具備邏輯上的鏈接,能夠知道這個tcp鏈接的狀態,因此咱們都說tcp是面向鏈接的socket,而udp沒有握手,沒有分手,也不存在邏輯上的鏈接,因此咱們也都說udp是非面向鏈接的socket。
咱們都畏懼不知道狀態的東西,因此即便tcp的協議比udp複雜不少,但對於系統應用層的編程來講,tcp編程其實比udp編程容易。而udp相對比較靈活,因此對於udp編程反而沒那麼容易,但其實掌握後udp編程也並不難。編程
2 2 (byte) +---+---+---+---+---+---+---+---+ - | src port | dst port | | +---+---+---+---+---+---+---+---+ 8(bytes) | length | check sum | | +---+---+---+---+---+---+---+---+ - | | + data + | | +---+---+---+---+---+---+---+---+
udp的首部真的很簡單,頭2個字節表示的是原端口,後2個字節表示的是目的端口,端口是系統層區分進程的標識。接着是udp長度,最後就是校驗和,這個其實很重要,如今的系統都是默認開啓udp校驗和的,因此咱們才能確保udp消息傳輸的完整性。若是這個校驗和關閉了,那會讓咱們絕對會很憂傷,由於udp不只不能保證數據必定到達,還不能保證即便數據到了,這個數據是不是正確的。好比:我在發送端發送了「hello」,而接收端卻接收到了「hell」。若是真的是這樣,咱們就必須本身去校驗數據的正確性。還好udp默認開發了校驗,咱們能夠保證udp的數據完整性。服務器
+---------+ | 應用數據 | +---------+ | | v v +---------+---------+ | udp首部 | 應用數據 | +---------+---------+ | | v UDP數據報 v +---------+---------+---------+ | ip首部 | udp首部 | 應用數據 | +---------+---------+---------+ | | v IP數據報 v +---------+---------+---------+---------+---------+ |以太網首部 | ip首部 | udp首部 | 應用數據 |以太網尾部 | +---------+---------+---------+---------+---------+ | 14 20 8 4 | | -> 以太網幀 <- |
數據的封裝和tcp是同樣,應用層的數據加上udp首部,構成udp數據報,再加上ip首部構成ip數據報,最後加上以太網首部和尾部構成以太網幀,通過網卡發送出去。網絡
實踐出真知,編程就須要多實踐,才能體會其中的奧妙。socket
echo服務,實現數據包的回顯,這是不少人網絡編程起點,由於這個服務足夠簡單,但又把網絡的數據流都過了一遍,這裏也用go udp實現一個echo服務。
實現客戶端發送一個「hello」,服務端接收消息並原封不動的返回給客戶度。tcp
server.go
ui
package main import ( "flag" "fmt" "log" "net" ) var addr = flag.String("addr", ":10000", "udp server bing address") func init() { log.SetFlags(log.LstdFlags | log.Lshortfile) flag.Parse() } func main() { //Resolving address udpAddr, err := net.ResolveUDPAddr("udp", *addr) if err != nil { log.Fatalln("Error: ", err) } // Build listining connections conn, err := net.ListenUDP("udp", udpAddr) if err != nil { log.Fatalln("Error: ", err) } defer conn.Close() // Interacting with one client at a time recvBuff := make([]byte, 1024) for { log.Println("Ready to receive packets!") // Receiving a message rn, rmAddr, err := conn.ReadFromUDP(recvBuff) if err != nil { log.Println("Error:", err) return } fmt.Printf("<<< Packet received from: %s, data: %s\n", rmAddr.String(), string(recvBuff[:rn])) // Sending the same message back to current client _, err = conn.WriteToUDP(recvBuff[:rn], rmAddr) if err != nil { log.Println("Error:", err) return } fmt.Println(">>> Sent packet to: ", rmAddr.String()) } }
client1.go
code
package main import ( "flag" "fmt" "log" "net" ) var raddr = flag.String("raddr", "127.0.0.1:10000", "remote server address") func init() { log.SetFlags(log.LstdFlags | log.Lshortfile) flag.Parse() } func main() { // Resolving Address remoteAddr, err := net.ResolveUDPAddr("udp", *raddr) if err != nil { log.Fatalln("Error: ", err) } // Make a connection tmpAddr := &net.UDPAddr{ IP: net.ParseIP("127.0.0.1"), Port: 0, } conn, err := net.DialUDP("udp", tmpAddr, remoteAddr) // Exit if some error occured if err != nil { log.Fatalln("Error: ", err) } defer conn.Close() // write a message to server _, err = conn.Write([]byte("hello")) if err != nil { log.Println(err) } else { fmt.Println(">>> Packet sent to: ", *raddr) } // Receive response from server buf := make([]byte, 1024) rn, rmAddr, err := conn.ReadFromUDP(buf) if err != nil { log.Println(err) } else { fmt.Printf("<<< %d bytes received from: %v, data: %s\n", rn, rmAddr, string(buf[:rn])) } }
client2.go
server
package main import ( "flag" "fmt" "log" "net" ) var ( laddr = flag.String("laddr", "127.0.0.1:9000", "local server address") raddr = flag.String("raddr", "127.0.0.1:10000", "remote server address") ) func init() { log.SetFlags(log.LstdFlags | log.Lshortfile) flag.Parse() } func main() { // Resolving Address localAddr, err := net.ResolveUDPAddr("udp", *laddr) if err != nil { log.Fatalln("Error: ", err) } remoteAddr, err := net.ResolveUDPAddr("udp", *raddr) if err != nil { log.Fatalln("Error: ", err) } // Build listening connections conn, err := net.ListenUDP("udp", localAddr) // Exit if some error occured if err != nil { log.Fatalln("Error: ", err) } defer conn.Close() // write a message to server _, err = conn.WriteToUDP([]byte("hello"), remoteAddr) if err != nil { log.Println(err) } else { fmt.Println(">>> Packet sent to: ", *raddr) } // Receive response from server buf := make([]byte, 1024) rn, remAddr, err := conn.ReadFromUDP(buf) if err != nil { log.Println(err) } else { fmt.Printf("<<< %d bytes received from: %v, data: %s\n", rn, remAddr, string(buf[:rn])) } }
這裏實現echo的服務端和客戶端,和tcp的差很少,可是有一些小細節須要注意。
對於server端,先net.ListenUDP
創建udp一個監聽,返回一個udp鏈接,這裏須要注意udp不像tcp,創建tcp監聽後返回的是一個Listener
,而後阻塞等待接收一個新的鏈接,這樣區別是由於udp一個非面向鏈接的協議,它沒有會話管理。同時也由於udp是非面向鏈接的協議,當接收到消息後,想把消息返回給當前的客戶端時,是不能像tcp同樣,直接往conn裏寫的,而是須要指定遠端地址。
對於client端,相似tcp先Dial
,返回一個鏈接,對於發送消息用Write,接收消息用Read,固然udp也能夠用ReadFromUDP
,這樣能夠知道從哪獲得的消息。但其實client也能夠用另外一種方式寫,如client2.go
程序,先創建一個監聽,返回一個鏈接,用這個鏈接發送消息給服務端和從服務器接收消息,這種方式和tcp卻是有很大的不一樣。進程