一、广播(broadcast)
1.概念
单播:前几篇博客介绍的数据包发送方式只有一个接受方,称为单播
广播:如果同时发给局域网中的所有主机,称为广播
组播:如果只发给局域网内的部分主机,称为组播
同一个sockfd 只能处理一种接收方式,如果即想发单播又想发广播,还想发组播,需要三个sockfd。只有用户数据报(使用UDP协议)套接字才能广播。
2.广播地址
以192.168.1.0 (255.255.255.0) 网段为例,最大的主机地址192.168.1.255代表该网段的广播地址(具体以ifconfig 命令查看到的 broadcast 后面的为准)发到该地址的数据包被所有的主机接收。
注:255.255.255.255在所有网段中都代表广播地址。
广播能发给局域网所有主机的原理:
因为广播的数据包比较特殊,他的目的mac地址全是f(ff:ff:ff:ff:ff:ff) 这个数据包会发给交换机,交换机是工作在链路层的,交换机看到这样目的mac全是f的数据包,就会将该数据包发给局域网内的所有主机。到达主机后,进行拆包,看到目的mac是广播的mac,则允许通过。到达网络层一看IP地址是广播的IP地址,则可以通过。到达传输层,只要端口号匹配,则数据就能到达应用层。
广播的应用:ARP请求,通过ip地址获取对方的mac地址,使用的就是广播。
3.广播的流程
发送者:
创建套接字 socket( )
设置为允许发送广播权限 setsockopt( )
填充广播信息结构体 sockaddr_in
发送数据 sendto( )
接收者:
创建套接字 socket( )
填充广播信息结构体 sockaddr_in
将套接字与广播信息结构体绑定 bind( )
接收数据 recvfrom( )
getsockopt函数说明:
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
功能:设置允许发送广播
#include <sys/types.h>
#include <sys/socket.h>
参数:
@sockfd: socket函数返回的套接字
@level:SOL_SOCKET 套接字级别
@optname:SO_BROADCAST 允许发送广播
@optval:int on = 1
@optlen:sizeof(on)
返回值:成功返回0,失败返回-1,置位错误码
示例:
int on = 1;
if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on))){
ERRLOG("setsockopt error");
}
4.代码实现
//接收端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.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[]){
if(3 != argc){
printf("Usage : %s <ip> <port>\n", argv[0]);
exit(-1);
}
//1.创建用户数据报式套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == sockfd){
ERRLOG("socket error");
}
//创建服务器广播信息结构体
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
//端口号随便定 ip地址需要是 广播的ip地址
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2]));
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t addrlen = sizeof(server_addr);
//3.将套接字和广播信息结构体进行绑定
if(-1 == bind(sockfd, (struct sockaddr *)&server_addr, addrlen)){
ERRLOG("bind error");
}
//定义一个结构体,保存客户端的信息
struct sockaddr_in client_addr;
memset(&server_addr, 0, sizeof(client_addr));//清空
socklen_t clientaddrlen = sizeof(client_addr);
char buff[N] = {0};
while(1){
//接收数据,如果想要给对方回应,就必须保存对方的网络信息结构体
//如果不回应,后两个参数写 NULL 也行
if(-1 == recvfrom(sockfd, buff, N, 0, (struct sockaddr *)&client_addr, &clientaddrlen)){
ERRLOG("recvfrom error");
}
printf("%s(%d):%s\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buff);
#if 0
//组装应答信息
strcat(buff, "--sever");
if(-1 == sendto(sockfd, buff, N, 0, (struct sockaddr *)&client_addr, clientaddrlen)){
ERRLOG("sendto error");
}
#endif
memset(buff, 0, N);
}
//关闭套接字
close(sockfd);
return 0;
}
//发送端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.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[]){
if(3 != argc){
printf("Usage : %s <ip> <port>\n", argv[0]);
exit(-1);
}
//1.创建用户数据报套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == sockfd){
ERRLOG("socket error");
}
//设置套接字允许发送广播 setsockopt
int on = 1;
if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on))){
ERRLOG("setsockopt error");
}
//填充服务器广播信息结构体
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[2]));
//广播的ip地址
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t addrlen = sizeof(server_addr);
char buff[N] = {0};
while(1){
printf("input your msg:");
fgets(buff, N, stdin);
buff[strlen(buff)-1] = '\0';//清除 \n
if(0 == strcmp(buff, "quit")){
break;
}
if(-1 == sendto(sockfd, buff, N, 0, (struct sockaddr *)&server_addr, addrlen)){
ERRLOG("sendto error");
}
#if 0
//因为客户端已经知道服务器的网络信息了 所以后两个参数可以传NULL
if(-1 == recvfrom(sockfd, buff, N, 0, NULL, NULL)){
ERRLOG("recvfrom error");
}
printf("recv:[%s]\n", buff);
#endif
memset(buff, 0, N);
}
//关闭套接字
close(sockfd);
return 0;