ping 源码,详细解释

#include "stdio.h"
#include "fcntl.h"
#include "errno.h"
#include "signal.h"
#include "sys/types.h"
#include "sys/socket.h"
#include "sys/time.h"
#include "netinet/in.h"
#include "arpa/inet.h"
#include "netdb.h"

#define ICMP_ECHO 8
#define ICMP_ECHOREPLY 0
#define ICMP_HEADSIZE   8
#define IP_HEADSIZE 20
#define MAX(a, b) ((a) > (b)) ? (a) : (b)
#define MIN(a, b) ((a) > (b)) ? (b) : (a)


char *host;
char *prog;
extern errno;
long lSendTime;
u_short seq;
int iTimeOut;
int sock, sent, recvd, max, min, total;
u_long lHostIp;
struct sockaddr_in it; //套接字地址信息
struct timeval now;     //用于计算时间,timeval为系统内定义结构体
int ping();
void stat();

typedef struct tagIpHead   //定义IP报头结构
{
    u_char ip_verlen; //header length
    u_char ip_tos;    //type of service
    u_char ip_len;    //total length
    u_short ip_id;    //identification
    u_short ip_fragoff; //fragment offset field
    u_char ip_ttl;    //time to live
    u_char ip_proto; //protocol
    u_short ip_chksum; //IP checksum
    u_long ip_src_addr; //source address
    u_long ip_dst_addr; //dest address
}IPHEAD;

typedef struct tagIcmpHead //定义Icmp报头结构
{
    u_char icmp_type; //type of message
    u_char icmp_code; //type sub code
    u_short icmp_chksum; //ones complement checksum of struct
    u_short icmp_id;   //标示符
    u_short icmp_seq; //顺序号
    u_char icmp_data[1];
}ICMPHEAD;

/*校验和算法
原理:把被校验的数据16位进行累加,然后取反码,若数据字节长度为奇数,则数据尾部补一个字节的0以凑成偶数.
接收方在计算过程中包含了发送方存在首部中的校验和,因此如果在传输过程中没发生任何差错,那么接收方计算的校验和结果应全为1。如果不全为1(即校验和错误),那么IP就丢弃收到的数据报。
*/
u_short ChkSum(u_short *pIcmpData, int iDataLen)
{
    u_short iSum;
    u_short iOddByte;
    iSum = 0;

    /*把ICMP报头二进制数据以2字节为单位累加起来*/
    while(iDataLen > 1)
    {
        iSum ^= *(pIcmpData++); //^表示异或运算
        iDataLen -= 2;
    }

    /*若ICMP报头为奇数个字节,会剩下最后一字节.把最后一个字节视为一个2字节数据的高字节,这个2字节数据的低字节为0,继续累加*/
    if(iDataLen == 1)
    {
        iOddByte = 0;
        *((u_char *) & iOddByte) = *(u_char *)pIcmpData;
        iSum ^= iOddByte;
    }
    iSum^0xffff;   //继续累加
    return(iSum);
}

/*获取当前时间*/
long time_now()
{
    struct timeval now;    
    long lPassed;
    gettimeofday(&now, 0);
    
    /* tv_sec 秒; tv_usec 微秒 */
    lPassed = now.tv_sec * 1000000 + now.tv_usec; //采用微秒精度
    return lPassed;
}



