• Linux C++ 实现一个简易版的ping (也就是ICMP协议)


    背景:

    想实现一个在没外网的时候就自动重启路由器的功能。

    又不想用ping命令,因为在代码里调用system("ping"); 可能会比较耗时,得单开线程。于是找了个实现ICMP协议的代码。

     

    参考:https://blog.csdn.net/qivan/article/details/7237051

    代码:

    复制代码
    #include <stdio.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <netinet/ip_icmp.h>
    #include <arpa/inet.h>
    #include <sys/types.h>
    #include <sys/time.h>
    #include <unistd.h>
    #include <netdb.h>
    #include <string.h> 
    
    #define PACKET_SIZE     4096
    #define ERROR           0
    #define SUCCESS         1
     
    //效验算法(百度下有注释,但是还是看不太明白)
    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;
    }
    // Ping函数
    int ping( char *ips, int timeout)  
    {  
        struct timeval *tval;        
        int maxfds = 0;  
        fd_set readfds;  
        
        struct sockaddr_in addr;  
        struct sockaddr_in from;  
        // 设定Ip信息  
        bzero(&addr,sizeof(addr));  
        addr.sin_family = AF_INET;  
    
        addr.sin_addr.s_addr = inet_addr(ips);  
    
    #if 1
        int sockfd;  
        // 取得socket  。  如果没加sudo 这里会报错
        sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);  
        if (sockfd < 0)  
        {  
            printf("ip:%s,socket error\n",ips);  
            return ERROR;  
        }  
        
        struct timeval timeo;  
        // 设定TimeOut时间  
        timeo.tv_sec = timeout / 1000;  
        timeo.tv_usec = timeout % 1000;  
        
        if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeo, sizeof(timeo)) == -1)  
        {  
            printf("ip:%s,setsockopt error\n",ips);  
            return ERROR;  
        }  
     
        char sendpacket[PACKET_SIZE];  
        char recvpacket[PACKET_SIZE];  
        // 设定Ping包  
        memset(sendpacket, 0, sizeof(sendpacket));  
        
        pid_t pid;  
        // 取得PID,作为Ping的Sequence ID  
        pid=getpid();  
        
        struct ip *iph;  
        struct icmp *icmp;  
        
      
        icmp=(struct icmp*)sendpacket;  
        icmp->icmp_type=ICMP_ECHO;  //回显请求
        icmp->icmp_code=0;  
        icmp->icmp_cksum=0;  
        icmp->icmp_seq=0;  
        icmp->icmp_id=pid; 
        tval= (struct timeval *)icmp->icmp_data;  
        gettimeofday(tval,NULL);  
        icmp->icmp_cksum=cal_chksum((unsigned short *)icmp,sizeof(struct icmp));  //校验
        
        int n;  
        // 发包 。可以把这个发包挪到循环里面去。 
        n = sendto(sockfd, (char *)&sendpacket, sizeof(struct icmp), 0, (struct sockaddr *)&addr, sizeof(addr));  
        if (n < 1)  
        {  
            printf("ip:%s,sendto error\n",ips);  
            return ERROR;  
        }  
        
        // 接受  
        // 由于可能接受到其他Ping的应答消息,所以这里要用循环  
        while(1)  
        {  
            // 设定TimeOut时间,这次才是真正起作用的  
            FD_ZERO(&readfds);  
            FD_SET(sockfd, &readfds);  
            maxfds = sockfd + 1;  
            n = select(maxfds, &readfds, NULL, NULL, &timeo);  
            if (n <= 0)  
            {              
            printf("ip:%s,Time out error\n",ips);  
                close(sockfd);  
                return ERROR;  
            }  
            
            // 接受  
            memset(recvpacket, 0, sizeof(recvpacket));  
            int fromlen = sizeof(from);  
            n = recvfrom(sockfd, recvpacket, sizeof(recvpacket), 0, (struct sockaddr *)&from, (socklen_t *)&fromlen);  
        printf("recvfrom Len:%d\n",n);
            if (n < 1) 
        {  
            return ERROR;  
            }          
         
            char *from_ip = (char *)inet_ntoa(from.sin_addr);  
            // 判断是否是自己Ping的回复  
            if (strcmp(from_ip,ips) != 0)  
            {  
                printf("NowPingip:%s Fromip:%s NowPingip is not same to Fromip,so ping wrong!\n",ips,from_ip);  
               return ERROR;
            }  
            
            iph = (struct ip *)recvpacket;  
            
            icmp=(struct icmp *)(recvpacket + (iph->ip_hl<<2));  
            
            printf("ip:%s,icmp->icmp_type:%d,icmp->icmp_id:%d\n",ips,icmp->icmp_type,icmp->icmp_id);  
            // 判断Ping回复包的状态  
            if (icmp->icmp_type == ICMP_ECHOREPLY && icmp->icmp_id == pid)   //ICMP_ECHOREPLY回显应答
            {  
                // 正常就退出循环 
            printf("icmp succecss .............  \n");
                break;  
            }  
            else  
            {  
                // 否则继续等  
                continue;  
            }  
        } 
    #endif
        return SUCCESS;
    }
        
    int main()
    {
    #if 1
        char cPing[16];
        printf("Please input ping IP:");
        scanf("%s",cPing);
    #else
        char *cPing = "192.168.1.200";    
    #endif
        if(ping(cPing,10000))
        {
            printf("Ping succeed!\n");
        }
        else
        {
            printf("Ping wrong!\n");
        }
    
        return 0;    
    }
    复制代码

     

    实际效果:

     

    补充说明:

    0)直接用参考链接上的代码时编译不过,不知道是不是因为我用的是cpp,没太深究。

    1)实际使用的时候需要加上sudo,不然在创建套接字那个地方会报错。我还没想好怎么在代码里用sudo,(因为实际项目运行起来是不需要加sudo的)。

     

  • 相关阅读:
    mysql学习--binlog与gtid主从同步
    黑马JVM总结(二十三)
    视频技术在智慧营业厅中的应用:AI识别与智能化转型
    第1章 走近Java【深入理解Java虚拟机:JVM高级特性与最佳实践(第三版)】
    【***操作系统---第五章***】
    【附源码】Python计算机毕业设计农村地产物品交易网站
    机试:成绩排名
    资本赋能,跨境电商Starday逐鹿年终场
    【制作数字人】零门槛通过三维重建技术生成个人三维模型
    回文 马蹄集
  • 原文地址:https://www.cnblogs.com/xcywt/p/16070814.html