网络(十六)广播和组播
广播
广播(Broadcast)是一种网络通信方式,允许一台主机向网络中的所有其他主机发送数据包。
在IP网络中,广播地址用于向同一子网中的所有主机发送数据包。
广播地址通常是子网中的最后一个地址,例如,如果子网掩码是255.255.255.0,那么广播地址就是192.168.1.255。
只有用户数据报(使用UDP协议)套接字才能广播
数据包发送方式只有一个接受方,称为单播
如果同时发给局域网中的所有主机,称为广播
如果同时发给局域网中的部分主机,称为组播
在局域网中,广播是一种将消息发送给所有主机的通信方式。以下是广播在局域网中发送的基本原理和步骤:
- 广播地址:
在以太网中,广播地址是一个特殊的MAC地址,通常是全FF(即FF:FF:FF:FF:FF:FF)。
在IP网络中,广播地址通常是网络地址加上全1的子网掩码。例如,在一个C类网络中,如果网络地址是192.168.1.0,子网掩码是255.255.255.0,那么广播地址就是192.168.1.255。- 数据包发送:
当一台主机需要发送广播消息时,它会将要发送的数据包的目的MAC地址设置为广播地址(FF:FF:FF:FF:FF:FF)。
数据包的源MAC地址设置为发送主机的MAC地址。- 数据包传输:
交换机或集线器接收到这个数据包后,会将其转发给连接到它的所有其他端口。
这样,局域网中的所有主机都会收到这个广播数据包。- 数据包处理:
每台主机在接收到广播数据包后,会检查数据包的目的MAC地址。
如果目的MAC地址是广播地址,主机会处理这个数据包。- 应用层广播:
在应用层,例如在UDP协议中,应用程序可以发送一个目的IP地址为广播地址的数据包。
操作系统会将这个数据包封装成一个以太网帧,并将目的MAC地址设置为广播地址,然后发送出去。
在C语言中,使用广播进行通信通常涉及以下步骤:
- 创建套接字:使用 socket() 函数创建一个UDP套接字。
- 设置广播选项:使用 setsockopt() 函数设置套接字的广播选项,允许套接字发送广播数据包。
- 设置广播地址:使用 sockaddr_in 结构体设置广播地址和端口。
- 发送数据:使用 sendto() 函数发送广播数据包。
示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
// 定义广播端口和广播IP地址
#define PORT 8080
#define BROADCAST_IP "255.255.255.255"
#define BUFSIZE 1024
int main() {
int sockfd; // 套接字文件描述符
struct sockaddr_in broadcast_addr; // 广播地址结构体
char *message = "Hello, this is a broadcast message!"; // 要发送的广播消息
int broadcastEnable = 1; // 广播选项,设置为1表示启用广播
// 创建UDP套接字
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("socket failed"); // 如果创建失败,输出错误信息
exit(EXIT_FAILURE); // 退出程序
}
// 设置广播选项
if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, sizeof(broadcastEnable)) < 0) {
perror("setsockopt failed"); // 如果设置失败,输出错误信息
close(sockfd); // 关闭套接字
exit(EXIT_FAILURE); // 退出程序
}
// 设置广播地址
memset(&broadcast_addr, 0, sizeof(broadcast_addr)); // 初始化广播地址结构体
broadcast_addr.sin_family = AF_INET; // 设置地址族为IPv4
broadcast_addr.sin_addr.s_addr = inet_addr(BROADCAST_IP); // 设置广播IP地址
broadcast_addr.sin_port = htons(PORT); // 设置广播端口
// 发送广播数据
if (sendto(sockfd, message, strlen(message), 0, (struct sockaddr *)&broadcast_addr, sizeof(broadcast_addr)) < 0) {
perror("sendto failed"); // 如果发送失败,输出错误信息
close(sockfd); // 关闭套接字
exit(EXIT_FAILURE); // 退出程序
}
printf("Broadcast message sent successfully.\n"); // 发送成功,输出提示信息
// 关闭套接字
close(sockfd);
return 0;
}
组播
组播(Multicast)是一种网络通信方式,它允许将数据包发送给一组特定的主机,而不是所有主机。组播通过使用特定的组播地址来实现,这些地址标识了一组希望接收相同数据的主机。
以下是组播的基本概念和原理:
1.组播地址:
组播地址是一个特殊的IP地址范围,用于标识一组主机。
在IPv4中,组播地址的范围是224.0.0.0到239.255.255.255。
在IPv6中,组播地址以FF开头,后面跟着一个标识符和组ID。
2.组播组:
主机可以加入一个或多个组播组,通过加入组播组,主机表明它希望接收发送到该组播地址的数据包。
组播组是动态的,主机可以随时加入或离开组播组。
3.数据包发送:
当一台主机需要发送组播数据包时,它会将要发送的数据包的目的IP地址设置为组播地址。
数据包的源IP地址设置为发送主机的IP地址。
4.数据包传输:
路由器和交换机在接收到组播数据包后,会根据组播路由协议(如IGMP、PIM等)将数据包转发给所有加入该组播组的主机。
这样,只有那些加入该组播组的主机才会收到这个数据包。
5.应用层组播:
在应用层,例如在UDP协议中,应用程序可以发送一个目的IP地址为组播地址的数据包。
操作系统会将这个数据包封装成一个以太网帧,并将目的MAC地址设置为对应的组播MAC地址,然后发送出去。
组播的优点在于它能够有效地将数据传输给一组特定的主机,而不需要发送多个单播数据包,从而节省了网络带宽。组播广泛应用于视频会议、在线直播、软件更新等需要同时向多个接收者传输数据的场景。
在C语言中,使用广播进行通信通常涉及以下步骤:
- 创建套接字:使用 socket() 函数创建一个UDP套接字。
- 设置组播选项:使用 setsockopt() 函数设置套接字的组播选项,允许套接字发送组播数据包。
- 设置组播地址:使用 sockaddr_in 结构体设置组播地址和端口。
- 加入组播组:使用 setsockopt() 函数加入组播组。
- 发送数据:使用 sendto() 函数发送组播数据包。
示例
设置本地地址加入多播组
使用setsockopt函数设置 参数是一个 struct ip_mreqn 结构体
struct ip_mreqn {
struct in_addr imr_multiaddr; /* 多播组的IP地址 */
struct in_addr imr_address; /* 本地IP地址 */
int imr_ifindex; /* 接口索引 填0 即可 */
};
struct ip_mreqn mreqn;
mreqn.imr_multiaddr.s_addr = inet_addr("多播组的IP地址");
mreqn.imr_address.s_addr = inet_addr("本地IP地址");
mreqn.imr_ifindex = 0;
if(-1 == setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreqn, sizeof(mreqn)))
ERR_LOG("setsockopt error");
接收端
#include <my_head.h>
int main(int argc, const char *argv[])
{
//创建套接字
int sockfd = 0;
if(-1 == (sockfd = socket(AF_INET, SOCK_DGRAM, 0)))
ERR_LOG("socket error");
//填充服务器网络信息结构体
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
//要填组播的IP地址 224-239
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddrlen = sizeof(serveraddr);
//绑定
if(-1 == bind(sockfd, (struct sockaddr *)&serveraddr, serveraddrlen))
ERR_LOG("bind error");
//设置加入多播组
struct ip_mreqn mreqn;
mreqn.imr_multiaddr.s_addr = inet_addr(argv[1]);
mreqn.imr_address.s_addr = inet_addr("192.168.2.91");
mreqn.imr_ifindex = 0;
if(-1 == setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreqn, sizeof(mreqn)))
ERR_LOG("setsockopt error");
//定义结构体保存发送方信息
struct sockaddr_in clientaddr;
socklen_t clientaddrlen = sizeof(clientaddr);
//接收数据
char buff[128];
while(1){
memset(buff, 0, sizeof(buff));
if(-1 == recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&clientaddr, &clientaddrlen))
ERR_LOG("recvfrom error");
printf("客户端[%s:%d]发来数据[%s]\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), buff);
}
//关闭套接字
close(sockfd);
return 0;
}
发送端
#include <my_head.h>
int main(int argc, const char *argv[])
{
//创建套接字
int sockfd = 0;
if(-1 == (sockfd = socket(AF_INET, SOCK_DGRAM, 0)))
ERR_LOG("socket error");
//填充服务器网络信息结构体
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
//要填组播的IP地址 224-239
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddrlen = sizeof(serveraddr);
//发送数据
char buff[128];
while(1){
fgets(buff, sizeof(buff), stdin);
buff[strlen(buff)-1] = '\0';
if(-1 == sendto(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&serveraddr, serveraddrlen))
ERR_LOG("sendto error");
}
//关闭套接字
close(sockfd);
return 0;
}
本地通信(UNIX域套接字)
在本地通信中,UNIX域套接字是一种高效的进程间通信(IPC)机制。它允许同一台机器上的不同进程之间进行通信。
UNIX域套接字是一种基于文件系统的IPC机制,它不需要网络协议栈的支持。
UNIX域套接字主要使用以下几个系统调用函数:
socket():创建一个新的套接字。
bind():将套接字绑定到一个地址。
listen():使套接字进入监听状态,等待客户端连接。
accept():接受一个客户端连接。
connect():连接到服务器端的套接字。
send() 和 recv():发送和接收数据。
close():关闭套接字。
服务器端:
socket():创建一个UNIX域套接字。
bind():将套接字绑定到一个路径(例如 /tmp/my_unix_socket)。
listen():使套接字进入监听状态,等待客户端连接。
accept():接受一个客户端连接。
recv():接收数据。
send():发送响应。
close():关闭套接字。
unlink():删除套接字文件。
客户端:
socket():创建一个UNIX域套接字。
connect():连接到服务器端的套接字路径。
send():发送数据。
recv():接收响应。
close():关闭套接字。
服务器端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#define SOCKET_PATH "/tmp/my_unix_socket"
int main() {
struct sockaddr_un server_addr;
int server_socket, client_socket;
socklen_t client_len;
char buffer[1024];
// 创建UNIX域套接字
if ((server_socket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1);
// 绑定套接字
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
close(server_socket);
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_socket, 5) == -1) {
perror("listen");
close(server_socket);
exit(EXIT_FAILURE);
}
printf("服务器正在监听...\n");
while (1) {
// 接受连接
client_len = sizeof(struct sockaddr_un);
if ((client_socket = accept(server_socket, (struct sockaddr *)&server_addr, &client_len)) == -1) {
perror("accept");
continue;
}
printf("连接已建立\n");
// 接收数据
ssize_t num_bytes = recv(client_socket, buffer, sizeof(buffer), 0);
if (num_bytes > 0) {
buffer[num_bytes] = '\0';
printf("收到数据: %s\n", buffer);
// 发送响应
send(client_socket, "Hello from server", 17, 0);
}
close(client_socket);
}
close(server_socket);
unlink(SOCKET_PATH);
return 0;
}
客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#define SOCKET_PATH "/tmp/my_unix_socket"
int main() {
struct sockaddr_un server_addr;
int client_socket;
char buffer[1024];
// 创建UNIX域套接字
if ((client_socket = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1);
// 连接到服务器
if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("connect");
close(client_socket);
exit(EXIT_FAILURE);
}
printf("已连接到服务器\n");
// 发送数据
send(client_socket, "Hello from client", 17, 0);
// 接收响应
ssize_t num_bytes = recv(client_socket, buffer, sizeof(buffer), 0);
if (num_bytes > 0) {
buffer[num_bytes] = '\0';
printf("收到响应: %s\n", buffer);
}
close(client_socket);
return 0;
}