如何在以太网中监听MAC地址?C++收发程序实现及应用场景答疑
以太网无IP/端口通信:运作逻辑、实现方案与应用场景
一、以太网替代互联网的核心运作逻辑
当脱离互联网改用以太网直接通信时,无需依赖IP地址和端口,完全靠MAC地址实现设备识别与数据传输:
- 以太网属于链路层协议,每个数据帧的头部会携带目标MAC地址和源MAC地址;
- 局域网内的交换机维护着MAC地址表,会根据目标MAC将帧转发到对应端口;
- 网卡收到帧后,会先检查目标MAC是否与自身MAC匹配(或是广播MAC),匹配则接收并向上传递,否则直接丢弃;
- 这种模式跳过了IP层的路由、端口映射等开销,直接实现设备级的点对点/广播通信。
二、以太网的实际应用场景
- 工业控制系统:PLC、工业机器人等设备对延迟和可靠性要求极高,直接用以太网链路层通信可避免IP协议栈的额外开销,保证实时性;
- 局域网设备发现:打印机、智能家居设备的自动探测,通常通过广播MAC帧来寻找同网段内的设备;
- 网络调试与监控:Wireshark等抓包工具底层就是监听以太网帧,用于排查链路层故障(比如网卡冲突、帧丢失);
- 嵌入式设备通信:单片机、物联网网关等资源有限的设备,省去IP协议栈的占用,直接通过MAC地址完成短距离局域网内的数据交互;
- 特殊安全场景:某些封闭局域网内,禁用IP协议可减少网络攻击面,仅通过MAC地址实现设备间的可控通信。
三、C++实现MAC地址通信(Linux平台)
1. 向指定MAC地址发送数据的客户端
需要创建原始套接字来构造并发送以太网帧,代码示例如下:
#include <iostream> #include <cstring> #include <sys/socket.h> #include <netpacket/packet.h> #include <net/ethernet.h> #include <net/if.h> #include <unistd.h> #include <arpa/inet.h> using namespace std; // 字符串MAC转字节数组 void mac_str_to_bytes(const char* mac_str, unsigned char* mac_bytes) { sscanf(mac_str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &mac_bytes[0], &mac_bytes[1], &mac_bytes[2], &mac_bytes[3], &mac_bytes[4], &mac_bytes[5]); } int main() { // 创建原始套接字 int sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (sockfd < 0) { perror("socket create failed"); return -1; } // 指定本地网卡(替换为你的网卡名称,如eth0、ens33) const char* if_name = "eth0"; int if_index = if_nametoindex(if_name); if (if_index == 0) { perror("get interface index failed"); close(sockfd); return -1; } // 构造以太网帧 unsigned char eth_frame[ETH_FRAME_LEN] = {0}; const char* dest_mac_str = "00:11:22:33:44:55"; // 目标设备MAC const char* src_mac_str = "aa:bb:cc:dd:ee:ff"; // 本地网卡MAC unsigned char dest_mac[6], src_mac[6]; mac_str_to_bytes(dest_mac_str, dest_mac); mac_str_to_bytes(src_mac_str, src_mac); // 填充帧头部:目标MAC、源MAC、帧类型(自定义0x1234) memcpy(eth_frame, dest_mac, 6); memcpy(eth_frame + 6, src_mac, 6); unsigned short eth_type = htons(0x1234); memcpy(eth_frame + 12, ð_type, 2); // 填充负载数据 const char* payload = "Direct MAC communication test"; int payload_len = strlen(payload); memcpy(eth_frame + 14, payload, payload_len); // 绑定套接字到指定网卡 struct sockaddr_ll sock_addr = {0}; sock_addr.sll_family = AF_PACKET; sock_addr.sll_protocol = htons(ETH_P_ALL); sock_addr.sll_ifindex = if_index; sock_addr.sll_halen = ETH_ALEN; memcpy(sock_addr.sll_addr, dest_mac, 6); // 发送帧 int send_len = sendto(sockfd, eth_frame, 14 + payload_len, 0, (struct sockaddr*)&sock_addr, sizeof(sock_addr)); if (send_len < 0) { perror("sendto failed"); close(sockfd); return -1; } cout << "Sent " << send_len << " bytes to MAC: " << dest_mac_str << endl; close(sockfd); return 0; }
编译运行:
g++ send_mac.cpp -o send_mac sudo ./send_mac # 需root权限,原始套接字需要管理员权限
2. MAC地址监听程序
监听指定网卡上的以太网帧,筛选出目标MAC匹配的帧:
#include <iostream> #include <cstring> #include <sys/socket.h> #include <netpacket/packet.h> #include <net/ethernet.h> #include <net/if.h> #include <unistd.h> #include <arpa/inet.h> using namespace std; // 比较两个MAC地址是否相等 bool mac_equal(const unsigned char* mac1, const unsigned char* mac2) { return memcmp(mac1, mac2, 6) == 0; } // 字节数组MAC转字符串 void mac_bytes_to_str(const unsigned char* mac_bytes, char* mac_str) { sprintf(mac_str, "%02x:%02x:%02x:%02x:%02x:%02x", mac_bytes[0], mac_bytes[1], mac_bytes[2], mac_bytes[3], mac_bytes[4], mac_bytes[5]); } int main() { // 创建原始套接字 int sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (sockfd < 0) { perror("socket create failed"); return -1; } // 指定监听网卡 const char* if_name = "eth0"; int if_index = if_nametoindex(if_name); if (if_index == 0) { perror("get interface index failed"); close(sockfd); return -1; } // 绑定套接字到网卡 struct sockaddr_ll sock_addr = {0}; sock_addr.sll_family = AF_PACKET; sock_addr.sll_protocol = htons(ETH_P_ALL); sock_addr.sll_ifindex = if_index; if (bind(sockfd, (struct sockaddr*)&sock_addr, sizeof(sock_addr)) < 0) { perror("bind failed"); close(sockfd); return -1; } // 要监听的目标MAC(替换为你的网卡MAC) const char* target_mac_str = "aa:bb:cc:dd:ee:ff"; unsigned char target_mac[6]; sscanf(target_mac_str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &target_mac[0], &target_mac[1], &target_mac[2], &target_mac[3], &target_mac[4], &target_mac[5]); cout << "Listening for frames to MAC: " << target_mac_str << " on " << if_name << endl; // 循环接收帧 unsigned char eth_frame[ETH_FRAME_LEN]; while (true) { int recv_len = recvfrom(sockfd, eth_frame, ETH_FRAME_LEN, 0, nullptr, nullptr); if (recv_len < 0) { perror("recvfrom failed"); close(sockfd); return -1; } // 解析帧头部 unsigned char* dest_mac = eth_frame; unsigned char* src_mac = eth_frame + 6; unsigned short eth_type = ntohs(*(unsigned short*)(eth_frame + 12)); unsigned char* payload = eth_frame + 14; int payload_len = recv_len - 14; // 筛选目标MAC匹配或广播的帧 if (mac_equal(dest_mac, target_mac) || mac_equal(dest_mac, (unsigned char*)"\xff\xff\xff\xff\xff\xff")) { char src_mac_str[18]; mac_bytes_to_str(src_mac, src_mac_str); cout << "\nReceived from: " << src_mac_str << endl; cout << "Frame type: 0x" << hex << eth_type << dec << endl; cout << "Payload (" << payload_len << " bytes): " << string((char*)payload, payload_len) << endl; } } close(sockfd); return 0; }
编译运行:
g++ listen_mac.cpp -o listen_mac sudo ./listen_mac
四、Windows平台实现说明
Windows下没有PF_PACKET套接字,若要实现链路层通信,可通过以下两种方式:
- 使用WinPcap或Npcap库,提供了跨平台的链路层操作API;
- 调用Windows原生的
WSASocket创建原始套接字,设置AF_LINK地址族,但操作比Linux复杂,且需要管理员权限。
内容的提问来源于stack exchange,提问作者Joseph Conrad
相关产品推荐
相关产品推荐

