VC++實現UDP資料包傳送
UDP協議的全稱是使用者資料包協議,在網路中它與TCP協議一樣用於處理
UDP資料包,是一種無連線的協議。在OSI模型中,在第四層——傳輸層,處於IP協議的上一層。UDP有不提供資料包分組、組裝和不能對資料包進行排序的缺點,也就是說,當報文傳送之後,是無法得知其是否安全完整到達的。UDP用來支援那些需要在計算機之間傳輸資料的網路應用。包括網路視訊會議系統在內的眾多的客戶/伺服器模式的網路應用都需要使用UDP協議。UDP協議從問世至今已經被使用了很多年,雖然其最初的光彩已經被一些類似協議所掩蓋,但是即使是在今天UDP仍然不失為一項非常實用和可行的網路傳輸層協議。
與所熟知的TCP(傳輸控制協議)協議一樣,UDP協議直接位於IP(網際協議)協議的頂層。根據OSI(開放系統互連)參考模型,UDP和TCP都屬於傳輸層協議。
UDP協議的主要作用是將網路資料流量壓縮成資料包的形式。一個典型的資料包就是一個二進位制資料的傳輸單位。每一個數據包的前8個位元組用來包含報頭資訊,剩餘位元組則用來包含具體的傳輸資料。
在選擇使用協議的時候,選擇UDP必須要謹慎。在網路質量令人不十分滿意的環境下,UDP協議資料包丟失會比較嚴重。但是由於UDP的特性:它不屬於連線型協議,因而具有資源消耗小,處理速度快的優點,所以通常音訊、視訊和普通資料在傳送時使用UDP較多,因為它們即使偶爾丟失一兩個資料包,也不會對接收結果產生太大影響。比如我們聊天用的ICQ和QQ就是使用的UDP協議。
UDP報頭由4個域組成,其中每個域各佔用2個位元組,具體如下: UDP源埠號
目標埠號
資料報長度
校驗值
UDP協議使用埠號為不同的應用保留其各自的資料傳輸通道。UDP和TCP協議正是採用這一機制實現對同一時刻內多項應用同時傳送和接收資料的支援。資料傳送一方(可以是客戶端或伺服器端)將UDP資料包通過源埠傳送出去,而資料接收一方則通過目標埠接收資料。有的網路應用只能使用預先為其預留或註冊的靜態埠;而另外一些網路應用則可以使用未被註冊的動態埠。因為UDP報頭使用兩個位元組存放埠號,所以埠號的有效範圍是從0到65535。一般來說,大於49151的埠號都代表動態埠。
資料報的長度是指包括報頭和資料部分在內的總位元組數。因為報頭的長度是固定的,所以該域主要被用來計算可變長度的資料部分(又稱為資料負載)。資料報的最大長度根據操作環境的不同而各異。從理論上說,包含報頭在內的資料報的最大長度為65535位元組。不過,一些實際應用往往會限制資料報的大小,有時會降低到8192位元組。
UDP協議使用報頭中的校驗值來保證資料的安全。校驗值首先在資料傳送方通過特殊的演算法計算得出,在傳遞到接收方之後,還需要再重新計算。如果某個資料報在傳輸過程中被第三方篡改或者由於線路噪音等原因受到損壞,傳送和接收方的校驗計算值將不會相符,由此UDP協議可以檢測是否出錯。這與TCP協議是不同的,後者要求必須具有校驗值。
許多鏈路層協議都提供錯誤檢查,包括流行的乙太網協議,也許你想知道為什麼UDP也要提供檢查和校驗。其原因是鏈路層以下的協議在源端和終端之間的某些通道可能不提供錯誤檢測。雖然UDP提供有錯誤檢測,但檢測到錯誤時,UDP不做錯誤校正,只是簡單地把損壞的訊息段扔掉,或者給應用程式提供警告資訊。
UDP協議的幾個特性
1) UDP是一個無連線協議,傳輸資料之前源端和終端不建立連線,當 UDP它想傳送時就簡單地去抓取來自應用程式的資料,並儘可能快地把它扔到網路上。在傳送端,UDP傳送資料的速度僅僅是受應用程式生成資料的速度、計算機的能力和傳輸頻寬的限制;在接收端,UDP把每個訊息段放在佇列中,應用程式每次從佇列中讀一個訊息段。
(2) 由於傳輸資料不建立連線,因此也就不需要維護連線狀態,包括收發狀態等,因此一臺服務機可同時向多個客戶機傳輸相同的訊息。
(3) UDP資訊包的標題很短,只有8個位元組,相對於TCP的20個位元組資訊包的額外開銷很小。
(4) 吞吐量不受擁擠控制演算法的調節,只受應用軟體生成資料的速率、傳輸頻寬、源端和終端主機效能的限制。
(5)UDP使用盡最大努力交付,即不保證可靠交付,因此主機不需要維持複雜的連結狀態表(這裡面有許多引數)。
(6)UDP是面向報文的。傳送方的UDP對應用程式交下來的報文,在新增首部後就向下交付給IP層。既不拆分,也不合並,而是保留這些報文的邊界,因此,應用程式需要選擇合適的報文大小。
雖然UDP是一個不可靠的協議,但它是分發資訊的一個理想協議。例如,在螢幕上報告股票市場、在螢幕上顯示航空資訊等等。UDP也用在路由資訊協議RIP(Routing Information Protocol)中修改路由表。在這些應用場合下,如果有一個訊息丟失,在幾秒之後另一個新的訊息就會替換它。UDP廣泛用在多媒體應用中,例如,Progressive Networks公司開發的RealAudio軟體,它是在因特網上把預先錄製的或者現場音樂實時傳送給客戶機的一種軟體,該軟體使用的RealAudio audio-on-demand protocol協議就是執行在UDP之上的協議,大多數因特網電話軟體產品也都執行在UDP之上。
請見程式碼
#include <stdio.h>#include <ws2tcpip.h> // 定義了IP_HDRINCLCInitSock theSock;/* 計算UDP偽頭校驗和。UDP校驗和基於如下幾個域: 源IP地址 目的IP地址 8位0域 8位協議域 16位UDP長度 16位源埠號 16位目的埠號 16位UDP封包長度 16位UDP校驗和(0) UDP淨荷 */void ComputeUdpPseudoHeaderChecksum( IPHeader *pIphdr, UDPHeader *pUdphdr, char *payload, int payloadlen ){ char buff[1024]; char *ptr = buff; int chksumlen = 0; ULONG zero = 0; // 包含源IP地址和目的IP地址 memcpy(ptr, &pIphdr->ipSource, sizeof(pIphdr->ipSource)); ptr += sizeof(pIphdr->ipSource); chksumlen += sizeof(pIphdr->ipSource); memcpy(ptr, &pIphdr->ipDestination, sizeof (pIphdr->ipDestination)); ptr += sizeof(pIphdr->ipDestination); chksumlen += sizeof(pIphdr->ipDestination); // 包含8位0域 memcpy(ptr, &zero, 1); ptr += 1; chksumlen += 1; // 協議 memcpy(ptr, &pIphdr->ipProtocol, sizeof(pIphdr->ipProtocol)); ptr += sizeof(pIphdr->ipProtocol); chksumlen += sizeof(pIphdr->ipProtocol); // UDP長度 memcpy(ptr, &pUdphdr->len, sizeof(pUdphdr->len)); ptr += sizeof(pUdphdr->len); chksumlen += sizeof(pUdphdr->len); // UDP源埠號 memcpy(ptr, &pUdphdr->sourcePort, sizeof(pUdphdr->sourcePort)); ptr += sizeof(pUdphdr->sourcePort); chksumlen += sizeof(pUdphdr->sourcePort); // UDP目的埠號 memcpy(ptr, &pUdphdr->destinationPort, sizeof(pUdphdr->destinationPort)); ptr += sizeof(pUdphdr->destinationPort); chksumlen += sizeof(pUdphdr->destinationPort); // 又是UDP長度 memcpy(ptr, &pUdphdr->len, sizeof(pUdphdr->len)); ptr += sizeof(pUdphdr->len); chksumlen += sizeof(pUdphdr->len); // 16位的UDP校驗和,置為0 memcpy(ptr, &zero, sizeof(USHORT)); ptr += sizeof(USHORT); chksumlen += sizeof(USHORT); // 淨荷 memcpy(ptr, payload, payloadlen); ptr += payloadlen; chksumlen += payloadlen; // 補齊到下一個16位邊界 for(int i=0; i<payloadlen%2; i++) { *ptr = 0; ptr++; chksumlen++; } // 計算這個校驗和,將結果填充到UDP頭 pUdphdr->checksum = checksum((USHORT*)buff, chksumlen);}int main(){ // 輸入引數資訊 char szDestIp[] = "10.16.115.88"; // <<== 填寫目的IP地址 char szSourceIp[] = "127.0.0.1"; // <<== 填寫您自己的IP地址 USHORT nDestPort = 4567; USHORT nSourcePort = 8888; char szMsg[] = "This is a test \r\n"; int nMsgLen = strlen(szMsg); // 建立原始套節字 SOCKET sRaw = ::socket(AF_INET, SOCK_RAW, IPPROTO_UDP); // 有效IP頭包含選項 BOOL bIncl = TRUE; ::setsockopt(sRaw, IPPROTO_IP, IP_HDRINCL, (char *)&bIncl, sizeof(bIncl)); char buff[1024] = { 0 }; // IP頭 IPHeader *pIphdr = (IPHeader *)buff; pIphdr->iphVerLen = (4<<4 | (sizeof(IPHeader)/sizeof(ULONG))); pIphdr->ipLength = ::htons(sizeof(IPHeader) + sizeof(UDPHeader) + nMsgLen); pIphdr->ipTTL = 128; pIphdr->ipProtocol = IPPROTO_UDP; pIphdr->ipSource = ::inet_addr(szSourceIp); pIphdr->ipDestination = ::inet_addr(szDestIp); pIphdr->ipChecksum = checksum((USHORT*)pIphdr, sizeof(IPHeader)); // UDP頭 UDPHeader *pUdphdr = (UDPHeader *)&buff[sizeof(IPHeader)]; pUdphdr->sourcePort = htons(8888); pUdphdr->destinationPort = htons(nDestPort); pUdphdr->len = htons(sizeof(UDPHeader) + nMsgLen); pUdphdr->checksum = 0; char *pData = &buff[sizeof(IPHeader) + sizeof(UDPHeader)]; memcpy(pData, szMsg, nMsgLen); ComputeUdpPseudoHeaderChecksum(pIphdr, pUdphdr, pData, nMsgLen); // 設定目的地址 SOCKADDR_IN destAddr = { 0 }; destAddr.sin_family = AF_INET; destAddr.sin_port = htons(nDestPort); destAddr.sin_addr.S_un.S_addr = ::inet_addr(szDestIp); // 傳送原始UDP封包 int nRet; for(int i=0; i<5; i++) { nRet = ::sendto(sRaw, buff, sizeof(IPHeader) + sizeof(UDPHeader) + nMsgLen, 0, (sockaddr*)&destAddr, sizeof(destAddr)); if(nRet == SOCKET_ERROR) { printf(" sendto() failed: %d \n", ::WSAGetLastError()); break; } else { printf(" sent %d bytes \n", nRet); } } ::closesocket(sRaw); getchar(); return 0;}