linux 中的ping 命令的实现

编写.c 原文件

vi ping.c

编译 gcc -o ping ping.c

./ping IP 运行即可

编译内容如下:

/*ping.c*/ 
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <unistd.h>
#include <signal.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/time.h>
#include <stdio.h>
#include <string.h> /*bzero*/
#include <netdb.h>
#include <pthread.h>
/*保存已经发送包的状态值*/
typedef struct pingm_pakcet{
    struct timeval tv_begin;    /*发送的时间*/
    struct timeval tv_end;        /*接收到的时间*/
    short seq;                    /*序列号*/
    int flag;        /*1,表示已经发送但没有接收到回应包0,表示接收到回应包*/
}pingm_pakcet;
static pingm_pakcet pingpacket[128];
static pingm_pakcet *icmp_findpacket(int seq);
static unsigned short icmp_cksum(unsigned char *data,  int len);
static struct timeval icmp_tvsub(struct timeval end,struct timeval begin);
static void icmp_statistics(void);
static void icmp_pack(struct icmp *icmph, int seq, struct timeval *tv, int length );
static int icmp_unpack(char *buf,int len);
static void *icmp_recv(void *argv);
static void *icmp_send(void *argv);
static void icmp_sigint(int signo);
static void icmp_usage();
#define K 1024
#define BUFFERSIZE 72                    /*发送缓冲区大小*/
static char send_buff[BUFFERSIZE];
static char recv_buff[2*K];    /*为防止接收溢出,接收缓冲区设置大一些*/
static struct sockaddr_in dest;        /*目的地址*/
static int rawsock = 0;                    /*发送和接收线程需要的socket描述符*/
static pid_t pid=0;                        /*进程PID*/
static int alive = 0;                    /*是否接收到退出信号*/
static short packet_send = 0;            /*已经发送的数据包有多少*/
static short packet_recv = 0;            /*已经接收的数据包有多少*/
static char dest_str[80];                /*目的主机字符串*/
static struct timeval tv_begin, tv_end,tv_interval;
/*本程序开始发送、结束和时间间隔*/
static void icmp_usage()
{
    /*ping加IP地址或者域名*/
    printf("ping aaa.bbb.ccc.ddd\n");
}
/*主程序*/
int main(int argc, char *argv[])
{
    struct hostent * host = NULL;
    struct protoent *protocol = NULL;
    char protoname[]= "icmp";
    unsigned long inaddr = 1;
    int size = 128*K;
    /*参数是否数量正确*/
    if(argc < 2)
    {
        icmp_usage();
        return -1;
    }
    /*获取协议类型ICMP*/
    protocol = getprotobyname(protoname);
    if (protocol == NULL)
    {
        perror("getprotobyname()");
        return -1;
    }
    /*复制目的地址字符串*/
    memcpy(dest_str,  argv[1], strlen(argv[1])+1);
    memset(pingpacket, 0, sizeof(pingm_pakcet) * 128);
    /*socket初始化*/
    rawsock = socket(AF_INET, SOCK_RAW,  protocol->p_proto);
    if(rawsock < 0)
    {
        perror("socket");
        return -1;
    }
    /*为了与其他进程的ping程序区别,加入pid*/
    pid = getuid();
    /*增大接收缓冲区,防止接收的包被覆盖*/
    setsockopt(rawsock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));
    bzero(&dest, sizeof(dest));
    /*获取目的地址的IP地址*/
    dest.sin_family = AF_INET;
    
    /*输入的目的地址为字符串IP地址*/
    inaddr = inet_addr(argv[1]);
    if(inaddr == INADDR_NONE)
    {
        /*输入的是DNS地址*/
        host = gethostbyname(argv[1]);
        if(host == NULL)
        {
            perror("gethostbyname");
            return -1;
        }
        /*将地址复制到dest中*/
        memcpy((char *)&dest.sin_addr, host->h_addr, host->h_length);
    }
    else        /*为IP地址字符串*/
    {
        memcpy((char*)&dest.sin_addr, &inaddr, sizeof(inaddr));
    }
    /*打印提示*/
    inaddr = dest.sin_addr.s_addr;
    printf("PING %s (%ld.%ld.%ld.%ld) 56(84) bytes of data.\n", 
        dest_str, 
        (inaddr&0x000000FF)>>0,
        (inaddr&0x0000FF00)>>8,
        (inaddr&0x00FF0000)>>16,
        (inaddr&0xFF000000)>>24);
    /*截取信号SIGINT,将icmp_sigint挂接上*/
    signal(SIGINT, icmp_sigint);
    alive = 1;                        /*初始化为可运行*/
    pthread_t send_id, recv_id;        /*建立两个线程,用于发送和接收*/
    int err = 0;
    err = pthread_create(&send_id, NULL, icmp_send, NULL);        /*发送*/
    if(err < 0)
    {
        return -1;
    }
    err = pthread_create(&recv_id, NULL, icmp_recv, NULL);        /*接收*/
    if(err < 0)
    {
        return -1;
    }
    
    /*等待线程结束*/
    pthread_join(send_id, NULL);
    pthread_join(recv_id, NULL);
    /*清理并打印统计结果*/
    close(rawsock);
    icmp_statistics();
    return 0;    
}

