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( )
创建套接字 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( )
创建套接字 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;
}