欢迎访问我的网站,希望内容对您有用,感兴趣的可以加入我们的社群。

Linux下ping的C语言实现

C/C++ 迷途小书童 2年前 (2023-06-14) 692次浏览 0个评论

环境

  • ubuntu 18.04 64bit
  • gcc 4.8.5

简介

ping 命令是一种常用的网络工具,用于测试网络连接的质量和稳定性。它通过向目标主机发送 ICMP (Internet Control Message Protocol) 数据包,并等待目标主机的响应来检测网络连接的状态。

基本原理

ping 命令发送 ICMP 数据包时,它会记录发送时间,并在接收到目标主机的响应后计算往返时间 (RTT)。如果目标主机没有响应,或者响应时间超过了一定的阈值,ping 命令会认网络连接存在问题。

代码示例

ping 命令通常是使用 C 语言来编写实现。它使用原始套接字 (raw socket) 来发送和接收 ICMP 数据包。原始套接字是一种特殊的套接字类型,它可以直接访问网络层协议,而不需要经过传输层协议 (如 TCPUDP)的处理。这就使得 ping 命令可以直接发送和接收 ICMP 数据包,而不需要考虑传输层协议的复杂性。

下面是一个简单的 ping 命令的代码示例,使用 C 语言编写,可以在 Linux 系统上编译运行

// ping.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <netdb.h>

#define PACKET_SIZE 64
#define MAX_WAIT_TIME 5
#define MAX_NO_PACKETS 3

int sockfd;
char sendpacket[PACKET_SIZE];
char recvpacket[PACKET_SIZE];
struct sockaddr_in dest_addr;
struct sockaddr_in from_addr;
struct timeval tvrecv;
pid_t pid;
int nsend = 0;
int nreceived = 0;
double total_time = 0;
double min_time = 0;
double max_time = 0;

unsigned short cal_chksum(unsigned short *addr, int len)
{
    int nleft = len;
    int sum = 0;
    unsigned short *w = addr;
    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;
}

void send_packet()
{
    int packsize;
    struct icmp *icmp;
    icmp = (struct icmp *)sendpacket;
    icmp->icmp_type = ICMP_ECHO;
    icmp->icmp_code = 0;
    icmp->icmp_cksum = 0;
    icmp->icmp_id = pid;
    icmp->icmp_seq = nsend++;
    memset(icmp->icmp_data, 0xa5, PACKET_SIZE - 8);
    gettimeofday((struct timeval *)icmp->icmp_data, NULL);
    packsize = 8 + PACKET_SIZE;
    icmp->icmp_cksum = cal_chksum((unsigned short *)icmp, packsize);
    sendto(sockfd, sendpacket, packsize, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
}

void recv_packet()
{
    int n;
    socklen_t fromlen;
    fromlen = sizeof(from_addr);
    if ((n = recvfrom(sockfd, recvpacket, sizeof(recvpacket), 0, (struct sockaddr *)&from_addr, &fromlen)) < 0) {
        perror("recvfrom error");
        return;
    }
    gettimeofday(&tvrecv, NULL);
    struct ip *ip = (struct ip *)recvpacket;
    int ip_header_len = ip->ip_hl << 2;
    struct icmp *icmp = (struct icmp *)(recvpacket + ip_header_len);
    int icmp_len = n - ip_header_len;
    if (icmp->icmp_type == ICMP_ECHOREPLY && icmp->icmp_id == pid) {
        nreceived++;
        double rtt = (tvrecv.tv_sec - ((struct timeval *)icmp->icmp_data)->tv_sec) * 1000.0 + (tvrecv.tv_usec - ((struct timeval *)icmp->icmp_data)->tv_usec) / 1000.0;
        total_time += rtt;
        if (rtt < min_time || min_time == 0) {
            min_time = rtt;
        }
        if (rtt > max_time) {
            max_time = rtt;
        }
        printf("%d bytes from %s: icmp_seq=%u ttl=%d time=%.1f ms\n", icmp_len, inet_ntoa(from_addr.sin_addr), icmp->icmp_seq, ip->ip_ttl, rtt);
    }
}

void ping(char *host_name)
{
    struct hostent *host;
    printf("hostname=%s\n", host_name);
    int size = 50 * 1024;
    if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {
        perror("socket error");
        return;
    }
    setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
    bzero(&dest_addr, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    if (inet_addr(host_name) == INADDR_NONE) {
        if ((host = gethostbyname(host_name)) == NULL) {
            perror("gethostbyname error");
            return;
        }
        memcpy((char *)&dest_addr.sin_addr, host->h_addr, host->h_length);
    } else {
        dest_addr.sin_addr.s_addr = inet_addr(host_name);
    }
    pid = getpid();
    printf("PING %s (%s) %d bytes of data.\n", host_name, inet_ntoa(dest_addr.sin_addr), PACKET_SIZE);
    while (nsend < MAX_NO_PACKETS) {
        send_packet();
        recv_packet();
        sleep(1);
    }
    printf("%d packets transmitted, %d received, %.1f%% packet loss, time %.1fms\n", nsend, nreceived, (nsend - nreceived) * 100.0 / nsend, total_time);
    printf("rtt min/avg/max = %.1f/%.1f/%.1f ms\n", min_time, total_time / nreceived, max_time);
}

int main(int argc, char *argv[])
{
    char host_name[256];
    if (argc < 2) {
        printf("usage: %s hostname\n", argv[0]);
        return 0;
    }

    strcpy(host_name, argv[1]);
    ping(host_name);
    return 0;
}

接着就来编译,为了和系统自带的 ping 命令区分开来,给可执行文件换个名字

gcc -o pingDemo ping.c 

然后运行一下,这里需要 root 权限

su
./pingDemo 192.168.1.1

linux ping code

参考资料

喜欢 (0)

您必须 登录 才能发表评论!