小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
广播
1 相关概念
前面介绍的数据包发送方式只有一个接受方,称为单播
如果同时发给局域网中的所有主机,称为广播
只有用户数据报(使用UDP协议)套接字才能广播
2 广播地址
以192.168.70.0 (255.255.255.0) 网段为例,
最大的主机地址192.168.70.255代表该网段的广播地址
255.255.255.255在所有网段中都代表广播地址
为什么广播可以发送给局域网所有主机:
因为广播的mac地址和IP地址非常特殊,广播的mac地址全是ff,ff:ff:ff:ff:ff:ff,广播的ip地址是当前网段的最后一个ip地址
当数据发送给局域网所有主机的时候,广播数据包中的目的mac是广播mac地址,目的ip地址是广播的ip地址,数据包会发送给交换机,交换机时工作在链路层的,一看数据包中的目的mac地址是广播的mac地址,就会将这个数据包发送给当前网段所有主机,每一台主机接收到数据之后会进行解包,先对比链路层的目的mac地址,一看是广播的,则可以通过,然后对比网络层的目的IP地址,一看是同网段的广播的ip地址,则可以通过,在到达传输层,只要保证端口号一致,就可以正常接收到应用层的数据
广播的用途:
ARP协议获取对方mac地址的时候,发送的数据包中的目的mac地址要指定为广播地址,也就意味着,==arp协议就是通过广播获取对方mac地址==
3 广播流程
广播是依据UDP实现,所以整个流程类似于UDP网络编程
发送者:
- 创建套接字 socket()
- 设置为允许发送广播 setsockopt()
- 填充广播信息结构体 struct sockaddr_in
- 发送数据 sendto()
接收者:
- 创建套接字 socket()
- 填充广播信息结构体 struct sockaddr_in
- 将套接字与广播信息结构体绑定 bind()
- 接收数据 recvfrom()
4 设置允许发送广播
- 头文件:
#include <sys/types.h>
#include <sys/socket.h>
- 原型:
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
- 功能:设置一个套接字的选项
- 参数:
- sockfd:文件描述符
- level:协议层次
- SOL_SOCKET 套接字层次
- optname:选项的名称
- SO_BROADCAST 设置是否允许发送广播
- optval:要设置的值 1 允许 0 不允许
- optlen:optval的大小
- 返回值:
- 成功:0
- 失败:-1
5 代码
5.1 发送者
//广播之发送者
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#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, char const *argv[])
{
if(argc < 3)
{
fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
exit(1);
}
int sockfd;
struct sockaddr_in broadcastaddr;
socklen_t addrlen = sizeof(broadcastaddr);
//第一步:创建套接字
if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
ERRLOG("socket error");
}
//设置允许发送广播权限
int on = 1;
if(setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) == -1)
{
ERRLOG("setsockopt error");
}
//第二步:填充广播信息结构体
broadcastaddr.sin_family = AF_INET;
broadcastaddr.sin_addr.s_addr = inet_addr(argv[1]); //192.168.70.255 255.255.255.255
broadcastaddr.sin_port = htons(atoi(argv[2]));
//进行通信
char buf[N] = {0};
while(1)
{
fgets(buf, N, stdin);
buf[strlen(buf) - 1] = '\0';
if(sendto(sockfd, buf, N, 0, (struct sockaddr *)&broadcastaddr, addrlen) == -1)
{
ERRLOG("sendto error");
}
}
return 0;
}
5.2 接收者
//广播之接收者
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#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, char const *argv[])
{
if(argc < 3)
{
fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
exit(1);
}
int sockfd;
struct sockaddr_in broadcastaddr, sendaddr;
socklen_t addrlen = sizeof(broadcastaddr);
char buf[N] = {0};
//第一步:创建套接字
if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
ERRLOG("socket error");
}
//第二步:填充广播信息结构体
broadcastaddr.sin_family = AF_INET;
broadcastaddr.sin_addr.s_addr = inet_addr(argv[1]); //192.168.70.255 255.255.255.255
broadcastaddr.sin_port = htons(atoi(argv[2]));
//第三步:将套接字与广播信息结构体绑定
if(bind(sockfd, (struct sockaddr *)&broadcastaddr, addrlen) == -1)
{
ERRLOG("bind error");
}
//进行通信
while(1)
{
if(recvfrom(sockfd, buf, N, 0, (struct sockaddr *)&sendaddr, &addrlen) == -1)
{
ERRLOG("recvfrom error");
}
printf("%s-%d:%s\n", inet_ntoa(sendaddr.sin_addr), ntohs(sendaddr.sin_port), buf);
}
return 0;
}
组播
1 相关概念
单播方式只能发给一个接收方。
广播方式发给所有的主机。
过多的广播会大量占用网络带宽,造成广播风暴,影响正常的通信。
组播(又称为多播)是一种折中的方式。只有加入某个多播组的主机才能收到数据。
多播方式既可以发给多个主机,又能避免象广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)
2 组播地址
D类地址(组播地址)
不分网络地址和主机地址,第1字节的前4位固定为1110
224.0.0.1 – 239.255.255.255
3 组播流程
发送者:
- 创建套接字 socket()
- 填充组播信息结构体 struct sockaddr_in
- 发送数据 sendto()
接收者:
- 创建套接字 socket()
- 填充组播信息结构体 struct sockaddr_in
- 将套接字与组播信息结构体绑定 bind()
- 设置加入多播组 setsockopt()
- 接收数据 recvfrom()
4 设置加入多播组
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
功能:设置一个套接字的选项
参数:
sockfd:文件描述符
level:协议层次
IPPROTO_IP IP层次
optname:选项的名称
IP_ADD_MEMBERSHIP 加入多播组
optval:要设置的值
struct ip_mreqn {
struct in_addr imr_multiaddr; 组播地址
struct in_addr imr_address; 本地IP地址
INADDR_ANY
int imr_ifindex; 接口号
0
};
或者
struct ip_mreq
{
struct in_addr imr_multiaddr; 组播地址
struct in_addr imr_interface; 本地主机地址
INADDR_ANY
};
optlen:optval的大小
返回值:
成功:0
失败:-1
5 代码
5.1 发送者
//组播之发送者
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#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, char const *argv[])
{
if(argc < 3)
{
fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
exit(1);
}
int sockfd;
struct sockaddr_in groupcastaddr;
socklen_t addrlen = sizeof(groupcastaddr);
//第一步:创建套接字
if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
ERRLOG("socket error");
}
//第二步:填充组播信息结构体
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]));
//进行通信
char buf[N] = {0};
while(1)
{
fgets(buf, N, stdin);
buf[strlen(buf) - 1] = '\0';
if(sendto(sockfd, buf, N, 0, (struct sockaddr *)&groupcastaddr, addrlen) == -1)
{
ERRLOG("sendto error");
}
}
return 0;
}
5.2 接收者
//组播之接收者
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#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, char const *argv[])
{
if(argc < 3)
{
fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
exit(1);
}
int sockfd;
struct sockaddr_in groupcastaddr, sendaddr;
socklen_t addrlen = sizeof(groupcastaddr);
char buf[N] = {0};
//第一步:创建套接字
if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
ERRLOG("socket error");
}
//第二步:填充组播信息结构体
groupcastaddr.sin_family = AF_INET;
groupcastaddr.sin_addr.s_addr = inet_addr(argv[1]); //192.168.70.255 255.255.255.255
groupcastaddr.sin_port = htons(atoi(argv[2]));
//第三步:将套接字与组播信息结构体绑定
if(bind(sockfd, (struct sockaddr *)&groupcastaddr, addrlen) == -1)
{
ERRLOG("bind error");
}
//设置加入多播组
struct ip_mreqn mreq;
mreq.imr_multiaddr.s_addr = inet_addr(argv[1]);
mreq.imr_address.s_addr = htonl(INADDR_ANY);
mreq.imr_ifindex = htonl(0);
if(setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)
{
ERRLOG("setsockopt error");
}
//进行通信
while(1)
{
if(recvfrom(sockfd, buf, N, 0, (struct sockaddr *)&sendaddr, &addrlen) == -1)
{
ERRLOG("recvfrom error");
}
printf("%s-%d:%s\n", inet_ntoa(sendaddr.sin_addr), ntohs(sendaddr.sin_port), buf);
}
return 0;
}