/*CRC16校验和计算icmp_cksum
参数:
    data:数据
    len:数据长度
返回值:
    计算结果,short类型
*/
static unsigned short icmp_cksum(unsigned char *data,  int len)
{
    int sum=0;                            /*计算结果*/
    int odd = len & 0x01;                    /*是否为奇数*/
    /*将数据按照2字节为单位累加起来*/
    while( len & 0xfffe)  {
        sum += *(unsigned short*)data;
        data += 2;
        len -=2;
    }
    /*判断是否为奇数个数据,若ICMP报头为奇数个字节,会剩下最后一字节*/
    if( odd) {
        unsigned short tmp = ((*data)<<8)&0xff00;
        sum += tmp;
    }
    sum = (sum >>16) + (sum & 0xffff);    /*高低位相加*/
    sum += (sum >>16) ;                    /*将溢出位加入*/
    
    return ~sum;                             /*返回取反值*/
}

/*设置ICMP报头*/
static void icmp_pack(struct icmp *icmph, int seq, struct timeval *tv, int length )
{
    unsigned char i = 0;
    /*设置报头*/
    icmph->icmp_type = ICMP_ECHO;    /*ICMP回显请求*/
    icmph->icmp_code = 0;            /*code值为0*/
    icmph->icmp_cksum = 0;      /*先将cksum值填写0,便于之后的cksum计算*/
    icmph->icmp_seq = seq;            /*本报的序列号*/
    icmph->icmp_id = pid &0xffff;    /*填写PID*/
    for(i = 0; i< length; i++)
        icmph->icmp_data[i] = i;
                                    /*计算校验和*/
    icmph->icmp_cksum = icmp_cksum((unsigned char*)icmph, length);
}

/*解压接收到的包,并打印信息*/
static int icmp_unpack(char *buf,int len)
{
    int iphdrlen;
    struct ip *ip = NULL;
    struct icmp *icmp = NULL;
    int rtt;
    
    ip=(struct ip *)buf;                     /*IP头部*/
    iphdrlen=ip->ip_hl*4;                    /*IP头部长度*/
    icmp=(struct icmp *)(buf+iphdrlen);        /*ICMP段的地址*/
    len-=iphdrlen;
                                            /*判断长度是否为ICMP包*/
    if( len<8) 
    {
        printf("ICMP packets\'s length is less than 8\n");
        return -1;
    }
    /*ICMP类型为ICMP_ECHOREPLY并且为本进程的PID*/
    if( (icmp->icmp_type==ICMP_ECHOREPLY) && (icmp->icmp_id== pid) )    
    {
        struct timeval tv_internel,tv_recv,tv_send;
        /*在发送表格中查找已经发送的包,按照seq*/
        pingm_pakcet* packet = icmp_findpacket(icmp->icmp_seq);
        if(packet == NULL)
            return -1;
        packet->flag = 0;    /*取消标志*/
        tv_send = packet->tv_begin;            /*获取本包的发送时间*/
        gettimeofday(&tv_recv, NULL);        /*读取此时间,计算时间差*/
        tv_internel = icmp_tvsub(tv_recv,tv_send);
        rtt = tv_internel.tv_sec*1000+tv_internel.tv_usec/1000; 
        /*打印结果,包含
        *  ICMP段长度
        *  源IP地址
        *  包的序列号
        *  TTL
        *  时间差
        */
        printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%d ms\n",
            len,
            inet_ntoa(ip->ip_src),
            icmp->icmp_seq,
            ip->ip_ttl,
            rtt);
        
        packet_recv ++;                        /*接收包数量加1*/
    }
    else
    {
        return -1;
    }
    return 0;
}

