這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
在上一篇文章Go socket編程實踐: TCP伺服器和用戶端實現, 我們實現了一個ECHO伺服器, 並且也實現了一個用戶端來訪問伺服器。
這篇文章講解如何?一個UDP伺服器和用戶端。
這次我們使用的協議是 RFC 868,
此協議提供了一個獨立於網站的,機器可讀的日期和時間資訊。時間服務返回的提供了一個32位的數字,是從1900年1月1日午夜到現在的秒數。
RFC 868定義時間協議使用連接埠37, TCP和UDP協議都可以。
另外還有兩個關於時間/日期的RFC協議。
NTP (RFC 1305)是網路時間協議,提供了精確的時間同步。
daytime (RFC 867)在TCP連接埠13偵聽,返回ACSII格式的日期和時間。
TCP/IP模型中,UDP為網路層以上和應用程式層以下提供了一個簡單的介面。UDP只提供資料的不可靠傳遞,它一旦把應用程式發給網路層的資料發送出去,就不保留資料備份(所以UDP有時候也被認為是不可靠的資料報協議)。UDP在IP資料報的頭部僅僅加入了複用和資料校正(欄位)。
UDP首部欄位由4個部分組成,其中兩個是可選的。各16bit的來源連接埠和目的連接埠用來標記發送和接受的應用進程。因為UDP不需要應答,所以來源連接埠是可選的,如果來源連接埠不用,那麼置為零。在目的連接埠後面是長度固定的以位元組為單位的長度域,用來指定UDP資料報包括資料部分的長度,長度最小值為8byte。首部剩下地16bit是用來對首部和資料部分一起做校正和(Checksum)的,這部分是可選的,但在實際應用中一般都使用這一功能。
由於缺乏可靠性且屬於非串連導向協定,UDP應用一般必須允許一定量的丟包、出錯和複製貼上。但有些應用,比如TFTP,如果需要則必須在應用程式層增加根本的可靠機制。但是絕大多數UDP應用都不需要可靠機制,甚至可能因為引入可靠機制而降低效能。流媒體(串流技術)、即時多媒體遊戲和IP電話(VoIP)一定就是典型的UDP應用。如果某個應用需要很高的可靠性,那麼可以用傳輸控制通訊協定(TCP協議)來代替UDP。
由於缺乏擁塞控制(congestion control),需要基於網路的機制來減少因失控和高速UDP流量負荷而導致的擁塞崩潰效應。換句話說,因為UDP寄件者不能夠檢測擁塞,所以像使用包隊列和丟棄技術的路由器這樣的網路基本裝置往往就成為降低UDP過大通訊量的有效工具。資料報擁塞控制協議(DCCP)設計成通過在諸如流媒體類型的高速率UDP流中,增加主機擁塞控制,來減小這個潛在的問題。
典型網路上的眾多使用UDP協議的關鍵應用一定程度上是相似的。這些應用程式套件括網域名稱系統(DNS)、簡易網路管理通訊協定(SNMP)、動態主機設定通訊協定(DHCP)、路由資訊協議(RIP)和某些影音串流服務等等。、
伺服器
Go語言套件中處理UDP Socket和TCP Socket不同的地方就是在伺服器端處理多個用戶端請求資料包的方式不同,UDP缺少了對用戶端串連請求的Accept函數。其他基本幾乎一模一樣,只有TCP換成了UDP而已。UDP的幾個主要函數如下所示:
123 |
func ListenUDP(net string, laddr *UDPAddr) (c *UDPConn, err os.Error)func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err os.Errorfunc (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (n int, err os.Error) |
net.ListenUDP返回UDPConn對象, 可以讀取資料,以及往相應的client發送資料 (指定UDPAddr)。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 |
package mainimport ("encoding/binary""flag""fmt""net""os""time")var host = flag.String("host", "", "host")var port = flag.String("port", "37", "port")func main() {flag.Parse()addr, err := net.ResolveUDPAddr("udp", *host+":"+*port)if err != nil {fmt.Println("Can't resolve address: ", err)os.Exit(1)}conn, err := net.ListenUDP("udp", addr)if err != nil {fmt.Println("Error listening:", err)os.Exit(1)}defer conn.Close()for {handleClient(conn)}}func handleClient(conn *net.UDPConn) {data := make([]byte, 1024)n, remoteAddr, err := conn.ReadFromUDP(data)if err != nil {fmt.Println("failed to read UDP msg because of ", err.Error())return}daytime := time.Now().Unix()fmt.Println(n, remoteAddr)b := make([]byte, 4)binary.BigEndian.PutUint32(b, uint32(daytime))conn.WriteToUDP(b, remoteAddr)} |
時間數是64位的,需要將其轉換成一個32位的位元組。
執行 go run timeserver.go啟動伺服器
用戶端
用戶端代碼和TCP類似。
12345 |
func ResolveUDPAddr(net, addr string) (*UDPAddr, os.Error)func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err os.Error)func ListenUDP(net string, laddr *UDPAddr) (c *UDPConn, err os.Error)func (c *UDPConn) Read(b []byte) (int, error)func (c *UDPConn) Write(b []byte) (int, error) |
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 |
package mainimport ("encoding/binary""flag""fmt""net""os""time")var host = flag.String("host", "localhost", "host")var port = flag.String("port", "37", "port")//go run timeclient.go -host time.nist.govfunc main() {flag.Parse()addr, err := net.ResolveUDPAddr("udp", *host+":"+*port)if err != nil {fmt.Println("Can't resolve address: ", err)os.Exit(1)}conn, err := net.DialUDP("udp", nil, addr)if err != nil {fmt.Println("Can't dial: ", err)os.Exit(1)}defer conn.Close()_, err = conn.Write([]byte(""))if err != nil {fmt.Println("failed:", err)os.Exit(1)}data := make([]byte, 4)_, err = conn.Read(data)if err != nil {fmt.Println("failed to read UDP msg because of ", err)os.Exit(1)}t := binary.BigEndian.Uint32(data)fmt.Println(time.Unix(int64(t), 0).String())os.Exit(0)} |
執行go run timeclient.go測試UDP。
也可以用公網的時間伺服器測試: go run timeclient.go -host time.nist.gov
參考
- https://tools.ietf.org/html/rfc868