網路通訊1:UDP
#UDP協議
###1.簡介
UDP(User Datagram Protocol),使用者資料報協議,是OSI(Open System Interconnection,開放式系統互聯) 參考模型中一種無連線的傳輸層協議,提供面向事務的簡單不可靠資訊傳送服務,IETF RFC 768是UDP的正式規範。UDP提供了無連線通訊,且不對傳送資料包進行可靠性保證,適合於一次傳輸少量資料,UDP傳輸的可靠性由應用層負責。常用的UDP埠號有:
UDP報文沒有可靠性保證、順序保證和流量控制欄位等,可靠性較差。但是正因為UDP協議的控制選項較少,在資料傳輸過程中延遲小、資料傳輸效率高,適合對可靠性要求不高的應用程式,或者可以保障可靠性的應用程式,如DNS、TFTP、SNMP等。
UDP在IP報文中的位置如下圖所示:
###2.UDP使用
在選擇使用協議的時候,選擇UDP必須要謹慎。在網路質量令人十分不滿意的環境下,UDP協議資料包丟失會比較嚴重。但是由於UDP的特性:它不屬於連線型協議,因而具有資源消耗小,處理速度快的優點,所以通常音訊、視訊和普通資料在傳送時使用UDP較多,因為它們即使偶爾丟失一兩個資料包,也不會對接收結果產生太大影響。比如我們聊天用的QQ就是使用的UDP協議。
既然UDP是一種不可靠的網路協議,那麼還有什麼使用價值或必要呢?其實不然,在有些情況下UDP協議可能會變得非常有用。因為UDP具有TCP所望塵莫及的速度優勢。雖然TCP協議中植入了各種安全保障功能,但是在實際執行的過程中會佔用大量的系統開銷,無疑使速度受到嚴重的影響。反觀UDP由於排除了資訊可靠傳遞機制,將安全和排序等功能移交給上層應用來完成,極大降低了執行時間,使速度得到了保證。
關於UDP協議的最早規範是RFC768,1980年釋出。儘管時間已經很長,但是UDP協議仍然繼續在主流應用中發揮著作用。包括視訊電話會議系統在內的許多應用都證明了UDP協議的存在價值。因為相對於可靠性來說,這些應用更加註重實際效能,所以為了獲得更好的使用效果(例如,更高的畫面幀重新整理速率)往往可以犧牲一定的可靠性(例如,畫面質量)。這就是UDP和TCP兩種協議的權衡之處。根據不同的環境和特點,兩種傳輸協議都將在今後的網路世界中發揮更加重要的作用。
###3.UDP報頭
UDP報頭由4個域組成,其中每個域各佔用2個位元組,如下圖所示:
-
UDP協議使用埠號為不同的應用保留其各自的資料傳輸通道。UDP和TCP協議正是採用這一機制實現對同一時刻內多項應用同時傳送和接收資料的支援。資料傳送一方(可以是客戶端或伺服器端)將UDP資料包通過源埠傳送出去,而資料接收一方則通過目標埠接收資料。有的網路應用只能使用預先為其預留或註冊的靜態埠;而另外一些網路應用則可以使用未被註冊的動態埠。因為UDP報頭使用兩個位元組存放埠號,所以埠號的有效範圍是從0到65535。一般來說,大於49151的埠號都代表動態埠。
-
資料報的長度是指包括報頭和資料部分在內的總位元組數。因為報頭的長度是固定的,所以該域主要被用來計算可變長度的資料部分(又稱為資料負載)。資料報的最大長度根據操作環境的不同而各異。從理論上說,包含報頭在內的資料報的最大長度為65535位元組。不過,一些實際應用往往會限制資料報的大小,有時會降低到8192位元組。(對於一次傳送多少位元組比較好,後面會講到)
-
UDP協議使用報頭中的校驗值來保證資料的安全。校驗值首先在資料傳送方通過特殊的演算法計算得出,在傳遞到接收方之後,還需要再重新計算。如果某個資料報在傳輸過程中被第三方篡改或者由於線路噪音等原因受到損壞,傳送和接收方的校驗計算值將不會相符,由此UDP協議可以檢測是否出錯。這與TCP協議是不同的,後者要求必須具有校驗值。
-
許多鏈路層協議都提供錯誤檢查,包括流行的乙太網協議,也許你想知道為什麼UDP也要提供檢查和校驗。其原因是鏈路層以下的協議在源端和終端之間的某些通道可能不提供錯誤檢測。雖然UDP提供有錯誤檢測,但檢測到錯誤時,UDP不做錯誤校正,只是簡單地把損壞的訊息段扔掉,或者給應用程式提供警告資訊。
###UDP特性 -
UDP是一個無連線協議,傳輸資料之前源端和終端不建立連線,當UDP它想傳送時就簡單地去抓取來自應用程式的資料,並儘可能快地把它扔到網路上。在傳送端,UDP傳送資料的速度僅僅是受應用程式生成資料的速度、計算機的能力和傳輸頻寬的限制;在接收端,UDP把每個訊息段放在佇列中,應用程式每次從佇列中讀一個訊息段。
-
由於傳輸資料不建立連線,因此也就不需要維護連線狀態,包括收發狀態等,因此一臺服務機可同時向多個客戶機傳輸相同的訊息。
-
UDP資訊包的標題很短,只有8個位元組,相對於TCP的20個位元組資訊包的額外開銷很小。
-
吞吐量不受擁擠控制演算法的調節,只受應用軟體生成資料的速率、傳輸頻寬、源端和終端主機效能的限制。
-
UDP使用盡最大努力交付,即不保證可靠交付,因此主機不需要維持複雜的連結狀態表(這裡面有許多引數)。
-
UDP是面向報文的。傳送方的UDP對應用程式交下來的報文,在新增首部後就向下交付給IP層。既不拆分,也不合並,而是保留這些報文的邊界,因此,應用程式需要選擇合適的報文大小。
###例項
####服務端實現
import (
"os"
"fmt"
"net"
)
//處理錯誤資訊
func checkError(err error) {
if err != nil { //指標不為空
fmt.Println("Error", err.Error())
os.Exit(1)
}
}
func receiveUDPMsg(udpConn *net.UDPConn) {
//宣告30位元組的緩衝區
buffer := make([]byte, 30)
//從udpConn讀取客戶端傳送過來的資料,放在緩衝區中(阻塞方法)
//返回值:n=讀到的位元組長度,remoteAddr=客戶端地址,err=錯誤
n, remoteAddr, err := udpConn.ReadFromUDP(buffer) //從udp接收資料
if err != nil {
fmt.Println("Error", err.Error()) //列印錯誤資訊
return
}
fmt.Printf("接收到來自%v的訊息:%s", remoteAddr,string(buffer[0:n]))
//向遠端地址中寫入資料
_, err = udpConn.WriteToUDP([]byte("hao nimei"), remoteAddr)
checkError(err)
}
func main() {
//解析IP和埠得到UDP地址
udp_addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:8848")
checkError(err)
//在解析得到的地址上建立UDP監聽
udpConn, err := net.ListenUDP("udp", udp_addr)
defer udpConn.Close() //關閉連結
checkError(err)
//從udpConn中接收UDP訊息
receiveUDPMsg(udpConn) //收訊息
}
####客戶端實現
import (
"net"
"fmt"
"os"
)
func main() {
//請求連線伺服器,得到連線物件
conn,err:=net.Dial("udp","127.0.0.1:8848")
defer conn.Close()
if err!=nil{
fmt.Println("網路連接出錯")
os.Exit(1)
}
//向連線中寫入訊息
conn.Write([]byte("hello nimei"))
fmt.Println("傳送訊息","hello nimei")
//讀取代表收取訊息(阻塞)
buffer := make([]byte, 30)
n, err := conn.Read(buffer)
if err!=nil{
fmt.Println("讀取訊息錯誤:err=",err)
os.Exit(1)
}
fmt.Println("收到訊息",string(buffer[:n]))
}