You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言多Ping请求异常:仅成功一次后接收回复阻塞

问题分析与修复:Ping程序仅能成功一次,第二次接收阻塞

核心问题

程序第二次发送请求后陷入阻塞的根本原因是:第一次发送完成后立即清空了发送缓冲区packet,导致后续循环发送的是无效空ICMP包,目标主机不会回复该无效包,因此recvfrom会一直阻塞等待不存在的回复。

具体流程问题:

  1. 第一次循环:发送预构建的有效ICMP包 → 调用bzero(packet, IP_MAXPACKET)清空缓冲区 → 接收回复写入packet
  2. 第二次循环:直接发送已被清空的packet → 目标主机收到无效包,不返回ICMP回复 → recvfrom无限阻塞等待

此外代码还存在其他关键问题:

  • ICMP包序列号未随循环递增,可能被目标主机判定为重复包而忽略
  • 接收时未过滤有效ICMP回复(未校验类型、ID、序列号),可能处理无关ICMP包
  • 响应时间计算公式错误,导致时间精度丢失
  • 硬编码SOURCE_IP为127.0.0.1,无法正确显示目标主机IP

修复步骤

  1. 分离发送与接收缓冲区:避免发送包被接收操作覆盖,或把ICMP包构建逻辑移到循环内部,确保每次发送有效包
  2. 递增ICMP序列号:每次循环更新icmphdr.icmp_seq并重新计算校验和
  3. 过滤有效回复:接收时校验ICMP类型为ICMP_ECHOREPLY,且ID、序列号与发送包匹配
  4. 修正时间计算:正确计算微秒级时间差
  5. 动态获取回复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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 20:35:27