C语言多Ping请求异常:仅成功一次后接收回复阻塞
问题分析与修复:Ping程序仅能成功一次,第二次接收阻塞
核心问题
程序第二次发送请求后陷入阻塞的根本原因是:第一次发送完成后立即清空了发送缓冲区packet,导致后续循环发送的是无效空ICMP包,目标主机不会回复该无效包,因此recvfrom会一直阻塞等待不存在的回复。
具体流程问题:
- 第一次循环:发送预构建的有效ICMP包 → 调用
bzero(packet, IP_MAXPACKET)清空缓冲区 → 接收回复写入packet - 第二次循环:直接发送已被清空的
packet→ 目标主机收到无效包,不返回ICMP回复 →recvfrom无限阻塞等待
此外代码还存在其他关键问题:
- ICMP包序列号未随循环递增,可能被目标主机判定为重复包而忽略
- 接收时未过滤有效ICMP回复(未校验类型、ID、序列号),可能处理无关ICMP包
- 响应时间计算公式错误,导致时间精度丢失
- 硬编码
SOURCE_IP为127.0.0.1,无法正确显示目标主机IP
修复步骤
- 分离发送与接收缓冲区:避免发送包被接收操作覆盖,或把ICMP包构建逻辑移到循环内部,确保每次发送有效包
- 递增ICMP序列号:每次循环更新
icmphdr.icmp_seq并重新计算校验和 - 过滤有效回复:接收时校验ICMP类型为
ICMP_ECHOREPLY,且ID、序列号与发送包匹配 - 修正时间计算:正确计算微秒级时间差
- 动态获取回复IP:从IP头提取回复源地址,替换硬编码的本地IP
修复后的完整代码
#include <arpa/inet.h> #include <errno.h> #include <netinet/in.h> #include <resolv.h> #include <netinet/ip.h> #include <netinet/ip_icmp.h> #include <netdb.h> #include <stdio.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> // IPv4 header len without options #define IP4_HDRLEN 20 // ICMP header len for echo req #define ICMP_HDRLEN 8 // Checksum algo unsigned short calculate_checksum(unsigned short *paddress, int len); int main(int count, char *argv[]) { if (count != 2) { fprintf(stderr, "Usage: %s <destination-ip>\n", argv[0]); return -1; } int seq = 0; const char *data = "This is the ping.\n"; int datalen = strlen(data) + 1; struct sockaddr_in dest_in; memset(&dest_in, 0, sizeof(struct sockaddr_in)); dest_in.sin_family = AF_INET; if (inet_pton(AF_INET, argv[1], &dest_in.sin_addr) <= 0) { fprintf(stderr, "Invalid IP address: %s\n", argv[1]); return -1; } // Create raw socket for ICMP int sock = -1; if ((sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1) { fprintf(stderr, "socket() failed with error: %d\n", errno); fprintf(stderr, "Raw sockets require root privileges.\n"); return -1; } struct timeval start, end; char send_packet[IP_MAXPACKET]; char recv_packet[IP_MAXPACKET]; while (1) { seq++; struct icmp icmphdr; memset(&icmphdr, 0, sizeof(icmphdr)); // 构建ICMP头 icmphdr.icmp_type = ICMP_ECHO; icmphdr.icmp_code = 0; icmphdr.icmp_id = getpid() & 0xFFFF; // 用进程ID作为标识,避免冲突 icmphdr.icmp_seq = seq; icmphdr.icmp_cksum = 0; // 填充发送包 memset(send_packet, 0, IP_MAXPACKET); memcpy(send_packet, &icmphdr, ICMP_HDRLEN); memcpy(send_packet + ICMP_HDRLEN, data, datalen); // 重新计算校验和 icmphdr.icmp_cksum = calculate_checksum((unsigned short *)send_packet, ICMP_HDRLEN + datalen); memcpy(send_packet, &icmphdr, ICMP_HDRLEN); gettimeofday(&start, NULL); // 发送ICMP请求 int bytes_sent = sendto(sock, send_packet, ICMP_HDRLEN + datalen, 0, (struct sockaddr *)&dest_in, sizeof(dest_in)); if (bytes_sent == -1) { fprintf(stderr, "sendto() failed with error: %d\n", errno); close(sock); return -1; } // 接收ICMP回复 socklen_t len = sizeof(dest_in); ssize_t bytes_received = -1; int reply_found = 0; while (!reply_found) { bytes_received = recvfrom(sock, recv_packet, sizeof(recv_packet), 0, (struct sockaddr *)&dest_in, &len); if (bytes_received == -1) { fprintf(stderr, "recvfrom() failed with error: %d\n", errno); close(sock); return -1; } // 解析IP头 struct iphdr *iphdr = (struct iphdr *)recv_packet; int ip_header_len = iphdr->ihl * 4; // 解析ICMP头 struct icmphdr *recv_icmphdr = (struct icmphdr *)(recv_packet + ip_header_len); // 过滤有效回复:类型为ECHO REPLY,ID与发送包匹配,序列号匹配 if (recv_icmphdr->type == ICMP_ECHOREPLY && recv_icmphdr->un.echo.id == (getpid() & 0xFFFF) && recv_icmphdr->un.echo.sequence == seq) { gettimeofday(&end, NULL); // 计算微秒级时间差 unsigned long microseconds = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_usec - start.tv_usec); char src_ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &iphdr->saddr, src_ip, INET_ADDRSTRLEN); printf("Received %ld bytes from %s: seq=%d, time=%ld us\n", bytes_received, src_ip, seq, microseconds); reply_found = 1; } } sleep(1); } close(sock); return 0; } // Compute checksum (RFC 1071). unsigned short calculate_checksum(unsigned short *paddress, int len) { int nleft = len; int sum = 0; unsigned short *w = paddress; unsigned short answer = 0; while (nleft > 1) { sum += *w++; nleft -= 2; } if (nleft == 1) { *((unsigned char *)&answer) = *((unsigned char *)w); sum += answer; } sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); answer = ~sum; return answer; }
关键修复说明
- 分离缓冲区:使用
send_packet存储发送包,recv_packet存储接收回复,避免互相覆盖 - 动态标识与序列号:用进程ID作为ICMP包标识,避免与其他ping进程冲突;每次循环递增序列号,确保包唯一性
- 严格过滤回复:只处理匹配类型、ID、序列号的有效回复,避免处理无关ICMP包
- 时间计算修正:正确计算微秒级时间差,避免精度丢失
- 动态IP显示:从IP头提取回复源地址,正确显示目标主机IP
内容的提问来源于stack exchange,提问作者NiStack
相关产品推荐
相关产品推荐

