网络编程day02-组播,广播
1.组播
在 UDP 中,可以用组播方式,针对于同一个网段中的部分客户端进行数据发送,可以一定程度的降低发送端的工作压力。满足数据的多端接收。
1.1组播技术要求
基于 IPv4 通信协议分析,对应的组播地址范围
标准组播 IP 地址范围 : 224.0.0.0 ~ 239.255.255.255
- 本地网络控制, 224.0.0.1 对应所有主机,224.0.0.2 对应所有路由
- 全局范围: 224.0.1.0 ~ 238.255.255.255,公共组播地址,可以实现跨域操作。
- 管理范围:239.0.0.0 ~ 239.255.255.255,组织内部使用,类似于私有 IP
针对于组播操作,需要提供给setsockopt函数实际参数为
setsockopt(sock_fd, // UDP socket 文件描述符IPPROTO_IP, // 当前协议 level 选择 IPPROTO_IPIP_ADD_MEMBERSHIP, // 当前操作是加入组播会籍ip_mreq, // 组播地址和本机地址映射结构体sizeof(struct ip_mreq)); // 映射结构体字节个数
组播地址和本机地址映射结构体
#include <netinet/in.h>// 广播,组播请求映射结构体
struct ip_mreq {struct in_addr imr_multiaddr; // 组播 IP 地址struct in_addr imr_interface; // 本地网络接口 IP
};/*
结构体中的成员变量,所需实际数据是1. struct in_addr imr_multiaddr; 组播 4 字节 int 类型网络字节序 IP 地址 ,inet_pton(AF_INET, "224.0.0.5", &imr_multiaddr);2. struct in_addr imr_interface; 本机 4 字节 int 类型网络字节序 IP 地址 ,inet_pton(AF_INET, "192.168.16.125", &imr_int erface);
*/
其他和点对点的UDP传播函数都是一样的,流程也差不多
2.组播发送端
#define _GNU_SOURCE#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>#define MULTICAST_IP_ADDR "224.1.1.1"
#define HOST_PORT (8848)
#define BUFFER_SIZE (256)int main(int argc, char const *argv[])
{// 1. 创建 UDP 协议 socketint sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);if (-1 == sock_fd){perror("socket failed!");exit(EXIT_FAILURE);}// 2. 设置本机 IP 地址相关结构体数据struct sockaddr_in host_addr;socklen_t socklen = sizeof(struct sockaddr_in);host_addr.sin_family = AF_INET; // IPv4 协议host_addr.sin_port = htons(HOST_PORT); // 本地端口号// INADDR_ANY : Address to accept any incoming messages.// 用于接收任何发送到当前接收端数据的 IP 地址,实际数据为 0x00000000host_addr.sin_addr.s_addr = INADDR_ANY; // 接受端选择 IP 地址 4 字节数据为 INADDR_ANY// 3. bind 当前接收端,用于接收数据的 IP 地址内容,主要是设置当前接受端口号if (bind(sock_fd, (const struct sockaddr *)&host_addr, socklen)){perror("bind failed!");close(sock_fd);exit(EXIT_FAILURE);}/*4.【注意】以上操作 bind 成功,当前 UDP 接收端已具备数据接收能力,开始配置组播注册。【需要】1. struct ip_mreq{struct in_addr imr_multiaddr; // 组播 IP 地址struct in_addr imr_interface; // 本地网络接口 IP};组播 IP 地址和当前本机 IP 地址映射2. setsockopt 函数int setsockopt(int sockfd,int level, int optname,const void *optval, socklen_t optlen);*/// 4.1 创建组播和本机映射 IP 数据结构体struct ip_mreq multicast_host_request;// 4.2 将点分十进制组播 IP 地址字符串转换为网络字节序 IP 地址。inet_pton(AF_INET, MULTICAST_IP_ADDR, &(multicast_host_request.imr_multiaddr.s_addr));// 4.3 将本机 host_addr 结构体数据中的 IP 地址内容直接赋值给 multicast_host_request 映射结构体
#if 1multicast_host_request.imr_interface.s_addr = host_addr.sin_addr.s_addr;
#elsemulticast_host_request.imr_interface.s_addr = INADDR_ANY;
#endif// 4.4 使用 setsockopt 函数配置当前 Socket 特殊功能,添加组播数据接收功能int ret = setsockopt(sock_fd, // 目标添加功能的 socket 文件描述符IPPROTO_IP, // 当前 Level 选择 IPPROTO_IP,确定后续功能模块只能在 IP 模块选择IP_ADD_MEMBERSHIP, // IP_ADD_MEMBERSHIP 添加组播会籍操作&multicast_host_request, // 组播地址和本机地址映射关系结构体sizeof(struct ip_mreq)); // 映射结构体字节个数if (ret){perror("setsockopt failed!");close(sock_fd);exit(EXIT_FAILURE);}// 5. recv 接受数据char buffer[BUFFER_SIZE] = "";struct sockaddr_in sender_addr;socklen_t sender_addr_len;char sender_ip_addr_str[16] = "";ssize_t count = recvfrom(sock_fd, // 接收数据目标 UDP Socketbuffer, // 数据接收缓冲区地址BUFFER_SIZE, // 数据缓冲器字节数0, // flag(struct sockaddr *)&sender_addr, // 发送端 IP 地址数据存储结 构体&sender_addr_len); // 发送端 IP 地址数据存储结构体字节个数if (count > 0){printf("Data from %s:%u, Data : %s\n",// 网络 4 字节 IP 地址数据,转换为 IPv4 本地点分十进制 IP 地址字符串inet_ntop(AF_INET, &(sender_addr.sin_addr.s_addr), sender_ip_addr_str, 16),// 网络 2 字节 Port 端口号数据转本地ntohs(sender_addr.sin_port),// 收到的数据内容buffer);}close(sock_fd);return 0;
}
3.组播接收端
#define _GNU_SOURCE#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>// 当前发送端发送组播消息对应的组播地址。
#define MULTICAST_IP_ADDR "224.1.1.1"
#define SENDER_PORT (8848)
#define BUFFER_SIZE (256)int main(int argc, char const *argv[])
{// 1. 准备 UDP 对应的 Socketint sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);if (-1 == sock_fd){perror("socket failed!");exit(EXIT_FAILURE);}// 2. 准备组播发送 IP 地址结构体数据struct sockaddr_in multicast_addr;socklen_t socklen = sizeof(struct sockaddr_in);memset(&multicast_addr, 0, socklen);// 2.1 明确当前组播对应的 IP 协议为 IPv4 协议multicast_addr.sin_family = AF_INET;// 2.2 端口号转换网络字节序multicast_addr.sin_port = htons(SENDER_PORT);// 2.3 点分十进制 IP 地址转换为网络字节序 4 字节 IP 地址。inet_pton(AF_INET, MULTICAST_IP_ADDR, &(multicast_addr.sin_addr.s_addr));// 3. 发送数据内容char *data = "既然已无退路,那就勇往直前!!!";// 4. 利用 sendto 发送数据到组播地址ssize_t count = sendto(sock_fd, // UDP 协议 socket 文件描述符data, // 发送的目标数据内容strlen(data), // 发送数据字节长度0, // 标志位 0 (const struct sockaddr *)&multicast_addr, // 组播地址 IP 结构体数据socklen); // 组播地址 IP 结构体数据字节数if (-1 == count){perror("sendto failed!");// 必须关闭资源!!!close(sock_fd);exit(EXIT_FAILURE);}close(sock_fd);return 0;
}
4.广播
UDP 协议中支持对整个局域网以内所有设备进行广播操作。基于 IPv4 协议对应 IP 地址是255.255.255.255
。发送端只需要将数据发送到广播地址,其他接收端设备正常数据接收即可。
发送端流程
- 创建对应 UDP 协议 Socket
- 创建对应广播地址 IP 地址结构体数据
- 利用 setsockopt 设置为广播接收模式
- 发送数据到广播地址
- 利用 sendto 发送数据
接收端流程
- 创建对应 UDP 协议 Socket
- 创建基于 INADDR_ANY 地址要求的 IP 地址结构体数据
- bind 操作对应端口号
- 利用 recvfrom 函数接收数据。
4.1核心控制函数setsockopt【小难点】
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
函数功能
设置当前 socket 对应文件描述符相关属性。
函数参数
sockfd
:这是一个表示套接字的文件描述符,也就是之前使用 socket()
函数创建的套接字返回值。通过这个描述符,setsockopt
函数可以知道要对哪个套接字进行操作。
level
:指定选项所在的协议层。常见的取值有:
SOL_SOCKET
:表示通用的套接字选项,这些选项适用于所有类型的套接字。IPPROTO_TCP
:用于设置 TCP 协议相关的选项。IPPROTO_IP
:用于设置 IP 协议相关的选项。IPPROTO_UDP :
用于设置 UDP 协议相关的选项
optname
:具体的选项名称,它和 level
参数配合使用,用于指定要设置的选项。不同的 level
下有不同的 optname
取值。
IP_ADD_MEMBERSHIP
当前 Socket 加入到组播中
optval
:指向一个包含选项值的缓冲区的指针。根据不同的选项,这个缓冲区中存储的数据类型和含义也不同。
#include <netinet/in.h> // 广播,组播请求映射结构体 struct ip_mreq {struct in_addr imr_multiaddr; // 组播 IP 地址struct in_addr imr_interface; // 本地网络接口 IP };
optlen
:表示 optval
缓冲区的长度,以字节为单位。
sizeof(struct ip_mreq);
返回值
设置成功,返回 0
设置失败返回 -1
4.2广播发送端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>#define BOARDCAST_IP_ADDR "255.255.255.255"//发送给同一网段下的所有人
#define BOARDCAST_PORT (9527)int main(int argc, char const *argv[])
{// 1. 创建 UDP 支持的 Socketint sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);if (-1 == sock_fd){perror("socket failed!");exit(EXIT_FAILURE);}// 2. 准备广播地址对应的 IP 结构体struct sockaddr_in boardcast_ip_struct;socklen_t socklen = sizeof(struct sockaddr_in);// 2.1 赋值 AF_INET IPv4 协议boardcast_ip_struct.sin_family = AF_INET;// 2.2 端口号转换为网络字节序赋值给结构体boardcast_ip_struct.sin_port = htons(BOARDCAST_PORT);// 2.3 本地点分十进制 IP 地址数据转换为 4 字节网络字节序 IP 地址数据inet_pton(AF_INET, BOARDCAST_IP_ADDR, &(boardcast_ip_struct.sin_addr.s_addr));// 3. 数据内容char *data = "天气转凉,谨防感冒!!!";// 4. 设置 Socket 广播功能int boardcast_enable = 1;int ret = setsockopt(sock_fd, // socket 对应的文件描述符SOL_SOCKET, // 选择设置原始套接字协议SO_BROADCAST, // 开启当前 Socket 广播功能&boardcast_enable, // 当前广播功能开启标志位参数,要求 int 类型数据为 1sizeof(boardcast_enable)); // 提供的标志位参数字节个数。if (ret){perror("setsockopt failed!");close(sock_fd);exit(EXIT_FAILURE);}// 5. 利用 sendto 发送数据内容ssize_t count = sendto(sock_fd, // UDP socket 套接字 data, // 发送数据首地址strlen(data), // 发送数据字节数0, // flag (const struct sockaddr *)&boardcast_ip_struct, // 广播 IP 地址结构体数据socklen); // 广播地址结构体数据字节数个数。if (-1 == count){perror("sendto failed!");close(sock_fd);exit(EXIT_FAILURE);}close(sock_fd);return 0;
}
4.3广播接收端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>#define BOARDCAST_PORT (9527)
#define BUFFER_SIZE (256)int main(int argc, char const *argv[])
{// 1. 创建 UDP Socket 套接字int sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);if (-1 == sock_fd){perror("socket failed!");exit(EXIT_FAILURE);}// 2. 根据广播端口号,以及 INADDR_ANY 创建对应的广播接收 IP 地址结构体数据struct sockaddr_in boardcast_ip_struct;socklen_t socklen = sizeof(struct sockaddr_in);boardcast_ip_struct.sin_family = PF_INET; // 原码中 #define AF_INET PF_INETboardcast_ip_struct.sin_port = htons(BOARDCAST_PORT);boardcast_ip_struct.sin_addr.s_addr = INADDR_ANY;// 3. bind 当前结构体数据if (bind(sock_fd, (const struct sockaddr *)&boardcast_ip_struct, socklen)){perror("bind failed!");close(sock_fd);exit(EXIT_FAILURE);}// 4. 准备接收数据char buffer[BUFFER_SIZE] = "";char sender_ip_addr_str[16] = "";struct sockaddr_in sender_ip_addr = {0};socklen_t sender_ip_len = 0;ssize_t count = recvfrom(sock_fd,buffer,BUFFER_SIZE,0,(struct sockaddr *)&sender_ip_addr,&sender_ip_len);if (count > 0){printf("boardcast sender %s:%u, data : %s\n",inet_ntop(AF_INET, &(sender_ip_addr.sin_addr.s_addr), sender_ip_addr_str, 16),ntohs(sender_ip_addr.sin_port),buffer);}close(sock_fd);return 0;
}