zoukankan      html  css  js  c++  java
  • ping程序

    ping程序目的是为了测试另一台主机是否可达。该程序发送一份ICMP回显请求(icmp消息类型0x8,ICMP_ECHO)报文给主机,并等待返回ICMP回显应答(消息类型0x0,ICMP_ECHOREPLY)。

    ping -q -w 1 -c 1 www.baidu.com >/dev/null 2>&1
    echo $?
    0

    ping程序还能测出到这台主机的往返时间,以表明该主机离我们“多远”。

    用tcpdump抓取一包ping 8.8.8.8的数据:

    ~$sudo tcpdump -i eth0 icmp -n -v -s0
    tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
    17:41:11.487640 IP (tos 0x0, ttl 64, id 48592, offset 0, flags [DF], proto ICMP (1), length 84)
        192.168.1.10 > 8.8.8.8: ICMP echo request, id 6914, seq 1, length 64
    17:41:11.980277 IP (tos 0x0, ttl 40, id 54159, offset 0, flags [none], proto ICMP (1), length 84)
        8.8.8.8 > 192.168.1.10: ICMP echo reply, id 6914, seq 1, length 64

    输出的第一行包括目的主机的 I P地址,尽管指定的是它的名字( www.baidu.com)。这说明名字已经经过解析器被转换成 I P地址了。我们将在第 1 4章介绍解析器和 D N S。现在,我们发现,如果敲入 p i n g命令,几秒钟过后会在第 1行打印出 I P地址, D N S就是利用这段时间来确定主机名所对应的 I P地址。

    通常,第 1个往返时间值要比其他的大。这是由于目的端的硬件地址不在 A R P高速缓存中的缘故。在发送第一个回显请求之前要发送一个 A R P请求并接收A R P应答,这需要花费几毫秒的时间。

    ICMP回显请求和回显应答报文如图:

    unix系统在实现ping程序时把ICMP报文中的标识符字段置为发送进程的ID号。这样即使在同一台主机上同时运行多个ping程序,ping程序也可以识别返回的信息。

    序列号从0开始,每发送一个新的回显请求就加1。ping程序打印出返回的每个分组的序列号,允许我们查看是否有分组丢失、失序或重复。IP是一种最好的数据报传递服务,因此这三个条件都有可能发生。

    ping程序通过在ICMP报文数据中存放发送请求的时间值来计算往返时间。当应答返回时,用当前时间减去存放在ICMP报文中的时间值,即往返时间。

    ping程序初版

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <fcntl.h>
    #include <sys/types.h>
    #include <stdbool.h>
    #include <sys/time.h>
    #include <sys/wait.h>
    #include <signal.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <netinet/ip_icmp.h>
    #include <netdb.h>
    #include <arpa/inet.h>
    #include <errno.h>
    
    #define PACKET_SIZE        4096
    #define MAX_WAIT_TIME    1
    #define ICMP_HEADSIZE    8
    
    struct timeval tvrecv;
    struct sockaddr_in dest_addr, recv_addr;
    int sockfd;
    pid_t pid;
    char packetsend[PACKET_SIZE];
    char packetrecv[PACKET_SIZE];
    int seq_no = 0;
    int err_ret = 0;
    
    unsigned short cal_chksum(unsigned short *addr, int len);
    int pack(int pkt_no, char *packet);
    int unpack(int cur_seq, char *buf, int len);
    int send_packet(int pkt_no, char *packet);
    int recv_packet(int pkt_no, char *packet);
    void tv_sub(struct timeval *out, struct timeval *in);
    void close_socket();
    void print_hex(const char *buf, int len);
    
    void print_hex(const char *buf, int len)
    {
        int i = 0;
    
        printf("------------------------------
    ");
        for(i = 0; i < len;){
            printf("0x%.2X ", (unsigned char)buf[i]);
            i++;
            if(i%4 == 0){
                printf("
    ");
            }
        }
        printf("------------------------------
    ");
    }
    
    void alarm_task(int signo)
    {
        printf("------------------ping-------%d-----------------------
    ", seq_no);
        if(send_packet(seq_no, packetsend)<0){
            printf("[NetStatus] error : send_packet
    ");
            err_ret = -1;
        } else {
            if(recv_packet(seq_no, packetrecv)<0){
                printf("[NetStatus] error : recv_packet
    ");
                err_ret = -1;
            } else {
                seq_no++;
                alarm(MAX_WAIT_TIME);
            }
        }
    }
    
    int main(int argc, char **argv)
    {
        struct hostent *host = NULL;
        struct protoent *protocol = NULL;
    
    #ifdef _USE_DNS
        char hostname[32];
        sprintf(hostname, "%s", "www.baidu.com");
        bzero(&dest_addr, sizeof(dest_addr));
        dest_addr.sin_family = AF_INET;
    
        if(host=gethostbyname(hostname) == NULL){
            printf("[NetStatus] error : can't get serverhost info!
    ");    
            return -1;
        }
        
        bcopy((char *)host->h_addr, (char*)&dest_addr.sin_addr, host->h_length);
    #else
        dest_addr.sin_addr.s_addr = inet_addr("8.8.8.8");
    #endif
    
        if((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0){
            printf("[NetStatus] error : socket
    ");
            return -1;
        }
    
        pid = getpid();
        printf("pid=[%d]
    ", pid);
    
        signal(SIGALRM, alarm_task);
        raise(SIGALRM);
    
        while(1){
            sleep(1);
            if(err_ret == -1){
                close_socket();
                return -1;
            }    
        }
    
        close_socket();
        return 0;
    }
    
    int send_packet(int pkt_no, char * packet)
    {
        int packetsize;
        packetsize = pack(pkt_no, packet);
        if(sendto(sockfd, packet, packetsize, 0, 
            (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0){
            printf("[NetStatus] error : sendto error
    ");
            return -1;
        }
        printf("send packet [%d]:
    ", packetsize);
        print_hex(packet, packetsize);
        return 1;
    }
    
    int recv_packet(int pkt_no, char *packet)
    {
        int n, fromlen;
        fd_set rfds;
        struct timeval tv;
        int ret = 0;
        
        FD_ZERO(&rfds);
        FD_SET(sockfd, &rfds);
        fromlen = sizeof(recv_addr);
    
        tv.tv_sec = 1;
        tv.tv_usec = 0;
    
        while(1){
            ret = select(sockfd+1, &rfds, NULL, NULL, &tv);
            if(ret > 0){
                if(FD_ISSET(sockfd, &rfds)){
                    if((n = recvfrom(sockfd, packet, PACKET_SIZE, 0, 
                        (struct sockaddr *)(&recv_addr), (socklen_t *)&fromlen)) < 0){
                        if(errno == EINTR)
                            continue;
                        perror("recvform error");
                        return -1;
                    }
                }
    
                gettimeofday(&tvrecv, NULL);
                if(unpack(pkt_no, packet, n) < 0){
                    //continue;
                    return -1;
                }
                return 1;
            } else if(ret == 0){
                return 0;
            } else {
                return -1;
            }
        }
    }
    
    int pack(int pkt_no, char *packet)
    {
        int packsize;
        struct icmp *icmp;
        struct timeval *tv;
    
        icmp = (struct icmp*)packet;
        icmp->icmp_type = ICMP_ECHO;
        icmp->icmp_code = 0;
        icmp->icmp_cksum = 0;
        icmp->icmp_seq = pkt_no;
        icmp->icmp_id = pid;
        packsize = ICMP_HEADSIZE + sizeof(struct timeval);
        tv = (struct timeval*)icmp->icmp_data;
        gettimeofday(tv, NULL);
        icmp->icmp_cksum = cal_chksum((unsigned short*)icmp, packsize);
        return packsize;
    }
    
    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;
    }
    
    int unpack(int cur_seq, char *buf, int len)
    {
        int iphdrlen;
        struct ip *ip;
        struct icmp *icmp;
        struct timeval *ptv;
        double rtt = 0.0;
    
        ip = (struct ip *)buf;
        iphdrlen=ip->ip_hl<<2;
        icmp = (struct icmp*)(buf + iphdrlen);
        len -= iphdrlen;
        if(len < 8){
            return -1;
        }
    
        ptv = (struct timeval *)icmp->icmp_data;
        if((icmp->icmp_type == ICMP_ECHOREPLY) 
            && (icmp->icmp_id == pid) 
            && (icmp->icmp_seq == cur_seq)) {
            printf("receive packet %d:
    ", len);
            print_hex((char *)icmp, len);
            tv_sub(&tvrecv, ptv);
            rtt = tvrecv.tv_sec * 1000 + tvrecv.tv_usec/1000.0;
            printf("count %.3f ms
    ", rtt);
            return 0;
        } else {
            return -1;
        }
    }
    
    void tv_sub(struct timeval *out, struct timeval *in)
    {
        if((out->tv_usec -= in->tv_usec) < 0){
            --out->tv_sec;
            out->tv_usec += 1000000;
        }
        out->tv_sec -= in->tv_sec;
    }
    
    void close_socket()
    {
        close(sockfd);
        sockfd = 0;
    }

    运行:

    ~$sudo ./a.out 
    pid=[8719]
    ------------------ping-------0-----------------------
    send packet [16]:
    ------------------------------
    0x08 0x00 0x97 0x3E 
    0x0F 0x22 0x00 0x00 
    0xE4 0xDE 0x4F 0x59 
    0x18 0x67 0x05 0x00 
    ------------------------------
    ------------------ping-------1-----------------------
    send packet [16]:
    ------------------------------
    0x08 0x00 0xEC 0x38 
    0x0F 0x22 0x01 0x00 
    0xE6 0xDE 0x4F 0x59 
    0xC0 0x6C 0x05 0x00 
    ------------------------------
    receive packet 16:
    ------------------------------
    0x00 0x00 0xF4 0x38 
    0x0F 0x22 0x01 0x00 
    0xE6 0xDE 0x4F 0x59 
    0xC0 0x6C 0x05 0x00 
    ------------------------------
    count 285.443 ms
    ------------------ping-------2-----------------------
    ......

    参考:

    1. linux环境下C编程指南

    2. TCP/IP详解卷一

    3. linux下判断网络是否连接

  • 相关阅读:
    C#的编码规范中文版(www.idesign.net Author: Juval Lowy)
    [经典推荐]事半功倍系列之javascript(二)
    [推荐]让SQL跑得更快
    SortedList 用法
    在C#中应用哈希表(Hashtable)
    SVN配置安装
    sql事务处理回滚及保留点transaction,ROLLBACK
    启用Service Broker
    BLOB
    WebServiceBinding属性
  • 原文地址:https://www.cnblogs.com/embedded-linux/p/7072729.html
Copyright © 2011-2022 走看看