/*计算时间差time_sub
参数:
    end,接收到的时间
    begin,开始发送的时间
返回值:
    使用的时间
*/
static struct timeval icmp_tvsub(struct timeval end,struct timeval begin)
{
    struct timeval tv;
    /*计算差值*/
    tv.tv_sec = end.tv_sec - begin.tv_sec;
    tv.tv_usec = end.tv_usec - begin.tv_usec;
    /*如果接收时间的usec值小于发送时的usec值,从usec域借位*/
    if(tv.tv_usec < 0)
    {
        tv.tv_sec --;
        tv.tv_usec += 1000000; 
    }
    
    return tv;
}

/*发送ICMP回显请求包*/
static void* icmp_send(void *argv)
{
    /*保存程序开始发送数据的时间*/
    gettimeofday(&tv_begin, NULL);
    while(alive)
    {
        int size = 0;
        struct timeval tv;
        gettimeofday(&tv, NULL);            /*当前包的发送时间*/
        /*在发送包状态数组中找一个空闲位置*/
        pingm_pakcet *packet = icmp_findpacket(-1);
        if(packet)
        {
            packet->seq = packet_send;        /*设置seq*/
            packet->flag = 1;                /*已经使用*/
            gettimeofday( &packet->tv_begin, NULL);    /*发送时间*/
        }
        
        icmp_pack((struct icmp *)send_buff, packet_send, &tv, 64 );
        /*打包数据*/
        size = sendto (rawsock,  send_buff, 64,  0,        /*发送给目的地址*/
            (struct sockaddr *)&dest, sizeof(dest) );
        if(size <0)
        {
            perror("sendto error");
            continue;
        }
        packet_send++;                    /*计数增加*/
        /*每隔1s,发送一个ICMP回显请求包*/
        sleep(1);
    }
}

/*接收ping目的主机的回复*/
static void *icmp_recv(void *argv)
{
    /*轮询等待时间*/
    struct timeval tv;
    tv.tv_usec = 200;
    tv.tv_sec = 0;
    fd_set  readfd;
    /*当没有信号发出一直接收数据*/
    while(alive)
    {
        int ret = 0;
        FD_ZERO(&readfd);
        FD_SET(rawsock, &readfd);
        ret = select(rawsock+1,&readfd, NULL, NULL, &tv);
        switch(ret)
        {
            case -1:
                /*错误发生*/
                break;
            case 0:
                /*超时*/
                break;
            default:
                {
                    /*接收数据*/
                    int size = recv(rawsock, recv_buff,sizeof(recv_buff), 
                    0);
                    if(errno == EINTR)
                    {
                        perror("recvfrom error");
                        continue;
                    }
                    /*解包,并设置相关变量*/
                    ret = icmp_unpack(recv_buff, size);
                    if(ret == -1)
                    {
                        continue;
                    }
                }
                break;
        }
        
    }
}

/*查找一个合适的包位置
*当seq为-1时,表示查找空包
*其他值表示查找seq对应的包*/
static pingm_pakcet *icmp_findpacket(int seq)
{
    int i=0;
    pingm_pakcet *found = NULL;
    /*查找包的位置*/
    if(seq == -1)/*查找空包的位置*/
    {
        for(i = 0;i<128;i++)
        {
            if(pingpacket[i].flag == 0)
            {
                found = &pingpacket[i];
                break;
            }
            
        }
    }
    else if(seq >= 0)/*查找对应seq的包*/
    {
        for(i = 0;i<128;i++)
        {
            if(pingpacket[i].seq == seq)
            {
                found = &pingpacket[i];
                break;
            }
            
        }
    }
    return found;
}

/*打印全部ICMP发送接收统计结果*/
static void icmp_statistics(void)
{       
    long time = (tv_interval.tv_sec * 1000 )+ (tv_interval.tv_usec/1000);
    printf("--- %s ping statistics ---\n",dest_str);    /*目的IP地址*/
    printf("%d packets transmitted, %d received, %d%% packet loss, time %ldms\n",
        packet_send,                                    /*发送*/
        packet_recv,                                      /*接收*/
        (packet_send-packet_recv)*100/packet_send,     /*丢失百分比*/
        time);                                             /*时间*/
}

/*终端信号处理函数SIGINT*/
static void icmp_sigint(int signo)
{
    
    alive = 0;                            /*告诉接收和发送线程结束程序*/    
    gettimeofday(&tv_end, NULL);        /*读取程序结束时间*/    
    tv_interval = icmp_tvsub(tv_end, tv_begin);  /*计算一下总共所用时间*/
    
    return;
}
 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

What’smean

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值