1. UDP概述
UDP 是User Datagram Protocol的簡稱, 中文名是用戶數據報協議,是OSI(Open System Interconnection,開放式系統互聯) 參考模型中一種無連接的傳輸層協議,提供面向事務的簡單不可靠信息傳送服務,IETF RFC 768是UDP的正式規范。UDP在IP報文的協議號是17。
UDP協議全稱是用戶數據報協議 ,在網絡中它與TCP協議一樣用於處理數據包,是一種無連接的協議。在OSI模型中,在第四層--傳輸層,處於IP協議的上一層。UDP有不提供數據包分組、組裝和不能對數據包進行排序的缺點,也就是說,當報文發送之后,是無法得知其是否安全完整到達的。UDP用來支持那些需要在計算機之間傳輸數據的網絡應用。包括網絡視頻會議系統在內的眾多的客戶/服務器模式的網絡應用都需要使用UDP協議。UDP協議從問世至今已經被使用了很多年,雖然其最初的光彩已經被一些類似協議所掩蓋,但是即使是在今天UDP仍然不失為一項非常實用和可行的網絡傳輸層協議。
與所熟知的TCP(傳輸控制協議)協議一樣,UDP協議直接位於IP(網際協議)協議的頂層。根據OSI(開放系統互連)參考模型,UDP和TCP都屬於傳輸層協議。UDP協議的主要作用是將網絡數據流量壓縮成數據包的形式。一個典型的數據包就是一個二進制數據的傳輸單位。每一個數據包的前8個字節用來包含報頭信息,剩余字節則用來包含具體的傳輸數據。
UDP協議全稱是用戶數據報協議 ,在網絡中它與TCP協議一樣用於處理數據包,是一種無連接的協議。在OSI模型中,在第四層--傳輸層,處於IP協議的上一層。UDP有不提供數據包分組、組裝和不能對數據包進行排序的缺點,也就是說,當報文發送之后,是無法得知其是否安全完整到達的。UDP用來支持那些需要在計算機之間傳輸數據的網絡應用。包括網絡視頻會議系統在內的眾多的客戶/服務器模式的網絡應用都需要使用UDP協議。UDP協議從問世至今已經被使用了很多年,雖然其最初的光彩已經被一些類似協議所掩蓋,但是即使是在今天UDP仍然不失為一項非常實用和可行的網絡傳輸層協議。
與所熟知的TCP(傳輸控制協議)協議一樣,UDP協議直接位於IP(網際協議)協議的頂層。根據OSI(開放系統互連)參考模型,UDP和TCP都屬於傳輸層協議。UDP協議的主要作用是將網絡數據流量壓縮成數據包的形式。一個典型的數據包就是一個二進制數據的傳輸單位。每一個數據包的前8個字節用來包含報頭信息,剩余字節則用來包含具體的傳輸數據。
在使用TCP編寫的應用程序與使用UDP編寫的應用程序之間存在一些本質差異,其原因在於這兩個傳輸層之間的差別:UDP是無連接不可靠的數據報協議,非常不同於TCP提供的面向連接的可靠字節流協議。然而,相比TCP,有些場合確實更適合使用UDP,典型應用程序又:DNS(域名系統)、NFS(網絡文件系統)和SNMP(簡單網絡管理協議)。
2. UDP套接字編程
與面向連接的協議相比,面向無連接協議極為不同。其中一個重要的不同點就是客戶端與服務器之間不必建立連接。
對於UDP套接字編程而言,服務器創建套接字后,調用bind()函數將套接字與准備接收數據的接口綁定在一起。和TCP編程不同的是,應用程序不必調用listen()和accept()函數等待客戶端的連接。而只需要等待接收數據了。開發UDP套接字應用程序,有兩個重要的函數sendto()和recvfrom()。服務器采用recvfrom()來接收來自客戶端的數據報,並獲得客戶端的端地址,之后向客戶端發送數據時,采用sendto()函數。
對於UDP套接字編程而言,服務器創建套接字后,調用bind()函數將套接字與准備接收數據的接口綁定在一起。和TCP編程不同的是,應用程序不必調用listen()和accept()函數等待客戶端的連接。而只需要等待接收數據了。開發UDP套接字應用程序,有兩個重要的函數sendto()和recvfrom()。服務器采用recvfrom()來接收來自客戶端的數據報,並獲得客戶端的端地址,之后向客戶端發送數據時,采用sendto()函數。
下圖為
UDP套接字編程流程圖:

