📜  在C中ping(1)

📅  最后修改于: 2023-12-03 15:37:35.134000             🧑  作者: Mango

在C中ping

简介

Ping 是一种网络工具,用于测试主机之间的连通性。在 C 语言中,可以使用 socket 库编写程序实现 Ping 功能。

Ping 协议基于 ICMP 协议,Ping 命令会向目标主机发送 ICMP Echo Request 数据包,如果目标主机接收到请求,它将响应一个 ICMP Echo Reply 数据包,这样就测试了两台主机之间的连通性。

实现

C语言中可以使用 socket 库的 sendtorecvfrom 函数实现 Ping 功能。具体可以分为以下几个步骤:

  1. 创建 socket
  2. 构造 ICMP Echo Request 数据包
  3. 发送数据包到目标主机
  4. 接收目标主机发回的 ICMP Echo Reply 数据包
  5. 解析 ICMP Echo Reply 数据包,计算往返时间(RTT)

代码示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netdb.h>

int ping(char *hostname, int count);

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("Usage: %s <hostname>\n", argv[0]);
        exit(1);
    }
    ping(argv[1], 4);
    return 0;
}

int ping(char *hostname, int count) {
    int sockfd;
    struct sockaddr_in addr;
    struct hostent *host;
    struct timeval tv_start, tv_end;
    double rtt_ms;
    char sendbuf[BUFSIZ], recvbuf[BUFSIZ];
    int send_bytes, recv_bytes;
    int seq = 0;
    int nsend = 0, nrecv = 0;
    int i;

    host = gethostbyname(hostname);
    if (!host) {
        printf("[ERROR] Unknown host %s\n", hostname);
        return -1;
    }

    sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (sockfd < 0) {
        perror("[ERROR] Create socket failed");
        return -1;
    }

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr = *(struct in_addr *) host->h_addr;

    for (i = 1; i <= count; i++) {
        seq++;
        memset(sendbuf, 0, BUFSIZ);
        memset(recvbuf, 0, BUFSIZ);

        struct icmp *icmp_hdr = (struct icmp *) sendbuf;
        icmp_hdr->icmp_type = ICMP_ECHO;
        icmp_hdr->icmp_code = 0;
        icmp_hdr->icmp_id = getpid() & 0xffff;
        icmp_hdr->icmp_seq = seq;
        gettimeofday(&tv_start, NULL);
        send_bytes = sendto(sockfd, sendbuf, sizeof(struct icmp), 0, (struct sockaddr *) &addr, sizeof(addr));
        if (send_bytes <= 0) {
            perror("[ERROR] Send ICMP ECHO request failed");
            continue;
        }
        nsend++;

        socklen_t addr_len = sizeof(addr);
        recv_bytes = recvfrom(sockfd, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *) &addr, &addr_len);
        if (recv_bytes <= 0) {
            perror("[ERROR] Recv ICMP ECHO reply failed");
            continue;
        }
        if (recv_bytes < sizeof(struct iphdr) + sizeof(struct icmp)) {
            printf("[WARNING] ICMP packet too short (%d bytes)\n", recv_bytes);
            continue;
        }
        struct iphdr *ip_hdr = (struct iphdr *) recvbuf;
        struct icmp *icmp_reply = (struct icmp *) (recvbuf + sizeof(struct iphdr));
        if ((icmp_reply->icmp_type == ICMP_ECHOREPLY) && (icmp_reply->icmp_id == getpid())) {
            nrecv++;
            gettimeofday(&tv_end, NULL);
            rtt_ms = (tv_end.tv_sec - tv_start.tv_sec) * 1000 + (tv_end.tv_usec - tv_start.tv_usec) / 1000.0;
            printf("%d bytes from %s: icmp_seq=%d ttl=%d time=%.2f ms\n", recv_bytes, hostname, seq, ip_hdr->ttl, rtt_ms);
        } else {
            printf("[WARNING] ICMP packet not correct (%d bytes)\n", recv_bytes);
            continue;
        }

        sleep(1);
    }

    printf("--- %s ping statistics ---\n", hostname);
    printf("%d packets transmitted, %d received, %.2f%% packet loss\n", nsend, nrecv, (nsend - nrecv) * 100.0 / nsend);

    close(sockfd);
    return nrecv;
}

本例中使用了 gettimeofday 函数获取系统时间,icmp_seq 作为 Echo Request 和 Echo Reply 数据包的报文序列号,通过计算时间差计算往返时间(RTT)。在发送和接收数据包时,需要处理错误情况,例如发送数据包失败、接收数据包失败或接收到的数据包不正确时,需要显示错误信息并继续处理下一个数据包。

运行

编译并运行:

gcc ping.c -o ping
./ping 127.0.0.1

输出:

84 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.02 ms
84 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.02 ms
84 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.02 ms
84 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.02 ms
--- 127.0.0.1 ping statistics ---
4 packets transmitted, 4 received, 0.00% packet loss