main(int argc, char **argv)
{
    struct hostent *h;
    char buf[200];
    char dst_host[32];
    int i, namelen;
    IPHEAD *pIpHead;    
    ICMPHEAD *pIcmpHead;
    int normalSize = 0;
    int times = 0;
    if(argc < 2) //输入参数不合要求
    {
        printf("usage: %s [-timeout] host | IP\n", argv[0]);
        exit(0);
    }
    //printf("argc: %d argv: %s", argc, *(argv + 1));
    prog = argv[0];
    host = argc == 2 ? argv[1] : argv[2];
    iTimeOut = argc == 2 ? 1 : atoi(argv[1]);
    
    /*数据发送之前要建立一个套接字(socket),系统调用socket()返回一个套接口描述符,如果出错,则返回-1。*/
    if((sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0)
    {
        perror("socket");
        exit(2);
    }
    
    /*初始化套接字it*/
    bzero(&it, sizeof(it)); //置字节字符串it的前sizeof(it)个字节为零
    it.sin_family = AF_INET; //地址族
    
    /*inet_addr(host)
    每个ip对应一个32位的数,inet_addr把他们倒序过来,再转换为十进制数即为结果.
    */

    if((lHostIp = inet_addr(host)) != INADDR_NONE) //host为32位IP地址,lHostIp为十进制数表示
    {
        it.sin_addr.s_addr = lHostIp; //获取主机IP地址
        strcpy(dst_host, host);   //拷贝二进制四段IP
    }
    else if(h = gethostbyname(host))
    {    
        bcopy(h -> h_addr, &it.sin_addr, h -> h_length);
        //bcopy()函数,将h->h_addr的前h->h_length个字节复制到&it.sin_addr中
        sprintf(dst_host, "%s(%s)", host, inet_ntoa(it.sin_addr));    
    }
    else //IP或主机名错误
    {
        fprintf(stderr, "bad IP or host\n");
        exit(3);
    }


    namelen = sizeof(it);
    i = IP_HEADSIZE + ICMP_HEADSIZE + sizeof(long);
    printf("\npinging %s, send %d bytes\n\n", dst_host, i);
    seq = 0;
    sigset(SIGINT, stat);   //sigset用于修改信号定位
    sigset(SIGALRM, ping);
    alarm(iTimeOut); //alarm()用来设置信号在经过iTimeOut后传送给目前的进程

    ping();

    for(;;)
    {
        register size;
        register u_char ttl;
        register delta;
        register iIpHeadLen;
        size = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *)&it, &namelen); //recvfrom()从(已连接)套接口上接收数据,并捕获数据发送源的地址,it为装有源地址的缓冲区.若无错误发生,recvfrom()返回读入的字节数。如果连接已中止,返回0。否则的话,返回SOCKET_ERROR错误
        if(size == -1)
        {
            //printf("recvrrom failed, size is %d\n", size);
            continue;
        }
        delta = (int)((time_now() - lSendTime)); //计算时间差,即PING所需时间
        pIpHead = (IPHEAD *)buf;
        iIpHeadLen = (int)((pIpHead -> ip_verlen&0xf) << 2);
        normalSize = iIpHeadLen + ICMP_HEADSIZE;
        if(size < normalSize)
        {
        //printf("sizeof iIpHeadLen + ICMP_HEADSIZE error:size is %\n", size);
        continue;
        }
        ttl = pIpHead -> ip_ttl;
        pIcmpHead = (ICMPHEAD *)(buf + iIpHeadLen);
        if(pIcmpHead -> icmp_type != ICMP_ECHOREPLY)
        {
            //printf("ICMP_ECHOREPLY error\n");
            continue;
        }
        if(pIcmpHead -> icmp_id != seq || pIcmpHead -> icmp_seq != seq)
        {
            //printf("icmp_id != seq\n");
            continue;
        }
        sprintf(buf, "icmp_seq = %u bytes = %d ttl = %d",pIcmpHead -> icmp_seq, seq, size, ttl);
        printf("myping reply from %s: %s time=%.3f ms\n",host ,buf, (float)delta / 1000);
        max = MAX(delta,max);
        min = min < delta ? MAX(delta, min) : delta;
        total += delta;
        ++recvd;
        ++seq;
        if(times == 3) //设置ping的次数
        {
            stat();
            break;
        }
        times++;
    }

}

ping()
{
    char buf[200];  
    int iPacketSize;
    int tag = -1;
    ICMPHEAD *pIcmpHead = (ICMPHEAD *)buf;
    pIcmpHead -> icmp_type = ICMP_ECHO;
    pIcmpHead -> icmp_code = 0;
    pIcmpHead -> icmp_id = seq;
    pIcmpHead -> icmp_seq = seq;
    pIcmpHead -> icmp_chksum = 0;
    *((long *)pIcmpHead -> icmp_data) = time_now();
    iPacketSize = ICMP_HEADSIZE + 4;
    pIcmpHead -> icmp_chksum = ChkSum((u_short *)pIcmpHead, iPacketSize); //校验和算法
    lSendTime = time_now();
    tag = sendto(sock, buf, iPacketSize, 0, (struct sockaddr *) & it, sizeof(it)); //发送数据
    if(tag < 0) //发送失败
    {
        printf("\nmy_ping send failed\n");
        exit(6);
    }
    ++sent;
    alarm(iTimeOut);
}


/*显示工作状态*/
void stat()
{
    if(sent)
    {
        printf("\n%s ping statistics summerized by 网络四班-彭华^-^\n", host);
        printf("%d packets sent, %d packets received, %.2f%% lost\n", sent, recvd, (float)(sent-recvd) / (float)sent * 100);
    }    
    if(recvd)
    {
        printf("round_trip min/avg/max: %d/%d/%d ms\n\n", min, total/recvd, max);
    }    
    exit(0);
}
原文地址:https://www.cnblogs.com/mokliu/p/2138278.html