网编(13):UDP的广播和多播

多播

多播( Multicast ) 方式的数据传输是基于UDP完成的。因此,与UDP服务器端/客户端的实现方式非常接近。区别在于, UDP数据传输以单一目标进行,而多播数据同时传递到加入(注册)特定组的大量主机。换言之,采用多播方式时,可以同时向多个主机传递数据。

多播的数据传输方式及流量方面的优点

  • 多播服务器端针对特定多播组,只发送1次数据。
  • 即使只发送1 次数据,但该组内的所有客户端都会接收数据。
  • 多播组数可在IP地址范围内任意增加。
  • 加入特定组即可接收发往该多播组的数据。

多播是基于UDP完成的,也就是说, 多播数据包的格式与UDP数据包相同。只是与一般的UDP数据包不同,向网络传递1 个多播数据包时,路由器将复制该数据包并传递到多个主机。像这样,多播需要借助路由器完成,如图所示。

虽然理论上可以完成多播通信,但不少路由器并不支持多播,或即便支持也因网络拥堵问题故意阻断多播。因此,为了在不支持多播的路由器中完成多播通信,也会使用隧道( Tunneling) 技术(这并非多播程序开发人员需要考虑的问题)。

路由( Routing) 和TTL (Time to Live, 生存时间)

TTL是Time to Live的简写,是决定“数据包传递距离”的主要因素。TTL用整数表示,并且每经过1 个路由器就减1 。TTL变为0时,该数据包无法再被传递,只能销毁。因此, TTL的值设置过大将影响网络流量。当然,设置过小也会无法传递到目标,需要引起注意。

程序中的TTL设置是通过套接字可选项完成的。与设置TTL相关的协议层为IPPROTO_IP , 选项名为IP_M口JTICAST_TTL。

套接字可选项可以参考:网编(8):套接字的多种可选项

int send_sock;
int time_live=64;//设置TTL
send_ sock=socket(PF_INET, SOCK_DGRAM, 0);
setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*) &time_live, sizeof(time_live));

另外,加入多播组也通过设置套接字选项完成。加入多播组相关的协议层为IPPROTO_IP,选项名为IP_ADD_ MEMBERSHIP 。可通过如下代码加入多播组。

int recv_sock;
struct ip_mreq join_adr;

recv_ sock=socket(PF_INET , SOCK_DGRAM, 0);

join_adr.imr_multiaddr.s_addr="多播组地址信息" ;
join_adr.imr_interface. s_addr="加入多播组的主机地址信息";
setsockopt(recv_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*)&join_adr, sizeof(join_adr));

ip_mreq结构体

struct ip_mreq
{
	struct in_addr imr_multiaddr;
	struct in_addr imr_interface;
}

实现多播Sender 和Receiver

  • Sender: 向AAA组广播( Broadcasting ) 文件中保存的新闻信息。
  • Receiver: 接收传递到AAA组的新闻信息。

发送端:

