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却是有很大的不一样。进程