Linux学习(二十六):网络广播组播

1、广播组播定义

         前面介绍的数据包发送方式只有一个接受方,称为单播。如果同时发给局域网中的所有主机,称为广播。只有用户数据报(使用UDP协议)套接字才能广播。
       单播方式只能发给一个接收方。
       广播方式发给所有的主机。过多的广播会大量占用网络带宽,造成广播风暴,影响正常的通信。
组播(又称为多播)是一种折中的方式。只有加入某个多播组的主机才能收到数据。
多播方式既可以发给多个主机,又能避免象广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)

2、广播组播地址信息

广播地址:以192.168.1.0 (255.255.255.0) 网段为例,最大的主机地址192.168.1.255代表该网段的广播地址。发到该地                   址的数据包被所有的主机接收。255.255.255.255在所有网段中都代表广播地址。
组播地址:D类地址(组播地址)不分网络地址和主机地址,第1字节的前4位固定为1110。
                  224.0.0.1 – 239.255.255.255

3、广播通信流程

发送者:
创建套接字 socket( )
填充广播信息结构体 sockaddr_in
设置为允许发送广播信息 setsockopt( )(默认不允许广播)
发送数据 sendto( )

接收者:
创建套接字 socket( )
填充广播信息结构体 sockaddr_in
将套接字与广播信息接结构体绑定 bind( )
绑定广播IP地址或INADDR_ANY和端口绑定的端口必须和发送方指定的                                                                   端口相同
接收数据 recvfrom( )

例程:
发送端:
sersockopt在Linux学习(二十五):网络超时检测中已经学过,这里把第三个参数设置为SO_BROADCAST
#include <stdio.h>  //printf
#include <arpa/inet.h>  //inet_addr htons
#include <sys/types.h>
#include <sys/socket.h>  //socket bind listen accept connect
#include <netinet/in.h>  //sockaddr_in
#include <stdlib.h>  //exit
#include <unistd.h>  //close
#include <string.h>

#define N 128
#define errlog(errmsg) do{\
							perror(errmsg);\
							printf("%s --> %s --> %d\n", __FILE__, __func__, __LINE__);\
							exit(1);\
						 }while(0)

int main(int argc, const char *argv[])
{
	int sockfd;
	struct sockaddr_in broadcastaddr;
	socklen_t addrlen = sizeof(broadcastaddr);
	char buf[N] = {};

	if(argc < 3)
	{
		printf("您输入的参数太少了: %s <ip> <port>\n", argv[0]);
		exit(1);
	}

	//第一步:创建套接字
	if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
	{
		errlog("fail to socket");
	}

	//第二步:填充服务器广播信息结构体
	//inet_addr:将点分十进制ip地址转化为网络字节序的整型数据
	//htons:将主机字节序转化为网络字节序
	//atoi:将数字型字符串转化为整型数据
	broadcastaddr.sin_family = AF_INET;
	broadcastaddr.sin_addr.s_addr = inet_addr(argv[1]); //192.168.3.255 255.255.255.255
	broadcastaddr.sin_port = htons(atoi(argv[2]));

	//设置为发送广播权限
	
	int on = 1;

	if(setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0)
	{
		errlog("fail to setsockopt");
	}

	while(1)
	{
		fgets(buf, N, stdin);
		buf[strlen(buf) - 1] = '\0';

		if(sendto(sockfd, buf, N, 0, (struct sockaddr *)&broadcastaddr, addrlen) < 0)
		{
			errlog("fail to sendto");
		}

	}

	close(sockfd);

	return 0;
}
客户端:
#include <stdio.h>  //printf
#include <arpa/inet.h>  //inet_addr htons
#include <sys/types.h>
#include <sys/socket.h>  //socket bind listen accept connect
#include <netinet/in.h>  //sockaddr_in
#include <stdlib.h>  //exit
#include <unistd.h>  //close
#include <string.h>

#define N 128
#define errlog(errmsg) do{\
							perror(errmsg);\
							printf("%s --> %s --> %d\n", __FILE__, __func__, __LINE__);\
							exit(1);\
						 }while(0)

int main(int argc, const char *argv[])
{
	int sockfd;
	struct sockaddr_in broadcastaddr, sendaddr;
	socklen_t addrlen = sizeof(broadcastaddr);
	char buf[N] = {};
	ssize_t bytes;

	if(argc < 3)
	{
		printf("您输入的参数太少了: %s <ip> <port>\n", argv[0]);
		exit(1);
	}

	//第一步:创建套接字
	if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
	{
		errlog("fail to socket");
	}

	//第二步:填充服务器广播信息结构体
	//inet_addr:将点分十进制ip地址转化为网络字节序的整型数据
	//htons:将主机字节序转化为网络字节序
	//atoi:将数字型字符串转化为整型数据
	broadcastaddr.sin_family = AF_INET;
	//broadcastaddr.sin_addr.s_addr = inet_addr(argv[1]);
	broadcastaddr.sin_addr.s_addr = inet_addr(INADDR_ANY);
	broadcastaddr.sin_port = htons(atoi(argv[2]));

	//第三步:将套接字域网络信息结构体绑定
	if(bind(sockfd, (struct sockaddr *)&broadcastaddr, sizeof(broadcastaddr)) < 0)
	{
		errlog("fail to bind");
	}

	while(1)
	{
		if((bytes = recvfrom(sockfd, buf, N, 0, (struct sockaddr *)&sendaddr, &addrlen)) < 0)
		{
			errlog("fail to recvfrom");
		}
		else 
		{
			//打印客户端的ip地址、端口号
			printf("%s --- %d\n", inet_ntoa(sendaddr.sin_addr), ntohs(sendaddr.sin_port));
			printf("broadcast : %s\n", buf);
		}
	}

	close(sockfd);

	return 0;
}