#include <stdio.h >
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define TTL 64
#define BUF_SIZE 30
void error_handling(char *message);
int main(int argc, char *argv[])
{
	int send_sock;
	struct sockaddr_in mul_adr;
	int time_live=TTL;
	FILE *fp;
	char buf[BUF_SIZE];
	if(argc!=3) {
		printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
		exit(1);
	}
	send_sock=socket(PF_INET, SOCK_DGRAM, 0);
	memset(&mul_adr, 0, sizeof(mul_adr));
	mul_adr.sin_family=AF_INET;
	mul_adr.sin_addr.s_addr=inet_addr(argv[1]); // Multicast IP
	mul_adr.sin_port=htons(atoi(argv[2]));//Multicast Port

	//设置TTL
	setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&time_live, sizeof(time_live));
	if((fp=fopen("news.txt", "r"))==NULL)
		error_handling("fopen() error");

	while(!feof(fp))/*Broadcasting*/
	{	
		fgets(buf, BUF_SIZE, fp);
		//发送数据
		sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr*)&mul_adr, sizeof(mul_adr));
		sleep(2);
	}
	fclose(fp);
	close(send_sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

接收端:

多播Receiver与普通UDP套接字有些不同。为了接收传向任意多播地址的数据, 需要经过加入多播组的过程。除此之外, Receiver同样与UDP套接字程序差不多。接下来给出与上述示例结合使用的Receiver程序。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char *message);
int main(int argc, char *argv[])
{
	int recv_sock;
	int str_len;
	char buf[BUF_SIZE];
	struct sockaddr_in adr;
	struct ip_mreq join_adr;
	if(argc!=3) {
		printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
		exit(1);
	}

	recv_sock=socket(PF_INET, SOCK_DGRAM, 0);
	memset(&adr, 0, sizeof(adr));
	adr.sin_family=AF_INET;
	adr.sin_addr.s_addr=htonl(INADDR_ANY) ;
	adr.sin_port=htons(atoi(argv[2]));
	
	//绑定
	if(bind(recv_sock, (struct sockaddr*) &adr, sizeof(adr))==-1)
		error_handling( "bind() error");

	join_adr.imr_multiaddr.s_addr=inet_addr(argv[1]);
	join_adr.imr_interface.s_addr=htonl(INADDR_ANY);

	//加入组播
	setsockopt(recv_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void* )&join_adr, sizeof(join_adr));

	while(1)
	{   //接收数据
		str_len=recvfrom(recv_sock, buf, BUF_SIZE-1, 0, NULL, 0);
		if(str_len<0)
			break;
		buf[str_len]=0;
		fputs(buf, stdout);
	}
	close(recv_sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

运行结果:

#发送端
./send 192.168.43.220 9190

#接收端  //要先运行接收端
$ ./rece  192.168.43.220 9190
aaaaaa
bbbbbb
cccccc
dddddd
eeeeee
eeeeee  //最后一次数据要发送两次

$ cat new.txt
aaaaaa
bbbbbb
cccccc
dddddd
eeeeee

广播

本节介绍的广播( Broadcast ) 在“一次性向多个主机发送数据“这一点上与多播类似,但传输数据的范围有区别。多播即使在跨越不同网络的情况下,只要加入多播组就能接收数据。相反,广播只能向同一网络中的主机传输数据。

广播是向同一网络中的所有主机传输数据的方法。与多播相同,广播也是基于UDP完成的。根据传输数据时使用的IP地址的形式,广播分为如下2种。

  • 直接广播( Directed Broadcast )
  • 本地广播( Local Broadcast )

二者在代码实现上的差别主要在于IP地址。

直接广播的IP地址中除了网络地址外,其余主机地址全部设置为1 。例如,希望向网络地址192.12 .34 中的所有主机传输数据时,可以向
192.12.34.255传输。换言之,可以采用直接广播的方式向特定区域内所有主机传输数据。

本地广播中使用的IP地址限定为255.255.255.255 。例如,192.32.24 网络中的主机向255.255.255.255传输数据时,数据将传递到192.32.24网络中的所有主机。

int send_sock;
int beast= 1;  //对变量进行初始化以将SO_BROADCAST 选项信息改为1。

send_sock = socket(PF_INET, SOCK_ DGRAM, 0);
setsockopt(send_sock, SOL_SOCKET, SO_BROADCAST, (void*) & beast, sizeof(bcast));

调用setsockopt函数,将SO_BROADCAST选项设置为beast变量中的值1 。这意味着可以进行数据广播。当然,上述套接字选项只需在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 BUF_SIZE 30
void error_handling(char *message);
int main(int argc, char *argv[])
{
	int send_sock;
	struct sockaddr_in broad_adr;
	int so_brd=1;
	FILE *fp;
	char buf[BUF_SIZE];
	if(argc!=3) {
		printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
		exit(1);
	}
	send_sock=socket(PF_INET, SOCK_DGRAM, 0);
	memset(&broad_adr, 0, sizeof(broad_adr));
	broad_adr.sin_family=AF_INET;
	broad_adr.sin_addr.s_addr=inet_addr(argv[1]); // Multicast IP
	broad_adr.sin_port=htons(atoi(argv[2]));//Multicast Port

	//设置TTL
	setsockopt(send_sock, SOL_SOCKET, SO_BROADCAST, (void*) &so_brd, sizeof(so_brd));
	if((fp=fopen("news.txt", "r"))==NULL)
		error_handling("fopen() error");

	while(!feof(fp))/*Broadcasting*/
	{	
		fgets(buf, BUF_SIZE, fp);
		//发送数据
		sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr*)&broad_adr, sizeof(broad_adr));
		sleep(2);
	}
	fclose(fp);
	close(send_sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

接收端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
	int recv_sock;
	int str_len;
	char buf[BUF_SIZE];
	struct sockaddr_in adr;
	if(argc!=2) {
		printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
		exit(1);
	}

	recv_sock=socket(PF_INET, SOCK_DGRAM, 0);
	memset(&adr, 0, sizeof(adr));
	adr.sin_family=AF_INET;
	adr.sin_addr.s_addr=htonl(INADDR_ANY) ;
	adr.sin_port=htons(atoi(argv[1]));
	
	//绑定
	if(bind(recv_sock, (struct sockaddr*) &adr, sizeof(adr))==-1)
		error_handling( "bind() error");

	while(1)
	{   //接收数据
		str_len=recvfrom(recv_sock, buf, BUF_SIZE-1, 0, NULL, 0);
		if(str_len<0)
			break;
		buf[str_len]=0;
		fputs(buf, stdout);
	}
	close(recv_sock);
	return 0;
}

void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

运行结果:

#发送端
$ ./send 192.168.43.220 9190  //或者  ./send 255.255.255.255 9190

#接收端  //要先运行接收端
$ ./rece  9190
aaaaaa
bbbbbb
cccccc
dddddd
eeeeee
eeeeee  //最后一次数据要发送两次

$ cat new.txt
aaaaaa
bbbbbb
cccccc
dddddd
eeeeee

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值