Linux网络编程:广播、组播与原始套接字
Linux网络编程:广播、组播与原始套接字
一、广播通信
1.1 广播的定义与特点
广播是一种将数据发送到同一网络中所有设备的通信方式。发送者只需要发送一次数据,网络中的所有设备都会接收到这些数据。
关键特点:
• 范围限制:仅限于同一局域网(LAN)
• 效率考量:数据会被发送到网络中的所有设备,即使某些设备不需要这些数据
• 典型应用:局域网设备发现、配置分发等场景
1.2 广播地址详解
广播通信依赖于特殊的IP地址:
• IPv4广播地址:
• 受限广播地址:255.255.255.255
• 子网广播地址:如192.168.1.255
(主机号全为1)
• IPv6注意事项:IPv6不支持传统广播,而是使用组播替代
1.3 广播实现代码示例
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>#define ErrExit(msg) do {perror(msg); exit(EXIT_FAILURE);} while(0)int main(int argc, char *argv[]) {int fd = -1;struct sockaddr_in peeraddr;char buf[BUFSIZ] = {};/* 参数检查 */if(argc < 3) {fprintf(stderr, "Usage: %s <broadcast_addr> <port>\n", argv[0]);exit(EXIT_FAILURE);}/* 创建UDP套接字 */if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)ErrExit("socket");/* 设置广播选项 */int on = 1;if(setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0)ErrExit("setsockopt");/* 配置目标地址 */memset(&peeraddr, 0, sizeof(peeraddr));peeraddr.sin_family = AF_INET;peeraddr.sin_port = htons(atoi(argv[2]));if(inet_pton(AF_INET, argv[1], &peeraddr.sin_addr) <= 0)ErrExit("inet_pton");/* 广播消息 */while(fgets(buf, BUFSIZ, stdin) != NULL) {if(sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)&peeraddr, sizeof(peeraddr)) < 0)ErrExit("sendto");}close(fd);return 0;
}
二、组播通信
2.1 组播基础概念
组播(多播)是一种允许将数据发送到一组特定接收者的通信方式,相比广播更加高效。
关键特性:
• 地址范围:IPv4组播地址范围为224.0.0.0
到239.255.255.255
• 地址类型:D类地址代表一个多播组
• 使用限制:多播地址只能用于目的地址,不能用于源地址
2.2 组播相关数据结构
ip_mreqn
结构体详解
struct ip_mreqn {struct in_addr imr_multiaddr; // 组播组IP地址struct in_addr imr_address; // 本地接口IP地址int imr_ifindex; // 网络接口索引
};
字段说明:
imr_multiaddr
:要加入/离开的组播组IP地址imr_address
:用于组播通信的本地接口IP地址imr_ifindex
:网络接口索引
2.3 组播实现示例
组播发送端
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>#define ErrExit(msg) do {perror(msg); exit(EXIT_FAILURE);} while(0)int main(int argc, char *argv[]) {int fd = -1;struct sockaddr_in multicast_addr;char buf[BUFSIZ] = {};/* 参数检查 */if(argc < 3) {fprintf(stderr, "Usage: %s <multicast_addr> <port>\n", argv[0]);exit(EXIT_FAILURE);}/* 创建UDP套接字 */if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)ErrExit("socket");/* 配置组播地址 */memset(&multicast_addr, 0, sizeof(multicast_addr));multicast_addr.sin_family = AF_INET;multicast_addr.sin_port = htons(atoi(argv[2]));if(inet_pton(AF_INET, argv[1], &multicast_addr.sin_addr) <= 0)ErrExit("inet_pton");/* 发送组播消息 */while(fgets(buf, BUFSIZ, stdin) != NULL) {if(sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)&multicast_addr, sizeof(multicast_addr)) < 0)ErrExit("sendto");}close(fd);return 0;
}
组播接收端
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>#define ErrExit(msg) do {perror(msg); exit(EXIT_FAILURE);} while(0)int main(int argc, char *argv[]) {int fd = -1;struct sockaddr_in local_addr;struct ip_mreqn mreq;char buf[BUFSIZ] = {};/* 参数检查 */if(argc < 3) {fprintf(stderr, "Usage: %s <multicast_addr> <port>\n", argv[0]);exit(EXIT_FAILURE);}/* 创建UDP套接字 */if((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)ErrExit("socket");/* 配置本地地址 */memset(&local_addr, 0, sizeof(local_addr));local_addr.sin_family = AF_INET;local_addr.sin_port = htons(atoi(argv[2]));local_addr.sin_addr.s_addr = htonl(INADDR_ANY);/* 绑定套接字 */if(bind(fd, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0)ErrExit("bind");/* 加入多播组 */memset(&mreq, 0, sizeof(mreq));if(inet_pton(AF_INET, argv[1], &mreq.imr_multiaddr) <= 0)ErrExit("inet_pton");mreq.imr_address.s_addr = htonl(INADDR_ANY);mreq.imr_ifindex = 0;if(setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)ErrExit("setsockopt");/* 接收组播消息 */while(1) {struct sockaddr_in sender_addr;socklen_t addrlen = sizeof(sender_addr);ssize_t n = recvfrom(fd, buf, BUFSIZ, 0, (struct sockaddr *)&sender_addr, &addrlen);if(n < 0)ErrExit("recvfrom");char sender_ip[INET_ADDRSTRLEN];inet_ntop(AF_INET, &sender_addr.sin_addr, sender_ip, INET_ADDRSTRLEN);printf("[%s:%d] %s\n", sender_ip, ntohs(sender_addr.sin_port), buf);}close(fd);return 0;
}
三、原始套接字编程
3.1 原始套接字概述
原始套接字提供对底层网络协议的直接访问,允许开发者处理更底层的网络数据包。
主要特点:
• 可以接收和发送原始网络数据包
• 能够构造自定义协议头
• 适用于网络监控、安全工具开发等场景
3.2 链路层原始套接字
链路层原始套接字工作在数据链路层,可以捕获和处理以太网帧。
示例代码:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/ether.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <net/ethernet.h>#define MTU 1500int main() {int sockfd;uint8_t buf[MTU];/* 创建链路层原始套接字 */if((sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0) {perror("socket");return 1;}printf("Socket created successfully. Waiting for packets...\n");while(1) {/* 接收原始数据包 */ssize_t len = recv(sockfd, buf, sizeof(buf), 0);if(len < 0) {perror("recv");break;}/* 解析以太网帧头 */struct ether_header *eth = (struct ether_header *)buf;uint16_t ether_type = ntohs(eth->ether_type);/* 根据协议类型处理 */switch(ether_type) {case ETHERTYPE_IP:printf("IP Packet\n");/* 解析IP头 */struct iphdr *iph = (struct iphdr *)(buf + sizeof(struct ether_header));printf(" Source IP: %s\n", inet_ntoa(*(struct in_addr *)&iph->saddr));printf(" Dest IP: %s\n", inet_ntoa(*(struct in_addr *)&iph->daddr));/* 如果是TCP协议 */if(iph->protocol == IPPROTO_TCP) {struct tcphdr *tcph = (struct tcphdr *)(buf + sizeof(struct ether_header) + iph->ihl*4);printf(" TCP Source Port: %d\n", ntohs(tcph->source));printf(" TCP Dest Port: %d\n", ntohs(tcph->dest));}break;case ETHERTYPE_ARP:printf("ARP Packet\n");break;default:printf("Other Protocol (0x%04x)\n", ether_type);}}close(sockfd);return 0;
}
3.3 网络层原始套接字
网络层原始套接字工作在IP层,可以处理IP数据包。
示例代码:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <sys/ioctl.h>
#include <termios.h>#define MTU 1500int main() {int sockfd;uint8_t buf[MTU];/* 创建原始套接字 */if((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP)) < 0) {perror("socket");return 1;}printf("Socket created successfully. Waiting for TCP packets...\n");while(1) {/* 接收原始数据包 */ssize_t len = recv(sockfd, buf, sizeof(buf), 0);if(len < 0) {perror("recv");break;}/* 解析IP头 */struct iphdr *iph = (struct iphdr *)buf;printf("IP Packet\n");printf(" Version: %d\n", iph->version);printf(" Header Length: %d bytes\n", iph->ihl*4);printf(" Total Length: %d bytes\n", ntohs(iph->tot_len));printf(" Source IP: %s\n", inet_ntoa(*(struct in_addr *)&iph->saddr));printf(" Dest IP: %s\n", inet_ntoa(*(struct in_addr *)&iph->daddr));/* 解析TCP头 */struct tcphdr *tcph = (struct tcphdr *)(buf + iph->ihl*4);printf("TCP Segment\n");printf(" Source Port: %d\n", ntohs(tcph->source));printf(" Dest Port: %d\n", ntohs(tcph->dest));printf(" Header Length: %d bytes\n", tcph->doff*4);/* 显示TCP数据 */if(len > iph->ihl*4 + tcph->doff*4) {uint8_t *data = buf + iph->ihl*4 + tcph->doff*4;int datalen = len - iph->ihl*4 - tcph->doff*4;printf("TCP Data (%d bytes):\n", datalen);for(int i = 0; i < datalen; i++) {if(isprint(data[i]))printf("%c", data[i]);elseprintf(".");if((i+1) % 16 == 0) printf("\n");}printf("\n");}printf("\n");}close(sockfd);return 0;
}