Linux下的TCP/IP程式設計多播和廣播的實現
【轉載】Linux下的TCP/IP程式設計----多播和廣播的實現
2016年06月05日 13:54:10 兜裡有糖心裡不慌 閱讀數:3795更多<div class="tags-box space"> <span class="label">所屬專欄:</span> <a class="tag-link" href="https://blog.csdn.net/column/details/linux-tcpip.html" target="_blank">Linux下的TCP/IP程式設計</a> </div> </div> <div class="operating"> </div> </div> </div> </div> <article> <div id="article_content" class="article_content clearfix csdn-tracking-statistics" data-pid="blog" data-mod="popu_307" data-dsm="post"> <div class="article-copyright"> 版權宣告:本文為博主原創文章,轉載請註明出處。 https://blog.csdn.net/wqc_CSDN/article/details/51588769 </div> <div class="markdown_views"> <!-- flowchart 箭頭圖示 勿刪 --> <svg xmlns="http://www.w3.org/2000/svg" style="display: none;"><path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path></svg> <p>在前邊我們實現的都是服務端客戶端這樣點對點的通訊,資料只是從一個點到另一個點,但是當我們需要將一份資料同時發給指定的多個人時就遇到了問題,總不能建立多個TCP的長連線或者是多個UDP資料報吧。這時我們就應該考慮使用多播或者時廣播來實現我們的需要。</p>
多播:
IP多播(也稱多址廣播或 組播)技術,是一種允許一臺或多臺主機(多播源)傳送單一 資料包到多臺主機(一次的,同時的)的TCP/ IP網路技術。多播作為一點對多點的通訊,是節省 網路頻寬的有效方法之一。多播組是D類IP地址(224.0.0.0~239.255.255.255)。
多播的特點: 1. 多播服務端針對特定多播地址只發送一次資料 2. 即使只發送一次資料,但是組內的所有客戶端都能收到資料 3. 多播組數可以在IP地址範圍內任意增加 4. 加入特定的多播組即可接收發往該多播組的資料
多播的實現:
多播其實也是依賴於UDP方式,只是由之前的UDP點對點發送變成了一對多的傳送,而這些改變主要依賴於我們對於socket可選項的設定。可以翻看之前對於socket可選項的介紹。
多播Sender的實現:
在實現多播的Sender時我們只需要將UDP傳送資料時的IP地址設定為多播組的IP地址,併為多播資料報設定最大存活時間(TTL)即可。
最大生存時間(TTL):Time To Live的簡稱,是決定資料包傳輸距離的主要因素,其值用正數表示(不能大於十進位制的255),並且每經過一個路由器值便減1,當TTL值為0時路由器便會丟棄這個資料包。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#define TTL 64
#define BUFF_SIZE 30
void error_handling(char *message);
int main(int argc, char *argv[]){
//聲明發送者的socket
int send_socket;
//宣告多波的地址
struct sockaddr_in multicast_addr;
//資料包的最大生存時間
int live_time = TTL;
//檔案指標
FILE *fp;
//字元緩衝
char buff[BUFF_SIZE];
if(argc != 3){
printf("Uasge : %s <GroupIP> <PORT> \n" ,argv[0]);
exit(1);
}
//初始化socket,設定為多播組的IP地址和埠號
send_socket = socket(PF_INET,SOCK_DGRAM,0);
memset(&multicast_addr,0,sizeof(multicast_addr));
multicast_addr.sin_family = AF_INET;
//和之前的UDP設定方式一樣,只是輸入的是多播組的IP地址和埠號
multicast_addr.sin_addr.s_addr = inet_addr(argv[1]);
multicast_addr.sin_port = htons(atoi(argv[2]));
//為多播資料報設定最大生存時間
setsockopt(send_socket,IPPROTO_IP,IP_MULTICAST_TTL,(void *)&live_time,sizeof(live_time));
//開啟檔案
if((fp = fopen("test.txt","r")) == NULL){
error_handling("fopen() error");
}
while(!(feof(fp))){
//從檔案中讀取資料
fgets(buff,BUFF_SIZE,fp);
printf("%s",buff);
//將資料傳送到多播組
sendto(send_socket,buff,BUFF_SIZE,0,(struct sockaddr *) &multicast_addr,sizeof(multicast_addr));
sleep(2);
}
fclose(fp);
close(send_socket);
return 0;
}
void error_handling(char * message){
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
多播Receiver的實現:
多播的Receiver實現比Sender稍微複雜一點,在Receiver中多了一步宣告加入多播組的步驟。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#define TTL 64
#define BUFF_SIZE 30
void error_handling(char *message);
int main(int argc ,char *argv[]){
//宣告接收者的
int receiver_socket;
//每次讀到的字串長度
int str_len;
//用於儲存資料的字元緩衝
char * buff[BUFF_SIZE];
//receiver的地址
struct sockaddr_in receiver_addr;
//宣告一個用於多播的地址結構體
struct ip_mreq join_addr;
if(argc!= 3){
printf("Uasge : %s <GroupIP> <PORT> ",argv[0]);
exit(1);
}
//初始化receiver_socket
receiver_socket = socket(PF_INET,SOCK_DGRAM,0);
memset(&receiver_addr,0,sizeof(receiver_addr));
receiver_addr.sin_family = AF_INET;
receiver_addr.sin_addr.s_addr = htonl(INADDR_ANY);
receiver_addr.sin_port = htons(atoi(argv[2]));
//繫結地址
if(bind(receiver_socket,(struct sockaddr *)&receiver_addr,sizeof(receiver_addr)) == -1){
error_handling("bind() error");
}
//初始化多播地址組
join_addr.imr_multiaddr.s_addr = inet_addr(argv[1]);//要加入的多播組地址
join_addr.imr_interface.s_addr = htonl(INADDR_ANY);//加入該組的套接字所屬的主機IP地址
//設定socket中的IP_ADD_MEMBERSHIP選項加入多播組
setsockopt(receiver_socket,IPPROTO_IP,IP_ADD_MEMBERSHIP,(void *)&join_addr,sizeof(join_addr));
while(1){
//接收發送來的訊息,因為之前已經將socket註冊到組播中
str_len = recvfrom(receiver_socket,buff,BUFF_SIZE-1,0,NULL,0);
if(str_len<0){
break;
}
buff[str_len] = 0;
//使用標準的輸入流輸出接受到的資料
fputs(buff,stdout);
}
close(receiver_socket);
return 0;
}
void error_handling(char * message){
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
這樣一個多播的傳送者和接收者就編寫完成了,其實在實際使用中並不會很明確的區分發送者和接受者。
廣播:
多播可以很便捷的使我們同時向多個地址傳送資料,但前提是要加入對應的多播組,否則就無法接受資料。但是有時我們需要將資料傳送給所有的人(例如發通告這一行為),這時多播也無法便捷的實現這個需求(因為要求所有的人都加入多播組),我們就要考慮使用廣播來實現這個功能。
主機之間一對所有的通訊模式,網路對其中每一臺主機發出的訊號都進行無條件複製並轉發,所有主機都可以接收到所有資訊(不管你是否需要),但是被限制在二層交換機的區域網範圍內,禁止廣播資料穿過路由器,防止廣播資料影響大面積的主機。廣播分為直接廣播和本地廣播。
廣播的特點: 1. 可以向廣播域內的所有主機發送資料 2. 不能夠跨越不同的網路,被路由器所隔離開。
廣播和多播的區別:最主要的區別在於多播可以跨越網路,不受路由器隔離的影響,只要加入多播組就可以接收到資料。而廣播只能是在廣播域內傳輸,被路由器所隔離,防止形成廣播風暴。
廣播的實現: 廣播也是基於UDP實現的,我們同樣需要對socket的可選項進行設定。
廣播Sender的實現:
廣播也是基於UDP的,根據其使用時的IP地址可以分為兩類:
- 直接廣播:目的廣播域之外的主機向廣播域傳送的廣播,IP地址除了網路位之外,主機位都為1。例如向網路地址為192.168.10這個網段傳送廣播,則IP地址為192.168.10.255
- 本地廣播:本地廣播使用的IP地址限制為255.255.255.255,是廣播域內的主機向該廣播域傳送廣播時使用的IP地址。例如192.168.10.1這個主機想向本網段內所有主機發送廣播,則要傳送資料的目的IP地址為192.168.10.255
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#define TTL 64
#define BUFF_SIZE 30
void error_handling(char *message);
int main(int argc,char *argv[]){
//sender的socket
int sender_socket;
//廣播地址
struct sockaddr_in broadcast_addr;
//檔案指標
FILE *fp;
//字元緩衝
char buff[BUFF_SIZE];
//用於配置socket引數
int opt_so_broadcast= 1;
if(argc != 3){
printf("Uasge : %s <GroupIP> <PORT> \n" ,argv[0]);
exit(1);
}
//初始化socket
sender_socket = socket(PF_INET,SOCK_DGRAM,0);
memset(&broadcast_addr,0,sizeof(broadcast_addr));
broadcast_addr.sin_family = AF_INET;
broadcast_addr.sin_addr.s_addr = inet_addr(argv[1]);
broadcast_addr.sin_port = htons(atoi(argv[2]));
//設定socket可選項,因為預設生成的會阻止廣播,所以要將可選項中的SO_BROADCAST標誌置為1
setsockopt(sender_socket,SOL_SOCKET,SO_BROADCAST,(void *)&opt_so_broadcast,sizeof(opt_so_broadcast));
//開啟檔案
if((fp = fopen("word_file.txt","r")) == NULL){
error_handling("fopen() error");
}
while(!(feof(fp))){
//從檔案中讀取資料
fgets(buff,BUFF_SIZE,fp);
printf("%s",buff);
//將資料傳送到多播組
sendto(sender_socket,buff,strlen(buff),0,(struct sockaddr *) &broadcast_addr,sizeof(broadcast_addr));
sleep(2);
}
fclose(fp);
close(sender_socket);
return 0;
}
void error_handling(char * message){
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
廣播Receiver的實現:
廣播的Receiver和UDP的並沒有太大的區別,主要是在接受的時候使用的是recvfrom()函式
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#define TTL 64
#define BUFF_SIZE 30
void error_handling(char * message);
int main(int argc , char *argv[]){
int receiver_socket;
struct sockaddr_in receiver_addr;
int str_len;
char buff[BUFF_SIZE];
if(argc!= 2){
printf("Uasge : %s <GroupIP> <PORT> ",argv[0]);
exit(1);
}
//初始化receiver_socket
receiver_socket = socket(PF_INET,SOCK_DGRAM,0);
memset(&receiver_addr,0,sizeof(receiver_addr));
receiver_addr.sin_family = AF_INET;
receiver_addr.sin_addr.s_addr = htonl(INADDR_ANY);
receiver_addr.sin_port = htons(atoi(argv[1]));
//繫結地址
if(bind(receiver_socket,(struct sockaddr *)&receiver_addr,sizeof(receiver_addr)) == -1){
error_handling("bind() error");
}
while(1){
str_len = recvfrom(receiver_socket,buff,BUFF_SIZE-1,0,NULL,0);
if(str_len < 0){
break;
}
buff[BUFF_SIZE] = 0;
fputs(buff,stdout);
}
close(receiver_socket);
return 0;
}
void error_handling(char * message){
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
至此socket的多播和廣播就完成了,其實都是在UDP的基礎上進行的。主要對建立的預設的socket進行相應的設定,使得在傳送UDP資料包時不再是點對點的,而是點對多的傳送。
<script>
(function(){
function setArticleH(btnReadmore,posi){
var winH = $(window).height();
var articleBox = $("div.article_content");
var artH = articleBox.height();
if(artH > winH*posi){
articleBox.css({
'height':winH*posi+'px',
'overflow':'hidden'
})
btnReadmore.click(function(){
articleBox.removeAttr("style");
$(this).parent().remove();
})
}else{
btnReadmore.parent().remove();
}
}
var btnReadmore = $("#btn-readmore");
if(btnReadmore.length>0){
if(currentUserName){
setArticleH(btnReadmore,3);
}else{
setArticleH(btnReadmore,1.2);
}
}
})()
</script>
</article>