從圖示中可以明顯的看出UDP套接字網絡編程與TCP的區別。
3. UDP套接字函數
套接字創建socket()、地址綁定bind()函數與TCP套接字編程相同,具體請參考
基本套接字編程(1) -- tcp篇,此處僅介紹消息傳輸函數sendto()與recvfrom();
3.1 消息發送函數sendto()
函數原型:
#include <sys/socket.h> ssize_t sendto(int sockfd , const void *buf , size_t nbytes , int flags , const struct sockaddr *to , socklen_t addrlen); <span style="white-space:pre"> </span>返回值:成功返回寫的字節數,出錯返回-1函數說明:
sendto() 用來將數據由指定的socket傳給對方主機。參數s為已建好連線的socket,如果利用UDP協議則不需經過連線操作。參數buf指向欲連線的數據內容,參數flags 一般設0,詳細描述請參考send()。參數to用來指定欲傳送的網絡地址,結構sockaddr請參考bind()。參數addrlen為sockaddr的結構長度。
參數:
- 前三個參數sockfd , buff 和 nbytes等同於read 和 write函數的三個參數:描述符、指向寫出緩沖區的指針和寫字節數;
- falgs參數一般置0;
- to參數指向一個含有數據報接收者的協議地址(如IP地址和端口號)的套接字地址結構,其大小由addrlen指定;
返回值:
若無錯誤發生,返回所發送數據的總數(請注意這個數字可能小於len中所規定的大小)。否則的話,返回SOCKET_ERROR錯誤(-1),應用程序可通過WSAGetLastError()獲取相應錯誤代碼。
- WSANOTINITIALISED:在使用此API之前應首先成功地調用WSAStartup();
- WSAENETDOWN:WINDOWS套接口實現檢測到網絡子系統失效;
- WSAEACESS:要求地址為廣播地址,但相關標志未能正確設置;
- WSAEINTR:通過一個WSACancelBlockingCall()來取消一個(阻塞的)調用;
- WSAEINPROGRESS:一個阻塞的WINDOWS套接口調用正在運行中;
- WSAEFAULT:buf或to參數不是用戶地址空間的一部分,或to參數太小(小於sockaddr結構大小);
- WSAENETRESET:由於WINDOWS套接口實現放棄了連接,故該連接必需被復位;
- WSAENOBUFS:WINDOWS套接口實現報告一個緩沖區死鎖;
- WSAENOTCONN:套接口未被連接;
- WSAENOTSOCK:描述字不是一個套接口;
- WSAEOPNOTSUPP:已設置了MSG_OOB,但套接口非SOCK_STREAM類型;
- WSAESHUTDOWN:套接口已被關閉。一個套接口以1或2的how參數調用shutdown()關閉后,無法再用sned()函數;
- WSAEWOULDBLOCK:套接口被標志為非阻塞, 但該調用會產生阻塞;
- WSAEMSGSIZE:套接口為SOCK_DGRAM類型,且數據報大於WINDOWS套接口實現所支持的最大值;
- WSAECONNABORTED:由於超時或其他原因引起虛電路的中斷;
- WSAECONNRESET:虛電路被遠端復位;
- WSAEADDRNOTAVAIL:所指地址無法從本地主機獲得;
- WSAEAFNOSUPPORT:所指定地址族中地址無法與本套接口一切使用;
- WSAEDESADDRREQ:需要目的地址;
- WSAENETUNREACH:當前無法從本主機聯上網絡;
3.2 消息接收函數recvfrom()
函數原型:
#include <sys/socket.h> ssize_t recvfrom(int sockfd , const void *buf , size_t nbytes , int flags , const struct sockaddr *from, socklen_t addrlen); 返回值:成功返回寫的字節數,出錯返回-1函數說明:
recvfrom()用來接收遠程主機經指定的socket傳來的數據,並把數據傳到由參數buf指向的內存空間,參數nbytes為可接收數據的最大長度.參數,flags一般設0,其他數值定義參考recv(),參數from用來指定欲傳送的網絡地址,結構sockaddr請參考bind()函數,參數fromlen為sockaddr的結構長度。
參數說明:
- 前三個參數sockfd , buff 和 nbytes等同於read 和 write函數的三個參數:描述符、指向寫出緩沖區的指針和寫字節數;
- falgs參數一般置0;
- from參數指向一個含有數據報接收者的協議地址(如IP地址和端口號)的套接字地址結構,其大小由addrlen指定;
同sendto()函數!若無錯誤發生,返回所接收數據的總數(請注意這個數字可能小於len中所規定的大小)。否則的話,返回SOCKET_ERROR錯誤(-1),應用程序可通過WSAGetLastError()獲取相應錯誤代碼。
- WSANOTINITIALISED:在使用此API之前應首先成功地調用WSAStartup();
- WSAENETDOWN:WINDOWS套接口實現檢測到網絡子系統失效;
- WSAEACESS:要求地址為廣播地址,但相關標志未能正確設置;
- WSAEINTR:通過一個WSACancelBlockingCall()來取消一個(阻塞的)調用;
- WSAEINPROGRESS:一個阻塞的WINDOWS套接口調用正在運行中;
- WSAEFAULT:buf或to參數不是用戶地址空間的一部分,或to參數太小(小於sockaddr結構大小);
- WSAENETRESET:由於WINDOWS套接口實現放棄了連接,故該連接必需被復位;
- WSAENOBUFS:WINDOWS套接口實現報告一個緩沖區死鎖;
- WSAENOTCONN:套接口未被連接;
- WSAENOTSOCK:描述字不是一個套接口;
- WSAEOPNOTSUPP:已設置了MSG_OOB,但套接口非SOCK_STREAM類型;
- WSAESHUTDOWN:套接口已被關閉。一個套接口以1或2的how參數調用shutdown()關閉后,無法再用sned()函數;
- WSAEWOULDBLOCK:套接口被標志為非阻塞, 但該調用會產生阻塞;
- WSAEMSGSIZE:套接口為SOCK_DGRAM類型,且數據報大於WINDOWS套接口實現所支持的最大值;
- WSAECONNABORTED:由於超時或其他原因引起虛電路的中斷;
- WSAECONNRESET:虛電路被遠端復位;
- WSAEADDRNOTAVAIL:所指地址無法從本地主機獲得;
- WSAEAFNOSUPPORT:所指定地址族中地址無法與本套接口一切使用;
- WSAEDESADDRREQ:需要目的地址;
- WSAENETUNREACH:當前無法從本主機聯上網絡;
4. UDP回射程序實例
4.1 server.c
<pre name="code" class="cpp">#include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <string.h> #include <sys/types.h> #include <netinet/ip.h> const int SERV_PORT = 6000; const int MAXLINE = 2048; void dg_echo(int sockfd , struct sockaddr *pcliaddr , socklen_t clilen) { int n; socklen_t len; char mesg[MAXLINE]; for( ; ;) { len = clilen; if((n = recvfrom(sockfd , mesg , MAXLINE , 0 , pcliaddr , &len))<0) { perror("recvfrom error"); exit(1); }//if if((n = sendto(sockfd , mesg , n , 0 , pcliaddr , len)) < 0) { perror("sendto error"); exit(1); }//if }//for } int main(int argc , char **argv) { int sockfd; struct sockaddr_in servaddr , cliaddr; bzero(&servaddr , sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); if((sockfd = socket(AF_INET , SOCK_DGRAM , 0)) < 0) { perror("socket error"); exit(1); }//if if(bind(sockfd , (struct sockaddr *)&servaddr , sizeof(servaddr))) { perror("bind error"); exit(1); }//if dg_echo(sockfd , (struct sockaddr *)&cliaddr , sizeof(cliaddr)); }
4.2 client.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> const int SERV_PORT = 6000; const int MAXLINE = 2048; void dg_cli(FILE *fp , int sockfd , const struct sockaddr *pservaddr , socklen_t servlen) { int n; char sendline[MAXLINE] , recvline[MAXLINE+1]; while(fgets(sendline , MAXLINE , fp) != NULL) { if(sendto(sockfd , sendline , strlen(sendline) , 0 , pservaddr , servlen) < 0) { perror("sendto error"); exit(1); }//if if( ( n = recvfrom(sockfd , recvline , MAXLINE , 0 , NULL , NULL)) < 0) { perror("recvfrom error"); exit(1); }//if recvline[n] = '\0'; fputs(recvline , stdout); }//while } int main(int argc , char **argv) { int sockfd , t; struct sockaddr_in servaddr; if(argc != 2) { perror("usage: udpcli <IPaddress>"); exit(1); }//if bzero(&servaddr , sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); if((t = inet_pton(AF_INET , argv[1], &servaddr.sin_addr)) <= 0) { perror("inet_pton error"); <span style="white-space:pre"> </span> exit(1); }//if if((sockfd = socket(AF_INET , SOCK_DGRAM , 0)) < 0) { perror("socket error"); exit(1); }//if dg_cli(stdin , sockfd , (struct sockaddr *)&servaddr , sizeof(servaddr)); exit(0); }
4.3 運行結果
