hello !大家好呀! 欢迎大家来到我的网络编程系列之广播原理剖析,在这篇文章中,
你将会学习到如何在网络编程中利用广播来与局域网内加入某个特定广播组的主机!
希望这篇文章能对你有所帮助,大家要是觉得我写的不错的话,那就点点免费的小爱心吧!
目录
一.什么是广播?
1.1 广播的概念
1.2 广播通信原理
二 .如何实现广播通信
2.1 广播的IP地址
IPv4
IPv6
特殊用途的广播地址
2.2 实现步骤
2.3 网卡接口信息
三.广播代码实例
一.什么是广播?
1.1 广播的概念
广播是一种网络通信技术,允许数据包(如 IP 数据包)被发送到网络上的所有设备。在广播通信中,数据包不是发送给特定的接收者,而是发送给网络中的所有设备。每个设备都会接收到这个数据包,但只有特定地址的设备会响应。广播通常用于局域网(LAN)中,允许设备之间直接通信,而不需要通过路由器。这种通信方式可以提高网络的效率,因为它避免了数据包在网络中的不必要的转发。
1.2 广播通信原理
广播通信的工作原理如下:
-
发送方:发送方将数据包发送到广播地址。在 IP 网络中,广播地址是一个特殊的 IP 地址,通常是 255.255.255.255。
-
网络设备:网络中的每个设备都会接收到这个广播数据包。每个设备都会检查数据包的目的地 IP 地址,以确定是否应该响应。
-
响应:如果数据包的目的地 IP 地址与设备自己的 IP 地址匹配,或者设备被配置为响应广播数据包,那么该设备将发送一个响应数据包。
-
接收方:发送方(或广播请求的发起方)会接收到来自响应设备的响应数据包。
广播通信通常用于以下场景:
- DHCP 服务器:在局域网中,DHCP 服务器使用广播来发现并分配 IP 地址给新加入网络的设备。
- 网络管理:网络管理员可以使用广播来诊断网络问题或发送通知给网络中的所有设备。
- 组播:虽然组播(multicast)与广播类似,但它允许数据包发送给一组特定的接收者,而不是所有设备。组播通常用于多媒体流等应用。
在实际网络环境中,广播通信可能会受到一些限制,例如防火墙规则或网络设备的配置。此外,由于广播通信可能会导致网络拥塞,一些网络设备可能会限制或阻止广播流量。
二 .如何实现广播通信
2.1 广播的IP地址
在 IP 网络中,广播地址用于将数据包发送到网络上的所有设备。广播地址可以是 IP 地址的一部分,具体取决于网络的地址类型和配置。以下是不同 IP 地址类型中的广播地址:
IPv4
在 IPv4 网络中,广播地址通常与子网掩码有关。广播地址可以通过将子网掩码中的所有主机位设置为 1 来计算。例如,如果你有一个子网掩码 255.255.255.0
,那么对于地址 192.168.1.10
,其广播地址将是 192.168.1.255
。
IPv6
在 IPv6 网络中,广播地址的格式是 ff00::/8
。这个地址范围是专门用于广播和多播的。然而,与 IPv4 不同,IPv6 网络中通常不使用广播地址来与网络中的所有设备通信。相反,IPv6 使用多播地址,如 ff02::1
,来代替广播地址。
特殊用途的广播地址
除了上述通用广播地址外,还有一些特殊用途的广播地址:
224.0.0.0/4
:这是一个特殊的多播地址范围,用于多播组播。255.255.255.255
:这是一个用于直接连接的广播地址,通常在同一网络段内使用。
广播地址的目的是将数据包发送到网络中的所有设备。在实际应用中,广播地址的用法和实现可能会因网络配置和协议的不同而有所差异。在 IPv4 网络中,广播地址通常与子网掩码有关;而在 IPv6 网络中,广播地址的用法相对较少,多播地址更为常见。
例如:我在centos系统上查看我的网卡信息:
我的ens33网卡ip地址为192.168.80.132 那么我的该接口的广播地址为192.168.80.255
2.2 实现步骤
当我们了解了广播的ip地址后,我们就可以着手与广播的编程实现了,在编程中实现广播通信通常涉及使用特定的网络编程接口,如 UDP 套接字。以下是一个基本的步骤,用于在 C 语言中实现广播编程:
-
创建套接字: 使用
socket
函数创建一个 UDP 套接字。你需要指定协议族(如 AF_INET)和协议类型(如 SOCK_DGRAM)。 -
设置套接字选项: 你可能需要设置套接字选项,如
SO_BROADCAST
,以允许广播通信。 -
绑定套接字: 使用
bind
函数将套接字绑定到一个地址和端口。这个地址通常是广播地址,端口是你选择的用于广播通信的端口。 -
发送广播消息: 使用
sendto
函数发送广播消息。你需要指定广播地址和端口,以及消息内容。 -
接收广播消息: 如果服务器端需要接收广播消息,可以使用
recvfrom
函数来接收来自广播地址的消息。 -
关闭套接字: 完成通信后,使用
close
函数关闭套接字
这是一般步骤,其中我们还需要更加多的细节和基础知识,大家可以去看我前面博客哦,这里给出相关链接:[C++/Linux] UDP编程-CSDN博客 , [C++/Linux] socket套接字函数-CSDN博客
2.3 网卡接口信息
同时,对于客户端代码来说,我们需要获取某个网卡接口的信息,以便我们得到其多播地址,那就要涉及到struct ifreq
, 这个结构通常用于获取和设置网络接口的参数:
struct ifreq {
char ifr_name[IFNAMSIZ]; /* Interface name */
union {
struct sockaddr ifr_addr;
struct sockaddr ifr_dstaddr;
struct sockaddr ifr_broadaddr;
struct sockaddr ifr_netmask;
struct sockaddr ifr_hwaddr;
};
short ifr_flags; /* Flags */
int ifr_ifindex; /* Interface index */
int ifr_metric; /* Metric */
int ifr_mtu; /* MTU */
// 可能还有其他字段...
};
这个结构中的字段包括:
ifr_name
: 网络接口的名称,例如 “eth0” 或 “wlan0”。ifr_addr
等字段: 网络接口的地址信息,包括 IP 地址、子网掩码、广播地址等。ifr_flags
: 接口的标志,如IFF_UP
(接口已启动)、IFF_RUNNING
(接口正在运行)等。ifr_ifindex
: 网络接口的索引。ifr_metric
: 接口的度量值,用于路由选择。ifr_mtu
: 接口的最大传输单元。
要使用 struct ifreq
,你通常会创建一个实例,设置适当的字段,然后通过 ioctl
调用来获取或设置网络接口的属性。例如,获取指定接口的 IP 地址,你可以这样做:
int sockfd;
struct ifreq ifr;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// 复制接口名称到ifr_name
strncpy(ifr.ifr_name, "eth0", IFNAMSIZ);
// 使用SIOCGIFADDR ioctl命令获取接口地址
if (ioctl(sockfd, SIOCGIFADDR, &ifr) == -1) {
perror("ioctl");
return 1;
}
// 现在ifr.ifr_addr包含IP地址
printf("IP Address: %s\n", inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr));
close(sockfd);
这段代码打开了一个数据报套接字,将接口名称设置为 “eth0”,然后使用 ioctl
获取该接口的 IP 地址,并将其打印出来。
三.广播代码实例
在这个例子中,服务器在局域网上侦听,当有数据到来的时候,判断udp数据报中是否含有关键字 IP_FOUND,如果有,说明此为某客户端的广播通讯消息,服务器会回应含IP_FOUND_ACK关键字的消息给客户端,如果客户端收到这条消息,就会判断该局域网上目前存在服务器,同时可以在消息里说明服务器的ip地址,这样客户端更能了解到当前局域网上的服务器信息,由于是UDP数据报,我们使用sendto发送数据,recvfrom接收消息。
服务器代码:
#include<t_stdio.h>
#include<t_file.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/time.h>
#include <string.h>
#include <sys/select.h>
#include<stdlib.h>
#include <arpa/inet.h>
#include<signal.h>
#define DBGPRINT printf
void sig_process(int signo){//信号处理函数
printf("catch a exit signal..\n");
_exit(0);
}
int main(int argc ,char * argv[]){
int ret = -1;
int sock = -1;
int count;
socklen_t from_len;
struct sockaddr_in local_addr ; //本地地址
struct sockaddr_in client_addr ; //客户端地址
fd_set readfd ; //文件描述符集合,用于接收客户端请求
char buffer[32];//设置数据数组,用于数据接收发送
struct timeval timeout ;//超时设置
timeout.tv_sec = 2;
timeout.tv_usec = 0;
signal(SIGINT,sig_process);//添加sigint信号到信号掩码
//创建数据报套接字
sock = socket(AF_INET , SOCK_DGRAM , 0);
if(sock < 0) E_MSG("socket",-1);
//本地地址数据清零
//memset((void *)local_addr, 0 , sizeof(struct sockaddr_in));
//设置本地地址数据
local_addr.sin_family = AF_INET;
local_addr.sin_addr.s_addr = htonl(INADDR_ANY);//本地地址
local_addr.sin_port = htons(8888); //用8888端口进行监听
//本地绑定
ret = bind(sock , (struct sockaddr *)&local_addr, sizeof(local_addr));
if(ret!=0)E_MSG("bind",-1);
//主处理过程
while(1){
//先将文件描述符清零
FD_ZERO(&readfd);
//将套接字文件描述符加入读集合
FD_SET(sock,&readfd);
//监听是否有数据到来
ret = select(sock+1 , &readfd , NULL,NULL,&timeout);
printf("ret is : %d/n" , ret);
switch (ret)
{
case -1 :
//发生错误,
break;
case 0 :
// 超时
break;
default:
//有数据到来
if(FD_ISSET(sock , &readfd)){
//接收数据
from_len = sizeof(client_addr); // 初始化from_len
count = recvfrom(sock , buffer , 32 , 0 ,(struct sockaddr *) & client_addr, from_len);
DBGPRINT("recv msg is %s\n", buffer);//打印接收的信息
if(strstr(buffer , "IP_FOUND")){//查看是否为ip广播请求数据报
//将应答数据复制进去
memcpy(buffer , "IP_FOUND_ACK" , strlen("IP_FOUND_ACK")+1);
//发送应答数据
count = sendto(sock , buffer , strlen(buffer) , 0 ,(struct sockaddr *)&client_addr,from_len);
}
}
}
}
return 0;
}
客户端代码:
#include<t_stdio.h>
#include<t_file.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/time.h>
#include <string.h>
#include <sys/select.h>
#include<stdlib.h>
#include <arpa/inet.h>
#include<signal.h>
#include<net/if.h>
#include <sys/ioctl.h>
#define DBGPRINT printf
void sig_process(int signo){//信号处理函数
printf("catch a exit signal..\n");
_exit(0);
}
int main(int argc ,char * argv[]){
int ret = -1 , sock = -1 , so_broadcast = 1, from_len = 0, count = 0;
struct ifreq ifr;
struct sockaddr_in broadcast_addr ; //本地广播地址
struct sockaddr_in server_addr ; //服务器地址
fd_set readfd ; //设置接收信息合集,和服务器代码一样
char buffer[32];//设置数据数组,用于数据接收发送
struct timeval timeout ;//超时设置
timeout.tv_sec = 2;
timeout.tv_usec = 0;
signal(SIGINT,sig_process);//添加sigint信号到信号掩码
sock = socket(AF_INET , SOCK_DGRAM , 0); //创建UDP数据报套接字
if(sock < 0 ) E_MSG("socket" , -1);
//将需要使用的网络接口字符串复制到网卡结构中
strcpy(ifr.ifr_name , "ens33");
//获取广播地址
if(ioctl(sock,SIOCGIFBRDADDR, &ifr ) == -1){
E_MSG("ioctl",-1);
}
//将获得的广播地址给本地广播地址结构
memcpy(&broadcast_addr , &ifr.ifr_broadaddr , sizeof(struct sockaddr_in));
//设置广播端口
broadcast_addr.sin_port = htons(8888) ; //设置广播端口
//设置套接字可以进行广播操作
ret = setsockopt(sock , SOL_SOCKET , SO_BROADCAST , &so_broadcast ,sizeof(so_broadcast));
//开始发送广播信息
int times = 10 , i = 0 ;
for(i = 0 ; i < times ; i++){
//广播发送服务器地址请求
ret = sendto(sock , "IP_FOUND" , strlen("IP_FOUND") , 0 ,(struct sockaddr * ) &broadcast_addr , sizeof(broadcast_addr));
if(ret == -1)continue ; //发送失败就继续下一次发送
//先将文件描述符清零
FD_ZERO(&readfd);
//将套接字文件描述符加入读集合
FD_SET(sock,&readfd);
//监听是否有数据到来
ret = select(sock+1 , &readfd , NULL,NULL,&timeout); //这里和服务器代码一样
switch (ret)
{
case -1:
//发生错误
break;
case 0 :
//超时
break;
default://成功监听到服务器回应
if(FD_ISSET(sock , &readfd)){
count = recvfrom(sock , buffer , 32 , 0 ,(struct sockaddr *)&server_addr , &from_len );
DBGPRINT("recv msg is %s\n" , buffer);
//判断是否为广播回应消息
if(strstr(buffer , "IP_FOUND_ACK")){
printf("found server IP is %s\n", inet_ntoa(server_addr.sin_addr));//打印服务器ip地址
}
break;
}
}
}
return 0;
}
好啦!到这里这篇文章就结束啦,关于实例代码中我写了很多注释,如果大家还有不懂得,可以评论区或者私信我都可以哦!! 感谢大家的阅读,我还会持续创造网络编程相关内容的,记得点点小爱心和关注哟!