UDP通訊知識介紹及簡易教程
使用UDP協議進行資訊的傳輸之前不需要建議連線。換句話說就是客戶端向伺服器傳送資訊,客戶端只需要給出伺服器的ip地址和埠號,然後將資訊封裝到一個待發送的報文中並且傳送出去。至於伺服器端是否存在,或者能否收到該報文,客戶端根本不用管。
單播用於兩個主機之間的端對端通訊,廣播用於一個主機對整個區域網上所有主機上的資料通訊。單播和廣播是兩個極端,要麼對一個主機進行通訊,要麼對整個區域網上的主機進行通訊。實際情況下,經常需要對一組特定的主機進行通訊,而不是整個區域網上的所有主機,這就是多播的用途。
通常我們討論的udp的程式都是一對一的單播程式。本章將討論一對多的服務:廣播(broadcast)、多播(multicast)。對於廣播,網路中的所有主機都會接收一份資料副本。對於多播,訊息只是傳送到一個多播地址,網路知識將資料分發給哪些表示想要接收發送到該多播地址的資料的主機。總得來說,只有UDP套接字允許廣播或多播。
一、UDP廣播
廣播UDP與單播UDP的區別就是IP地址不同,廣播使用廣播地址255.255.255.255,將訊息傳送到在同一廣播網路上的每個主機。值得強調的是:本地廣播資訊是不會被路由器轉發。當然這是十分容易理解的,因為如果路由器轉發了廣播資訊,那麼勢必會引起網路癱瘓。這也是為什麼IP協議的設計者故意沒有定義網際網路範圍的廣播機制。
廣播地址通常用於在網路遊戲中處於同一本地網路的玩家之間交流狀態資訊等。
其實廣播顧名思義,就是想區域網內所有的人說話,但是廣播還是要指明接收者的埠號的,因為不可能接受者的所有埠都來收聽廣播。
UDP服務端程式碼:
1 #include<iostream> 2 #include<stdio.h> 3 #include<sys/socket.h> 4 #include<unistd.h> 5 #include<sys/types.h> 6 #include<netdb.h> 7 #include<netinet/in.h> 8 #include<arpa/inet.h> 9 #include<string.h> 10 using namespace std; 11 int main() 12 { 13 setvbuf(stdout,NULL,_IONBF,0); 14 fflush(stdout); 15 int sock=-1; 16 if((sock=socket(AF_INET,SOCK_DGRAM,0))==-1) 17 { 18 cout<<"sock error"<<endl; 19 return -1; 20 } 21 const int opt=-1; 22 int nb=0; 23 nb=setsockopt(sock,SOL_SOCKET,SO_BROADCAST,(char*)&opt,sizeof(opt));//設定套接字型別 24 if(nb==-1) 25 { 26 cout<<"set socket error...\n"<<endl; 27 return -1; 28 } 29 struct sockaddr_in addrto; 30 bzero(&addrto,sizeof(struct sockaddr_in)); 31 addrto.sin_family=AF_INET; 32 addrto.sin_addr.s_addr=htonl(INADDR_BROADCAST);//套接字地址為廣播地址 33 addrto.sin_port=htons(6000);//套接字廣播埠號為6000 34 int nlen=sizeof(addrto); 35 while(1) 36 { 37 sleep(1); 38 char msg[]={"the message broadcast"}; 39 int ret=sendto(sock,msg,strlen(msg),0,(sockaddr*)&addrto,nlen);//向廣播地址釋出訊息 40 if(ret<0) 41 { 42 cout<<"send error...\n"<<endl; 43 return -1; 44 } 45 else 46 { 47 printf("ok\n"); 48 } 49 } 50 return 0; 51 }
UDP廣播客戶端程式碼:
1 #include<iostream> 2 #include<stdio.h> 3 #include<sys/socket.h> 4 #include<unistd.h> 5 #include<sys/types.h> 6 #include<netdb.h> 7 #include<netinet/in.h> 8 #include<arpa/inet.h> 9 #include<string.h> 10 11 12 using namespace std; 13 int main() 14 { 15 setvbuf(stdout,NULL,_IONBF,0); 16 fflush(stdout); 17 struct sockaddr_in addrto; 18 bzero(&addrto,sizeof(struct sockaddr_in)); 19 addrto.sin_family=AF_INET; 20 addrto.sin_addr.s_addr=htonl(INADDR_ANY); 21 addrto.sin_port=htons(6000); 22 socklen_t len=sizeof(addrto); 23 int sock=-1; 24 if((sock=socket(AF_INET,SOCK_DGRAM,0))==-1) 25 { 26 cout<<"socket error..."<<endl; 27 return -1; 28 } 29 const int opt=-1; 30 int nb=0; 31 nb=setsockopt(sock,SOL_SOCKET,SO_BROADCAST,(char*)&opt,sizeof(opt)); 32 if(nb==-1) 33 { 34 cout<<"set socket errror..."<<endl; 35 return -1; 36 } 37 if(bind(sock,(struct sockaddr*)&(addrto),len)==-1) 38 { 39 cout<<"bind error..."<<endl; 40 return -1; 41 } 42 char msg[100]={0}; 43 while(1) 44 { 45 int ret=recvfrom(sock,msg,100,0,(struct sockaddr*)&addrto,&len); 46 if(ret<=0) 47 { 48 cout<<"read error..."<<endl; 49 } 50 else 51 { 52 printf("%s\t",msg); 53 } 54 sleep(1); 55 } 56 return 0; 57 }
二、UDP多播
1、多播(組播)的概念
多播,也稱為“組播”,將網路中同一業務型別主機進行了邏輯上的分組,進行資料收發的時候其資料僅僅在同一分組中進行,其他的主機沒有加入此分組不能收發對應的資料。
在廣域網上廣播的時候,其中的交換機和路由器只向需要獲取資料的主機複製並轉發資料。主機可以向路由器請求加入或退出某個組,網路中的路由器和交換機有選擇地複製並傳輸資料,將資料僅僅傳輸給組內的主機。多播的這種功能,可以一次將資料傳送到多個主機,又能保證不影響其他不需要(未加入組)的主機的其他通 信。
相對於傳統的一對一的單播,多播具有如下的優點:
1、具有同種業務的主機加入同一資料流,共享同一通道,節省了頻寬和伺服器的優點,具有廣播的優點而又沒有廣播所需要的頻寬。
2、伺服器的總頻寬不受客戶端頻寬的限制。由於組播協議由接收者的需求來確定是否進行資料流的轉發,所以伺服器端的頻寬是常量,與客戶端的數量無關。
3、與單播一樣,多播是允許在廣域網即Internet上進行傳輸的,而廣播僅僅在同一區域網上才能進行。
組播的缺點:
1、多播與單播相比沒有糾錯機制,當發生錯誤的時候難以彌補,但是可以在應用層來實現此種功能。
2、多播的網路支援存在缺陷,需要路由器及網路協議棧的支援。
3、多播的應用主要有網上視訊、網上會議等。
2、廣域網的多播
多播的地址是特定的,D類地址用於多播。D類IP地址就是多播IP地址,即224.0.0.0至239.255.255.255之間的IP地址,並被劃分為區域性連線多播地址、預留多播地址和管理許可權多播地址3類:
1、區域性多播地址:在224.0.0.0~224.0.0.255之間,這是為路由協議和其他用途保留的地址,路由器並不轉發屬於此範圍的IP包。
2、預留多播地址:在224.0.1.0~238.255.255.255之間,可用於全球範圍(如Internet)或網路協議。
3、管理許可權多播地址:在239.0.0.0~239.255.255.255之間,可供組織內部使用,類似於私有IP地址,不能用於Internet,可限制多播範圍。
多播的程式設計使用setsockopt()函式和getsockopt()函式來實現,組播的選項是IP層的,其選項值和含義參見11.5所示。
表11.5 多播相關的選項
getsockopt()/setsockopt()的選項 |
含 義 |
IP_MULTICAST_TTL |
設定多播組資料的TTL值 |
IP_ADD_MEMBERSHIP |
在指定介面上加入組播組 |
IP_DROP_MEMBERSHIP |
退出組播組 |
IP_MULTICAST_IF |
獲取預設介面或設定介面 |
IP_MULTICAST_LOOP |
禁止組播資料回送 |
3、多播程式設計的框架
要進行多播的程式設計,需要遵從一定的程式設計框架。多播程式框架主要包含套接字初始化、設定多播超時時間、加入多播組、傳送資料、接收資料以及從多播組中離開幾個方面。其步驟如下:
(1)建立一個socket。
(2)然後設定多播的引數,例如超時時間TTL、本地迴環許可LOOP等。
(3)加入多播組。
(4)傳送和接收資料。
(5)從多播組離開。
4、多播實現程式碼
服務端程式碼:
1 #include<iostream> 2 #include<stdio.h> 3 #include<sys/socket.h> 4 #include<netdb.h> 5 #include<sys/types.h> 6 #include<arpa/inet.h> 7 #include<netinet/in.h> 8 #include<unistd.h> 9 #include<stdlib.h> 10 #include<string.h> 11 #define MCAST_PORT 8888 12 #define MCAST_ADDR "224.0.0.88" // 多播地址 13 #define MCAST_DATA "BROADCAST TEST DATA" // 多播內容 14 #define MCAST_INTERVAL 5 //多播時間間隔 15 using namespace std; 16 17 int main() 18 { 19 int sock; 20 struct sockaddr_in mcast_addr; 21 sock=socket(AF_INET,SOCK_DGRAM,0); 22 if(sock==-1) 23 { 24 cout<<"socket error"<<endl; 25 return -1; 26 } 27 memset(&mcast_addr,0,sizeof(mcast_addr)); 28 mcast_addr.sin_family=AF_INET; 29 mcast_addr.sin_addr.s_addr=inet_addr(MCAST_ADDR); 30 mcast_addr.sin_port=htons(MCAST_PORT); 31 while(1) 32 { //向區域性多播地址傳送多播內容 33 int n=sendto(sock,MCAST_DATA,sizeof(MCAST_DATA),0,(struct sockaddr*)&mcast_addr,sizeof(mcast_addr)); 34 if(n<0) 35 { 36 cout<<"send error"<<endl; 37 return -2; 38 } 39 else 40 { 41 cout<<"send message is going ...."<<endl; 42 } 43 sleep(MCAST_INTERVAL); 44 45 } 46 return 0; 47 }
客戶端程式碼:
1 #include<iostream> 2 #include<stdio.h> 3 #include<stdlib.h> 4 #include<string.h> 5 #include<sys/types.h> 6 #include<unistd.h> 7 #include<sys/socket.h> 8 #include<netdb.h> 9 #include<arpa/inet.h> 10 #include<netinet/in.h> 11 #define MCAST_PORT 8888 12 #define MCAST_ADDR "224.0.0.88" /*一個區域性連線多播地址,路由器不進行轉發*/ 13 #define MCAST_INTERVAL 5 //傳送時間間隔 14 #define BUFF_SIZE 256 //接收緩衝區大小 15 using namespace std; 16 int main() 17 { 18 int sock; 19 struct sockaddr_in local_addr; 20 int err=-1; 21 sock=socket(AF_INET,SOCK_DGRAM,0); 22 if(sock==-1) 23 { 24 cout<<"sock error"<<endl; 25 return -1; 26 } 27 /*初始化地址*/ 28 local_addr.sin_family=AF_INET; 29 local_addr.sin_addr.s_addr=htonl(INADDR_ANY); 30 local_addr.sin_port=htons(MCAST_PORT); 31 /*繫結socket*/ 32 err=bind(sock,(struct sockaddr*)&local_addr,sizeof(local_addr)); 33 if(err<0) 34 { 35 cout<<"bind error"<<endl; 36 return -2; 37 } 38 /*設定迴環許可*/ 39 int loop=1; 40 err=setsockopt(sock,IPPROTO_IP,IP_MULTICAST_LOOP,&loop,sizeof(loop)); 41 if(err<0) 42 { 43 cout<<"set sock error"<<endl; 44 return -3; 45 } 46 struct ip_mreq mreq;/*加入廣播組*/ 47 mreq.imr_multiaddr.s_addr=inet_addr(MCAST_ADDR);//廣播地址 48 mreq.imr_interface.s_addr=htonl(INADDR_ANY); //網路介面為預設 49 /*將本機加入廣播組*/ 50 err=setsockopt(sock,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq)); 51 if(err<0) 52 { 53 cout<<"set sock error"<<endl; 54 return -4; 55 } 56 int times=0; 57 socklen_t addr_len=0; 58 char buff[BUFF_SIZE]; 59 int n=0; 60 /*迴圈接受廣播組的訊息,5次後退出*/ 61 for(times=0;;times++) 62 { 63 addr_len=sizeof(local_addr); 64 memset(buff,0,BUFF_SIZE); 65 n=recvfrom(sock,buff,BUFF_SIZE,0,(struct sockaddr*)&local_addr,&addr_len); 66 if(n==-1) 67 { 68 cout<<"recv error"<<endl; 69 return -5; 70 } 71 /*列印資訊*/ 72 printf("RECV %dst message from server : %s\n",times,buff); 73 sleep(MCAST_INTERVAL); 74 } 75 /*退出廣播組*/ 76 err=setsockopt(sock,IPPROTO_IP,IP_DROP_MEMBERSHIP,&mreq,sizeof(mreq)); 77 close(sock); 78 return 0; 79 }
關於此處bind函式的解析
bind操作首先檢查使用者指定的埠是否可用,然後為socket的一些成員設定正確的值,並新增到雜湊表myudp_hash中。然後,協議棧每次收到UDP資料,就會檢查該資料報的源和目的地址,還有源和目的埠,在myudp_hash中找到匹配的socket,把該資料報放入該 socket的接收佇列,以備使用者讀取。在這個程式中,bind操作把socket繫結到地址224.0.0.88:8888上, 該操作產生的直接結果就是,對於socket本身,下列值受影響:
struct inet_sock{
.rcv_saddr = 224.0.0.88;
.saddr = 0.0.0.0;
.sport = 8888;
.daddr = 0.0.0.0;
.dport = 0;
}
這五個資料表示,該套接字在傳送資料包時,本地使用埠8888,本地可以使用任意一個網路裝置介面,發往的目的地址不指定。在接收資料時,只接收發往IP地址224.0.0.88的埠為8888的資料。
我的疑問???
為什麼要廣播方和接受方的埠號相同才能收到廣播?我試了在一臺linux機子上開兩個客戶端其中一個和廣播方的埠號不同,這個客戶端結果收不到廣播,哪位網友知道懇請告之。
程式中,緊接著bind有一個setsockopt操作,它的作用是將socket加入一個組播組,因為socket要接收組播地址224.0.0.1的資料,它就必須加入該組播組。
三、UDP廣播與單播
廣播與單播的比較
廣播和單播的處理過程是不同的,單播的資料只是收發資料的特定主機進行處理,而廣播的資料整個區域網都進行處理。
例如在一個乙太網上有3個主機,主機的配置如表11.4所示。
表11.4 某區域網中主機的配置情況
主 機 |
A |
B |
C |
IP地址 |
192.168.1.150 |
192.168.1.151 |
192.168.1.158 |
MAC地址 |
00:00:00:00:00:01 |
00:00:00:00:00:02 |
00:00:00:00:00:03 |
單播流程:主機A向主機B傳送UDP資料報,傳送的目的IP為192.168.1.151,埠為 80,目的MAC地址為00:00:00:00:00:02。此資料經過UDP層、IP層,到達資料鏈路層,資料在整個乙太網上傳播,在此層中其他主機會 判斷目的MAC地址。主機C的MAC地址為00:00:00:00:00:03,與目的MAC地址00:00:00:00:00:02不匹配,資料鏈路層 不會進行處理,直接丟棄此資料。
主機B的MAC地址為00:00:00:00:00:02,與目的MAC地址00:00:00:00:00:02一致,此資料會經過IP層、UDP層,到達接收資料的應用程式。
廣播的流程:主機A向整個網路傳送廣播資料,傳送的目的IP為192.168.1.255,埠為 80,目的MAC地址為FF:FF:FF:FF:FF:FF。此資料經過UDP層、IP層,到達資料鏈路層,資料在整個乙太網上傳播,在此層中其他主機會 判斷目的MAC地址。由於目的MAC地址為FF:FF:FF:FF:FF:FF,主機C和主機B會忽略MAC地址的比較(當然,如果協議棧不支援廣播,則 仍然比較MAC地址),處理接收到的資料。
主機B和主機C的處理過程一致,此資料會經過IP層、UDP層,到達接收資料的應用程式。