广播地址: 主机号最大的地址;
以192.168.1.0 (255.255.255.0) 网段为例,最大的主机地址192.168.1.255代表该网段的广播地址
● 前面介绍的数据包发送方式只有一个接受方,称为单播
● 如果同时发给局域网中的所有主机,称为广播
(同一局域网内的主机都会接收到,如果其他主机没有加入广播站,就会将消息丢弃)
一.广播 (UDP协议)
特点:
● 只有用户数据报(使用UDP协议)套接字才能广播
● 一般被设计为局域网搜索协议
函数接口
setsockopt 设置套接字的属性
头文件:
#include<sys.socket.h>
#include<sys/types.h>
#include<sys/time.h>
原型: int setsockopt(int sockfd,int level,int optname,\
void *optval,socklen_t optlen)
功能: 获得/设置套接字属性
参数:
sockfd:套接字描述符
level:协议层
optname:选项名
optval:选项值
optlen:选项值大小
返回值: 成功 0 失败-1
socket属性
选项名称 | 说明 | 数据类型 |
==== SOL_SOCKET 应用层 ==== | ||
SO_BROADCAST | 允许发送广播数据 | int |
SO_DEBUG | 允许调试 | int |
SO_DONTROUTE | 不查找路由 | int |
SO_ERROR | 获得套接字错误 | int |
SO_KEEPALIVE | 保持连接 | int |
SO_LINGER | 延迟关闭连接 | struct linger |
SO_OOBINLINE | 带外数据放入正常数据流 | int |
SO_RCVBUF | 接收缓冲区大小 | int |
SO_SNDBUF | 发送缓冲区大小 | int |
SO_RCVLOWAT | 接收缓冲区下限 | int |
SO_SNDLOWAT | 发送缓冲区下限 | int |
SO_RCVTIMEO | 接收超时 | struct timeval |
SO_SNDTIMEO | 发送超时 | struct timeval |
SO_REUSEADDR | 允许重用本地地址和端口 | int |
SO_TYPE | 获得套接字类型 | int |
SO_BSDCOMPAT | 与BSD系统兼容 | int |
==== IPPROTO_IP IP层/网络层 ==== | ||
IP_HDRINCL | 在数据包中包含IP首部 | int |
IP_OPTINOS | IP首部选项 | int |
IP_TOS | 服务类型 | int |
IP_TTL | 生存时间 | int |
IP_ADD_MEMBERSHIP | 将指定的IP加入多播组 | struct ip_mreq |
==== IPPRO_TCP 传输层 ==== | ||
TCP_MAXSEG | TCP最大数据段的大小 | int |
TCP_NODELAY | 不使用Nagle算法 | int |
端口与地址复用:
int opt = 1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
开通广播权限:
int optval =1;
setsockopt(sockfd,SOL_SOCKKET,SO_BROADCAST,&optval,sizeof(optval));
广播代码流程
广播的发送者
1. 创建用户数据报套接字;
sockfd = socket(AF_INET,SOCK_DGRAM,0);
2.setsockopt可以设置套接字属性,先设定该套接字允许发送广播
int optval = 1;
// SOL_SOCKET 传输层 SO_BROADCAST 允许发送广播
setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&optval,sizeo(optval));
3. 接收方地址指定为广播地址,指定端口号
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
addr.sin_addr.s_addr = inet_addr(argv[1]);
4. 发送数据包
sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr*)&addr,sizeof(addr));
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
if (argc != 3)
{
fprintf(stderr, "用法: %s <广播地址> <端口>\n", argv[0]);
return -1;
}
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket错误");
return -1;
}
int optval = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval));
struct sockaddr_in broadcast_addr;
broadcast_addr.sin_family = AF_INET;
broadcast_addr.sin_port = htons(atoi(argv[2]));
broadcast_addr.sin_addr.s_addr = inet_addr(argv[1]);
char buf[128] = "";
while (1)
{
sendto(sockfd, buf, sizeof(buf), 0,
(struct sockaddr *)&broadcast_addr, sizeof(broadcast_addr));
printf("广播消息已发送。\n");
sleep(2); // 根据需要调整延迟
}
close(sockfd);
return 0;
}
广播的接收者:
(基本无需改动)
1. 创建用户数据报套接字
sockfd = socket(AF_INET,SOCK_DGRAM,0);
2. 绑定IP地址(广播IP或0.0.0.0)和端口
struct sockaddr_in saddr,caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
//广播地址或0.0.0.0
//0.0.0.0 是一个特殊的IP地址,用于表示服务器端将监听所有可用的网络接口
// 而不仅仅是IP地址,广播地址也会监听。
socklen_t len = sizeof(caddr);
bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
3. 等待接收数据
recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&caddr,&len);
//填充结构体
struct sockaddr_in saddr,caddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[1]));
//接收者需要加入广播站
saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
int len = sizeof(caddr);
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
if (argc != 2)
{
fprintf(stderr, "用法: %s <端口>\n", argv[0]);
return -1;
}
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket错误");
return -1;
}
struct sockaddr_in local_addr;
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(atoi(argv[1]));
local_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0)
{
perror("bind错误");
return -1;
}
char buf[128];
struct sockaddr_in sender_addr;
socklen_t sender_len = sizeof(sender_addr);
while (1)
{
int recv_bytes = recvfrom(sockfd, buf, sizeof(buf), 0,
(struct sockaddr *)&sender_addr, &sender_len);
if (recv_bytes < 0)
{
perror("接收错误");
return -1;
}
buf[recv_bytes] = '\0';
printf("来自IP: %s 端口: %d 消息: %s\n", inet_ntoa(sender_addr.sin_addr),
ntohs(sender_addr.sin_port), buf);
}
close(sockfd);
return 0;
}
广播的缺点:
广播方式发给所有的主机,过多的广播会大量的占用网络带宽,造成广播风暴,影响正常的通信
二.组播(多播)
特点:
- 单播方式只能发给一个接收方
- 组播是一个人发送,加入到多播组的主机接收数据
- 多播方式既可以发给多个主机,又能避免像广播一样造成过多的负载
组播的地址:
IP的二级划分中 D类IP:
第一字节的前四位固定为 1110
D类IP : 224.0.0.1 - 239.255.255.255
组播的流程
组播的接收者:
1. 创建用户数据报套接字 UDP
2. 加入多播组 : 接收者加入多播组
3. 绑定组播IP地址(绑定组播IP或0.0.0.0)和端口
4. 等待接收数据
//多播结构体
struct ip_mreq{
struct in_addr imr_multiaddr; //指定多播组IP
struct in_addr imr_interface; //本地IP,通常指定为 INADDR_ANY--0.0.0.0
}
struct in_addr{
_be32 s_addr; //IP地址(大端)
}
//核心代码 ------------------------------------
struct ip_mreq mreq;
bzero(&mreq, sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr(argv[1]); //填充多播组
mreq.imr_interface.s_addr = inet_addr("0.0.0.0"); //自动获取本机IP
//改变套接字属性
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,sizeof(mreq));
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
if (argc != 3)
{
fprintf(stderr, "用法: %s <组播地址> <端口>\n", argv[0]);
return -1;
}
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket错误");
return -1;
}
struct sockaddr_in multicast_addr;
multicast_addr.sin_family = AF_INET;
multicast_addr.sin_port = htons(atoi(argv[2]));
multicast_addr.sin_addr.s_addr = inet_addr(arg v[1]);
// 将套接字加入到组播组
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = inet_addr(argv[1]);
mreq.imr_interface.s_addr = INADDR_ANY;
if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
{
perror("setsockopt错误");
return -1;
}
char buf[128];
struct sockaddr_in sender_addr;
socklen_t sender_len = sizeof(sender_addr);
while (1)
{
int recv_bytes = recvfrom(sockfd, buf, sizeof(buf), 0,
(struct sockaddr *)&sender_addr, &sender_len);
if (recv_bytes < 0)
{
perror("接收错误");
return -1;
}
buf[recv_bytes] = '\0';
printf("来自IP: %s 端口: %d 消息: %s\n", inet_ntoa(sender_addr.sin_addr),
ntohs(sender_addr.sin_port), buf);
}
close(sockfd);
return 0;
}
组播的发送者:
1. 创建用户数据报套接字
2. 指定接收方地址指定为组播地址(224.0.0.1 - 239.255.255.255都可以)
指定接收端端口信息
3. 发送数据包
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
if (argc != 4)
{
fprintf(stderr, "用法: %s <组播地址> <端口> <消息>\n", argv[0]);
return -1;
}
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket错误");
return -1;
}
struct sockaddr_in multicast_addr;
multicast_addr.sin_family = AF_INET;
multicast_addr.sin_port = htons(atoi(argv[2]));
multicast_addr.sin_addr.s_addr = inet_addr(argv[1]);
char buf[128];
snprintf(buf, sizeof(buf), "%s", argv[3]);
while (1)
{
sendto(sockfd, buf, strlen(buf), 0,
(struct sockaddr *)&multicast_addr, sizeof(multicast_addr));
printf("消息已发送。\n");
sleep(2); // 根据需要调整延迟
}
close(sockfd);
return 0;
}
组播和广播的区别:
● 单播方式只能发给一个接收方。
● 广播方式发给所有的主机。过多的广播会大量占用网络带宽,造成广播风暴,影响通信。
● 组播(又称为多播)是一种折中的方式。只有加入某个多播组的主机才能收到数据。
● 组播方式既可以发给多个主机,又能避免像广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)