4、组播通信流程

发送者:
创建套接字 socket( )
填充组播信息结构体 sockaddr_in( )
发送数据 sendto( )

接收者:
创建套接字 socket( )
填充组播信息结构体 sockaddr_in( )
将套接字与组播信息结构体绑定 bind( )
设置为加入多播组权限 setsockopt( )
接收数据 recvfrom( )

例程:
发送端:这里没什么特别的,发送地址设置为组播地址即可
#include <stdio.h>  //printf
#include <arpa/inet.h>  //inet_addr htons
#include <sys/types.h>
#include <sys/socket.h>  //socket bind listen accept connect
#include <netinet/in.h>  //sockaddr_in
#include <stdlib.h>  //exit
#include <unistd.h>  //close
#include <string.h>

#define N 128
#define errlog(errmsg) do{\
							perror(errmsg);\
							printf("%s --> %s --> %d\n", __FILE__, __func__, __LINE__);\
							exit(1);\
						 }while(0)

int main(int argc, const char *argv[])
{
	int sockfd;
	struct sockaddr_in groupcastaddr;
	socklen_t addrlen = sizeof(groupcastaddr);
	char buf[N] = {};

	if(argc < 3)
	{
		printf("您输入的参数太少了: %s <ip> <port>\n", argv[0]);
		exit(1);
	}

	//第一步:创建套接字
	if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
	{
		errlog("fail to socket");
	}

	//第二步:填充服务器组播信息结构体
	//inet_addr:将点分十进制ip地址转化为网络字节序的整型数据
	//htons:将主机字节序转化为网络字节序
	//atoi:将数字型字符串转化为整型数据
	groupcastaddr.sin_family = AF_INET;
	groupcastaddr.sin_addr.s_addr = inet_addr(argv[1]); //224.x.x.x - 239.x.x.x
	groupcastaddr.sin_port = htons(atoi(argv[2]));

	while(1)
	{
		fgets(buf, N, stdin);
		buf[strlen(buf) - 1] = '\0';

		if(sendto(sockfd, buf, N, 0, (struct sockaddr *)&groupcastaddr, addrlen) < 0)
		{
			errlog("fail to sendto");
		}
	}

	close(sockfd);

	return 0;
}
接收端:接收端需要将当前IP地址加入到组播当中,又要用到setsockopt函数,组播属于IP层次IPPROTO_IP,在IP层次有如下选项

我们的选项名称使用IP_ADD_MEMBERSHIP来加入组播,加入组播时还需指定组播地址和当前IP地址,第四个参数的形式如下


#include <stdio.h>  //printf
#include <arpa/inet.h>  //inet_addr htons
#include <sys/types.h>
#include <sys/socket.h>  //socket bind listen accept connect
#include <netinet/in.h>  //sockaddr_in
#include <stdlib.h>  //exit
#include <unistd.h>  //close
#include <string.h>

#define N 128
#define errlog(errmsg) do{\
							perror(errmsg);\
							printf("%s --> %s --> %d\n", __FILE__, __func__, __LINE__);\
							exit(1);\
						 }while(0)

int main(int argc, const char *argv[])
{
	int sockfd;
	struct sockaddr_in groupcastaddr, sendaddr;
	socklen_t addrlen = sizeof(groupcastaddr);
	char buf[N] = {};
	ssize_t bytes;

	if(argc < 3)
	{
		printf("您输入的参数太少了: %s <ip> <port>\n", argv[0]);
		exit(1);
	}

	//第一步:创建套接字
	if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
	{
		errlog("fail to socket");
	}

	//第二步:填充服务器组播信息结构体
	//inet_addr:将点分十进制ip地址转化为网络字节序的整型数据
	//htons:将主机字节序转化为网络字节序
	//atoi:将数字型字符串转化为整型数据
	groupcastaddr.sin_family = AF_INET;
	groupcastaddr.sin_addr.s_addr = inet_addr(argv[1]);
	groupcastaddr.sin_port = htons(atoi(argv[2]));

	//第三步:将套接字域网络信息结构体绑定
	if(bind(sockfd, (struct sockaddr *)&groupcastaddr, sizeof(groupcastaddr)) < 0)
	{
		errlog("fail to bind");
	}

	//加入多播组
	struct ip_mreq mreq;
	mreq.imr_multiaddr.s_addr = inet_addr(argv[1]);
	mreq.imr_interface.s_addr = htonl(INADDR_ANY);

	if(setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
	{
		perror("fail to setsockopt");
	}

	while(1)
	{
		if((bytes = recvfrom(sockfd, buf, N, 0, (struct sockaddr *)&sendaddr, &addrlen)) < 0)
		{
			errlog("fail to recvfrom");
		}
		else 
		{
			//打印客户端的ip地址、端口号
			printf("%s --- %d\n", inet_ntoa(sendaddr.sin_addr), ntohs(sendaddr.sin_port));
			printf("groupcast : %s\n", buf);
		}
	}

	close(sockfd);

	return 0;
}








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值