C语言实现PING功能

来源:互联网 发布:程序员试用期转正申请 编辑:程序博客网 时间:2024/06/05 07:13

网络协议分析的一次实验题:ping的原理不再赘述了,直接上代码吧
注:需要ws2_32支持
下载地址:http://download.csdn.net/detail/mass_effect/9867576

#include <iostream>#include <stdio.h>#include <WINSOCK2.H>#include <windows.h>using namespace std;//ICMP回送回应报文类型#define ECHO_REPLY 0//ICMP回送请求报文类型#define ECHO_REQUEST 8//ip首部数据结构struct Ip_Header {    //首部长度和版本,高4位为首部长度,低4位为版本    unsigned char ip_verlen;    //服务类型/区分服务    unsigned char ip_tos;    //总长度    unsigned short ip_total_len;    //标识    unsigned short ip_id;    //标志&分片偏移 高3位为标志    unsigned short ip_fragoff;    //生存时间    unsigned char ip_ttl;    //协议    unsigned char ip_proto;    //校验和    unsigned short ip_cksum;    //源ip地址    unsigned long ip_src_IP;    //目的ip地址    unsigned long ip_dst_IP;};//icmp 回送请求和应答报文数据结构struct Icmp_Header {    //类型    unsigned char icmp_type;    //代码    unsigned char icmp_code;    //校验和    unsigned short icmp_cksum;    //标识    unsigned short icmp_id;    //序列号    unsigned short icmp_seq;    //数据部分,这里使用了时间戳    unsigned long icmp_data;};//计算校验和,注意传入的是unsigned char数组, 所以需要两两合并才进行求和unsigned short checkSum(unsigned char *buffer,int size){    unsigned long cksum = 0;    unsigned long tmp;    while(size>1){        //以16位为单位相加        tmp = *buffer++ << 8;        tmp += *buffer++;        cksum += tmp;        size-=sizeof(unsigned short);    }    if(size) {        //size为奇数的情况        cksum += *(unsigned short*)buffer;    }    //将溢出的部分和低16位相加,高位溢出添加到低位,与通常的补码运算直接丢弃溢出的高位不同    cksum=(cksum >> 16) + (cksum & 0xffff);    //取反返回    return ((unsigned short) ~cksum);}//截取字符串, 得到从offset起的字符串char* subString(char* buffer,int offset) {     for(int i = 0 ; i < offset ; buffer++, i++);     return buffer;}//字节序转换unsigned short host2net4short(unsigned short value) {    return (unsigned short) (((value >> 8) & 0xff) | ((value & 0xff) << 8));}unsigned long host2net4long(unsigned long value) {    return (unsigned long) (((value >> 24) & 0xff) | ((value & 0xff) << 24)) | ((value & 0xff00) << 8) | ((value & 0xff0000) >> 8);}unsigned short net2host4short(unsigned short value) {    return host2net4short(value);}unsigned long net2host4long(unsigned long value) {    return host2net4long(value);}char* userHelp() {    char* buffer = new char[255];    char* command = new char[4];    gets(buffer);    strncpy(command, buffer, 4);    if(!strcmp(command, "ping")) {        return subString(buffer, 5);    }else if(!strcmp(command, "help")) {        printf("输入格式: ping ip地址/域名\n");        return "?";    }else {        printf("您输入的格式错误\n");        return NULL;    }}int main(int argc, char* argv[]) {    int n;    printf("+----------------------Ping小程序----------------------+\n");    printf("| 日期: 2017年6月8日                                   |\n");    printf("+------------------------------------------------------+\n");    printf("| PS: 输入help命令查看帮助                             |\n");    printf("+------------------------------------------------------+\n");    printf("| By:msidolphin                                       |\n");    printf("+------------------------------------------------------+\n");    cout << endl;    cout << endl;    struct sockaddr_in sa;    //socket初始化    WSADATA wsa_data;    if (WSAStartup(MAKEWORD(2,2),&wsa_data) != 0){        //代表失败        return -1;       }    //用户帮助    while(true) {        char* address = "127.0.0.1";        do {            address = userHelp();            if(address == NULL) {                return 0;            }        }while(!strcmp(address, "?"));        HOSTENT *phostent = gethostbyname(address);         if(phostent == NULL) {            printf("您填写的地址有误!");            return 0;        }        //一个域名可能对应多个IP地址        for(n=0 ; phostent->h_addr_list[n] ; n++) {            memcpy(&sa.sin_addr.s_addr, phostent->h_addr_list[n], phostent->h_length);        }        sa.sin_family = AF_INET;        sa.sin_port = htons(0);        //创建原始套接字        //原始套接字可以自行组装数据包(伪装本地 IP,本地 MAC),可以接收本机网卡上所有的数据帧(数据包)。另外,必须在管理员权限下才能使用原始套接字。        SOCKET sock_raw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);        if(sock_raw == INVALID_SOCKET) {            printf("原始套接字创建失败");            return -1;        }        printf("正在 Ping [ %s ] 具有 32 字节的数据:\r\n\r\n", inet_ntoa(sa.sin_addr));        //统计未接收到的回送回应报文数量        int lose_packet = 0;        sockaddr_in addrRecv;        //最大往返时间        int max_time = 0;        //最小往返时间        int min_time = 1000;        //平均往返时间        int avg_time = 0;        //临时变量,用来比较往返时间        int temp_time = 999;        //标记是否接收到数据包        bool flag = false;        Icmp_Header icmp_packet;        //连续发送4个icmp报文        for(int i = 0 ; i < 4 ; ++i) {            //icmp 回送请求报文初始化            icmp_packet.icmp_type = ECHO_REQUEST;            icmp_packet.icmp_code = 0x00;            icmp_packet.icmp_cksum = 0x0000;            icmp_packet.icmp_data = (unsigned long) ::GetTickCount();            icmp_packet.icmp_data = host2net4long(icmp_packet.icmp_data);            //连续发送数据包是处于同一个进程,所以4次封包的进程号是一致的            icmp_packet.icmp_id = (unsigned short) GetCurrentProcessId();            icmp_packet.icmp_id = host2net4short(icmp_packet.icmp_id);            //seqence值用来区分不同的请求            icmp_packet.icmp_seq = (unsigned short) (i+1);            icmp_packet.icmp_seq = host2net4short(icmp_packet.icmp_seq);            unsigned char temp[40];            memset(temp, 0, 40);            //将结构体中的数据拷贝到temp数组中,因为checkSum()函数接收的是unsigned char数组            memcpy(temp, &icmp_packet, 40);            icmp_packet.icmp_cksum = host2net4short(checkSum(temp, 40));            memcpy(temp, &icmp_packet, 40);            //等待1秒再发送下一个包            Sleep(1000);            //发送            sendto(sock_raw, (char*) temp, sizeof(temp), 0, (sockaddr*)&sa, sizeof(sa));            //通过选择模型,设置等待时间            fd_set fd;            FD_ZERO(&fd);            FD_SET(sock_raw, &fd);            //设定超过2秒为超时            timeval tv = {2, 0};            int nResult = select(0, &fd, NULL, NULL, &tv);            if (nResult == 0){                 lose_packet ++;                 printf("请求超时...\n");                 continue;             }            //接收数据包            unsigned char recv_packet[MAXBYTE];            int recv_add_len = sizeof(addrRecv);            recvfrom(sock_raw, (char*) recv_packet, sizeof(recv_packet), 0, (sockaddr *)&addrRecv, &recv_add_len);            //获取ip数据包            Ip_Header *ip_header = (Ip_Header*) recv_packet;            //获取icmp 回送应答报文            Icmp_Header *icmp_header = (Icmp_Header*) (recv_packet + 20);            memcpy(temp, icmp_header, 40);            //验证校验和            if(!checkSum(temp, sizeof(temp))) {                if(icmp_header->icmp_type == ECHO_REPLY) {                    flag = true;                    unsigned long current_time = ::GetTickCount();                    icmp_header->icmp_data = net2host4long(icmp_header->icmp_data);                    //计算发送和接收往返时间                    int interval = current_time - icmp_header->icmp_data - 1000;                    //累计往返时间                    avg_time += interval;                    //得到最大和最小往返时间                    if(i == 0) {                        temp_time = interval;                    }                    if(interval > max_time) {                        max_time = interval;                    }else {                        temp_time = interval;                    }                    if(temp_time < min_time) {                        min_time = temp_time;                    }                    if(interval < 1 && interval >= 0) {                        //如果间隔时间小于1ms,以时间<1ms形式输出                        printf("来自 %s 的回复: 字节=%d 时间<1ms  TTL=%d\n"                            ,inet_ntoa(addrRecv.sin_addr)                            ,sizeof(icmp_header->icmp_data)*8                            ,ip_header->ip_ttl                        );                    }else {                        printf("来自 %s 的回复: 字节=%d 时间=%dms TTL=%d\n"                            ,inet_ntoa(addrRecv.sin_addr)                            ,sizeof(icmp_header->icmp_data)*8                            ,interval                            ,ip_header->ip_ttl                        );                    }                }else {                    lose_packet ++;                    printf("目标不可达...\n");                }            }else {                lose_packet ++;            }         }         cout << endl;         //统计收发信息         printf("%s 的 Ping 统计信息:\n", inet_ntoa(sa.sin_addr));         printf("\t数据包: 已发送 = 4 , 已接收 = %d, 丢失 = %d (%d%%丢失)\n"                ,4 - lose_packet                ,lose_packet                ,(lose_packet)*100 / 4        );         if(flag) {            printf("往返行程的估计时间(以毫秒为单位):\n");            avg_time /= (4 - lose_packet);            printf("\t最短 = %dms, 最长 = %dms, 平均 = %dms\n"                   ,min_time                   ,max_time                   ,avg_time            );         }         cout << endl;    }}

运行结果:
这里写图片描述
不足之处:没有对ICMP差错报告报文进行处理

原创粉丝点击