【1】前言
通过广播我们可以很方便地实现发送数据包给局域网中的所有主机。但广播同样存在很多问题,例如,平凡地发送广播包会造成所有主机数据链路层都来接受并给上层协议处理,容易引起网络风暴。组播(多播)是一种向特定群组发送广播的方式。组播是单播和广播的折中,当发送组播数据包时,只有加入指定多播组的主机数据链路层才会处理,其他主机在数据链路层会直接丢掉收到的数据包。
发送方把数据发向组播地址。接收方要提前加入组播组内, 接收方要绑定组播地址。
【2】IPV4地址分类
IPv4地址的分类:
A类:32bit中以0开头的地址。1字节网络地址 + 3字节主机地址。用来管理超大规模的网络,通常是国家级的通信主干网。
0.0.0.0 ~ 127.255.255.255
10网段是私有IP,用作搭建大型局域网。
127网段是保留地址,用作本地环回。
B类:32bit中以10开头的地址。2字节网络地址 + 2字节主机地址。用来管理大规模的网络。
128.0.0.0 ~ 191.255.255.255
172.16.xx.xx - 172.31.xx.xx私有地址
C类:32bit中以110开头的地址。3字节网络地址 + 1字节主机地址。用来管理小型的网络。
192.0.0.0 ~ 223.255.255.255
192.168.xx.xx是C类的私有地址。
D类:32bit中以1110开头的地址。组播地址,不用来标识主机,也不划分网段。
224.0.0.0 ~ 239.255.255.255
E类:32bit中以11110开头的地址。 用作实验、测试等,不用做标识主机。
240.0.0.0 ~ 247.255.255.255
子网掩码:
用来在ABC类网段基础下划分子网,子网掩码和IP地址“位与”后得到网络段号,即“子网掩码 & IP地址 = 网络地址”。
例如:
11000000 10101000 00000001 00001010 192.168.1.10 IP地址
11111111 11111111 11111111 00000000 255.255.255.0 子网掩码
11000000 10101000 00000001 00000000 192.168.1.0 网络段
D类IP地址是组播地址,组播地址用来描述一个组播组,不用来了标识任何主机。
保留组播地址: 224
公有组播地址: 225 ~ 238
私有组播地址: 239
【3】setsockopt()函数详细介绍
setsockopt()函数用于任意类型、任意状态套接口的设置选项值。尽管在不同协议层上存在选项,但本函数仅定义了最高的“套接口”层次上的选项。选项影响套接口的操作,诸如加急数据是否在普通数据流中接收,广播数据是否可以从套接口发送等等。
#include
int setsockopt(int s,int level,int optname,const char *optval,int optlen);
功能:设置套接字的选项。
参数:
s:标识一个套接字的描述符。
level:选项定义的层次;目前仅支持SOL_SOCKET和IPPROTO_TCP层次。
optname:需设置的选项。
optval:指针,指向存放选项值的缓冲区。
optlen:optval缓冲区长度。
返回值:
若无错误发生,setsockopt()返回0。否则的话,返回-1错误,应用程序可通过WSAGetLastError()获取相应错误代码。
【4】组播发包流程和实例
(1)创建UDP套接字
(2)指定目标地址和端口
(3)发送数据包
#include
#include
#include
#include
#include
#include
#include
#define N 64//定义数据大小
typedef struct sockaddr SA;
int main(int argc, const char *argv[])
{
int sockfd;;//创建套接字
char buf[N]="This is a multicast package\n";
struct sockaddr_in dstaddr;//本地地址
if(argc<3)//检查命令行
{
printf("Usage :<%s>\n",argv[0]);
return -1;
}
if((sockfd=socket(PF_INET,SOCK_DGRAM,0))==-1)//建立数据包套接字
{
perror("fail to socket");
exit(-1);
}
bzero(&dstaddr,sizeof(dstaddr));//数据清零
dstaddr.sin_family=PF_INET;//协议族
dstaddr.sin_port=htons(atoi(argv[2]));//端口
dstaddr.sin_addr.s_addr=inet_addr(argv[1]);//本地地址;
while(1)
{
sendto(sockfd,buf,N,0,(SA *)&dstaddr,sizeof(dstaddr));//发送数据包
sleep(1);
}
return 0;
}
【5】组播接收包流程和实例
(1)创建UDP套接字
(2)加入多播组
(3)绑定地址和端口
(4)接收数据包
#include
#include
#include
#include
#include
#include
#include
#define N 64//缓冲区大小
typedef struct sockaddr SA;
int main(int argc, const char *argv[])
{
int sockfd;
char buf[N];
struct sockaddr_in myaddr,
peeraddr;
socklen_t peerlen=sizeof(peeraddr);
if(argc<3)//检查命令行参数
{
printf("Usage:<%s>\n",argv[0]);
return -1;
}
if((sockfd=socket(PF_INET,SOCK_DGRAM,0))==-1) //创建套接字文件描述
{
perror("fail to socket");
exit(-1);
}
bzero(&mreq,sizeof(mreq));//清零
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");
exit(-1);
}
bzero(&myaddr,sizeof(myaddr));//清零
myaddr.sin_family=PF_INET;//协议族
myaddr.sin_port=htons(atoi(argv[2]));//端口号
myaddr.sin_addr.s_addr=inet_addr(argv[1])//本地地址;
if(bind(sockfd,(SA *)&myaddr,sizeof(myaddr))<0)//绑定
{
perror("fail to bind");
exit(-1);
}
while(1)
{
recvfrom(sockfd,buf,N,0,(SA *)&peeraddr,&peerlen);//接收数据包
printf("[%s:%d]%s\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port),buf);
}
return 0;
}
运行结果:
【6】代码实例
服务器接收到客户端请求,将时间反馈给客户端:
头文件:
#ifndef __HEAD_H__
#define __HEAD_H__
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MCAST_PORT 8332
#define MCAST_ADDR "239.0.0.1"
#define error_exit(_errmsg_) error(EXIT_FAILURE, errno, _errmsg_)
#define BUFF_SIZE 1024
#endif /* __HEAD_H__ */
客户端:
#include "head.h"
int main()
{
int sockfd;
struct sockaddr_in mcastaddr;
char *buff = NULL;
int nbytes;
time_t time_sec;
if (-1 == (sockfd = socket(AF_INET, SOCK_DGRAM, 0)))
error_exit("socket");
mcastaddr.sin_family = AF_INET;
mcastaddr.sin_port = htons(MCAST_PORT);
mcastaddr.sin_addr.s_addr = inet_addr(MCAST_ADDR);
if (-1 == connect(sockfd, (struct sockaddr *)&mcastaddr, sizeof(mcastaddr)))
error_exit("bind");
time(&time_sec);
while (2) {
sleep(1);
time_sec ++;
buff = ctime(&time_sec);
printf("%s", buff);
if (-1 == send(sockfd, buff, strlen(buff), 0))
error_exit("send");
}
close(sockfd);
return 0;
}
服务器端:
#include "head.h"
int main()
{
int sockfd;
struct sockaddr_in peeraddr,
mcast;
char buff[BUFF_SIZE];
int nbytes;
socklen_t addrlen = sizeof(struct sockaddr);
struct ip_mreqn mcaddr;
if (-1 == (sockfd = socket(AF_INET, SOCK_DGRAM, 0)))
error_exit("socket");
mcast.sin_family = AF_INET;
mcast.sin_port = htons(MCAST_PORT);
mcast.sin_addr.s_addr = inet_addr(MCAST_ADDR); // INADDR_ANY;
if (-1 == bind(sockfd, (struct sockaddr *)&mcast, sizeof(mcast)))
error_exit("bind");
mcaddr.imr_multiaddr.s_addr = inet_addr(MCAST_ADDR);
mcaddr.imr_address.s_addr = inet_addr("169.254.8.225"); //INADDR_ANY;
mcaddr.imr_ifindex = 0;
if (-1 == setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mcaddr, sizeof(mcaddr)))
error_exit("setsockopt");
while (1) {
memset(buff, 0, BUFF_SIZE);
if (0 >= (nbytes = recvfrom(sockfd, buff, BUFF_SIZE, 0,
(struct sockaddr *)&peeraddr, &addrlen)))
error_exit("recvfrom");
printf("%s:%d->", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
printf("%s\n", buff);
}
#if 0
strcat(buff, "---echo");
if (-1 == sendto(sockfd, buff, strlen(buff)+1, 0,
(struct sockaddr *)&peeraddr, sizeof(peeraddr)))
error_exit("sendto");
#endif
close(sockfd);
return 0;
}