14.广播和组播
什么是广播
- 数据包发送方式只有一个接受方,称为单播
- 如果同时发给局域网中的所有主机,称为广播
- 只有用户数据报(使用UDP协议)套接字才能广播
广播地址
- 一个网络内主机号全为1的IP地址为广播地址
- 发到该地址的数据包被所有的主机接收
- 255.255.255.255在所有网段中都代表广播地址
实现广播
ifconfig
命令 查看广播ip
- IP地址(inet addr):192.168.19.139
- 广播地址(Bcast):192.168.19.255
- 子网掩码(Mask):255.255.255.0
设置允许广播
/*允许广播*/
int on = 1;
setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
sender.c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <strings.h>
#include <string.h>
#define ErrExit(msg) do {perror(msg); exit(EXIT_FAILURE);} while(0)
typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
int main(int argc, char *argv[])
{
int fd = -1;
Addr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
char buf[BUFSIZ] = {};
/*参数检查*/
if(argc < 3){
fprintf(stderr, "%s<multiaddr><port>", argv[0]);
exit(EXIT_FAILURE);
}
/*创建套接字*/
if( (fd = socket(AF_INET, SOCK_DGRAM, 0) ) < 0)
ErrExit("socket");
/*允许广播*/
int on = 1;
setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
/*设置通信结构体*/
peeraddr.sin_family = AF_INET;
peeraddr.sin_port = htons(atoi(argv[2]));
if(!inet_aton(argv[1], &peeraddr.sin_addr) ){
fprintf(stderr, "Invalid address\n");
exit(EXIT_FAILURE);
}
while(1){
fgets(buf, BUFSIZ, stdin);
sendto(fd, buf, strlen(buf)+1, 0, (Addr *)&peeraddr, peerlen);
}
return 0;
}
receive.c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <strings.h>
#define ErrExit(msg) do {perror(msg); exit(EXIT_FAILURE);} while(0)
typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
int main(int argc, char *argv[])
{
int fd = -1;
Addr_in myaddr, peeraddr;
socklen_t peerlen = sizeof(peeraddr);
char buf[BUFSIZ] = {};
/*参数检查*/
if(argc < 3){
fprintf(stderr, "%s<addr><port>", argv[0]);
exit(EXIT_FAILURE);
}
/*创建套接字*/
if( (fd = socket(AF_INET, SOCK_DGRAM, 0) ) < 0)
ErrExit("socket");
/*设置通信结构体*/
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(atoi(argv[2]));
if(!inet_aton(argv[1], &myaddr.sin_addr) ){
fprintf(stderr, "Invalid address\n");
exit(EXIT_FAILURE);
}
/*绑定通信结构体*/
if( bind(fd, (Addr *)&myaddr, sizeof(Addr_in)) )
ErrExit("bind");
while(1){
recvfrom(fd, buf, BUFSIZ, 0, (Addr *)&peeraddr, &peerlen);
printf("[%s:%d]%s\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port), buf);
}
return 0;
}
- 启动接收程序:
./receiver 0 8080
- 启动发送程序:
./sender 192.168.19.255 8080
- 开始发送数据
通信成功
什么是组播(多播)
-
在 IP 多播数据报的目的地址需要写入多播组的标识符。
-
多播组的标识符就是 IP 地址中的 D 类地址(多播地址)
- 地址范围:224.0.0.0 ~ 239.255.255.255
-
每一个 D 类地址标志一个多播组。
-
多播地址只能用于目的地址,不能用于源地址。
组播实现步骤
-
创建用户数据报套接字
-
加入多播组
-
绑定组播IP地址和端口
-
等待接收数据
组播设置
方法1
使用结构体struct ip_mreqn
struct ip_mreqn {
struct in_addr imr_multiaddr; /*IP 组播组地址*/
struct in_addr imr_address; /*本地接口的IP地址*/
int imr_ifindex; /*本地网卡的编号*/
}
创建完套接字后,调用设置
if(setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0){
perror("setsockopt");
exit(0);
}
方法2
使用结构体struct ip_mreq
struct ip_mreq {
struct in_addr imr_multiaddr; /*IP 组播组地址*/
struct in_addr imr_interface; /*本地接口的IP地址*/
}
创建完套接字后,调用设置
if(setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0){
perror("setsockopt");
exit(0);
}
组播实现
receive.c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <strings.h>
#define ErrExit(msg) do {perror(msg); exit(EXIT_FAILURE);} while(0)
typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
int main(int argc, char *argv[])
{
int fd = -1;
Addr_in myaddr, peeraddr;
socklen_t peerlen = sizeof(peeraddr);
struct ip_mreqn mreq;
char buf[BUFSIZ] = {};
/*参数检查*/
if(argc < 3){
fprintf(stderr, "%s<addr><port>", argv[0]);
exit(EXIT_FAILURE);
}
/*创建套接字*/
if( (fd = socket(AF_INET, SOCK_DGRAM, 0) ) < 0)
ErrExit("socket");
/*加入多播组*/
bzero(&mreq, sizeof(mreq) );
if(!inet_aton(argv[1], &mreq.imr_multiaddr) ){
fprintf(stderr, "Invalid address\n");
exit(EXIT_FAILURE);
}
if(setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0){
perror("setsockopt");
exit(0);
}
/*设置通信结构体*/
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(atoi(argv[2]));
if(!inet_aton(argv[1], &myaddr.sin_addr) ){
fprintf(stderr, "Invalid address\n");
exit(EXIT_FAILURE);
}
/*绑定通信结构体*/
if( bind(fd, (Addr *)&myaddr, sizeof(Addr_in)) )
ErrExit("bind");
while(1){
recvfrom(fd, buf, BUFSIZ, 0, (Addr *)&peeraddr, &peerlen);
printf("[%s:%d]%s\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port), buf);
}
return 0;
}
sender.c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <strings.h>
#include <string.h>
#define ErrExit(msg) do {perror(msg); exit(EXIT_FAILURE);} while(0)
typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
int main(int argc, char *argv[])
{
int fd = -1;
Addr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
char buf[BUFSIZ] = {};
/*参数检查*/
if(argc < 3){
fprintf(stderr, "%s<multiaddr><port>", argv[0]);
exit(EXIT_FAILURE);
}
/*创建套接字*/
if( (fd = socket(AF_INET, SOCK_DGRAM, 0) ) < 0)
ErrExit("socket");
/*设置通信结构体*/
peeraddr.sin_family = AF_INET;
peeraddr.sin_port = htons(atoi(argv[2]));
if(!inet_aton(argv[1], &peeraddr.sin_addr) ){
fprintf(stderr, "Invalid address\n");
exit(EXIT_FAILURE);
}
while(1){
fgets(buf, BUFSIZ, stdin);
sendto(fd, buf, strlen(buf)+1, 0, (Addr *)&peeraddr, peerlen);
}
return 0;
}
使用在 224.0.0.0 ~ 239.255.255.255 范围内的地址
- 启动接收端
./receiver 239.10.10.2 8080
- 启动发送段
./sender 239.10.10.2 8080
- 发送数据