Tracert 与 Ping 程序设计与实现(2024)

发布时间:2024年01月07日

1.题目描述

了解 Tracert 程序的实现原理,并调试通过。然后参考 Tracert 程序和计算机网络教材 4.4.2 节, 计算机网络 课程设计指导书 2 编写一个 Ping 程序,并能测试本局域网的所有机器是否在线,运行界面如下图所示的 QuickPing 程序。


2.程序Demo

aaf753f838634837989a1fc8fbb5e2bf.png


3.参考代码

#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <string>
#include "sstream"

using namespace std;
#pragma comment(lib, "Ws2_32.lib")
//IP 报头
typedef struct {
    unsigned char hdr_len: 4;       //4 位头部长度
    unsigned char version: 4;       //4 位版本号
    unsigned char tos;              //8 位服务类型
    unsigned short total_len;       //16位总长度
    unsigned short identifier;      //16位标识符
    unsigned short frag_and_flags;  //3 位标志加 13 位片偏移
    unsigned char ttl;              //8 位生存时间
    unsigned char protocol;         //8 位上层协议号
    unsigned short checksum;        //16位校验和
    unsigned long sourceIP;         //32位源 IP 地址
    unsigned long destIP;           //32位目的 IP 地址
} IP_HEADER;
//ICMP 报头
typedef struct {
    BYTE type; //8 位类型字段
    BYTE code; //8 位代码字段
    USHORT cksum; //16 位校验和
    USHORT id; //16 位标识符
    USHORT seq; //16 位序列号
} ICMP_HEADER;
//报文解码结构
typedef struct {
    USHORT usSeqNo; //序列号
    DWORD dwRoundTripTime; //往返时间
    in_addr dwIPaddr; //返回报文的 IP 地址
} DECODE_RESULT;

//计算网际校验和函数
USHORT checksum(USHORT *pBuf, int iSize) {
    unsigned long cksum = 0;
    while (iSize > 1) {
        cksum += *pBuf++;
        iSize -= sizeof(USHORT);
    }
    if (iSize) {
        cksum += *(UCHAR *) pBuf;
    }
    cksum = (cksum >> 16) + (cksum & 0xffff);
    cksum += (cksum >> 16);
    return (USHORT) (~cksum);
}

//对数据包进行解码
BOOL DecodeIcmpResponse(char *pBuf, int iPacketSize, DECODE_RESULT &DecodeResult, BYTE ICMP_ECHO_REPLY, BYTE ICMP_TIMEOUT) {
    //检查数据报大小的合法性
    IP_HEADER *pIpHdr = (IP_HEADER *) pBuf;
    int iIpHdrLen = pIpHdr->hdr_len * 4;
    if (iPacketSize < (int) (iIpHdrLen + sizeof(ICMP_HEADER))) return FALSE;
    //根据 ICMP 报文类型提取 ID 字段和序列号字段
    ICMP_HEADER *pIcmpHdr = (ICMP_HEADER *) (pBuf + iIpHdrLen);
    USHORT usID, usSquNo;
    if (pIcmpHdr->type == ICMP_ECHO_REPLY) //ICMP 回显应答报文
    {
        usID = pIcmpHdr->id; //报文 ID
        usSquNo = pIcmpHdr->seq; //报文序列号
    } else if (pIcmpHdr->type == ICMP_TIMEOUT)//ICMP 超时差错报文
    {
        char *pInnerIpHdr = pBuf + iIpHdrLen + sizeof(ICMP_HEADER); //载荷中的 IP 头
        int iInnerIPHdrLen = ((IP_HEADER *) pInnerIpHdr)->hdr_len * 4; //载荷中的 IP 头长
        ICMP_HEADER *pInnerIcmpHdr = (ICMP_HEADER *) (pInnerIpHdr + iInnerIPHdrLen);//载荷中的 ICMP头
        usID = pInnerIcmpHdr->id; //报文 ID
        usSquNo = pInnerIcmpHdr->seq; //序列号
    } else {
        return false;
    }
    //检查 ID 和序列号以确定收到期待数据报
    if (usID != (USHORT) GetCurrentProcessId() || usSquNo != DecodeResult.usSeqNo) {
        return false;
    }
    //记录 IP 地址并计算往返时间
    DecodeResult.dwIPaddr.s_addr = pIpHdr->sourceIP;
    DecodeResult.dwRoundTripTime = GetTickCount() - DecodeResult.dwRoundTripTime;
    //处理正确收到的 ICMP 数据报
    if (pIcmpHdr->type == ICMP_ECHO_REPLY || pIcmpHdr->type == ICMP_TIMEOUT) {
        //输出往返时间信息
        if (DecodeResult.dwRoundTripTime)
            cout << " " << DecodeResult.dwRoundTripTime << "ms" << flush;
        else
            cout << " " << "<1ms" << flush;
    }
    return true;
}

// 数字转String
string num2str(int &num) {
    string str;
    stringstream ss;
    ss << num;
    ss >> str;
    return str;
}

//提取IP
string GetIP(string ip) {
    char separator = '.';
    int i = 0, count = 3;
    // Temporary string used to split the string.
    string s;
    while (ip[i] != '\0') {
        if (ip[i] != separator) {
            s += ip[i];
        } else {
            s += ip[i];
            count--;
        }
        if (count == 0) {
            break;
        }
        i++;
    }
    return s;
}

int main() {
    cout<<" *======Tracert 与 Ping 程序设计与实现======* "<<endl;
    //定义=====================================================================
    int iTimeout = 10;                    //超时时间
    const int DEF_MAX_HOP = 1;             //检查次数
    BOOLEAN Show_All = false;              //是否输出所有信息
    string Scan_ip;
    //ICMP 类型字段
    const BYTE ICMP_ECHO_REQUEST = 8;      //请求回显
    const BYTE ICMP_ECHO_REPLY = 0;        //回显应答
    const BYTE ICMP_TIMEOUT = 11;          //传输超时

    //其他常量定义
    const int DEF_ICMP_DATA_SIZE = 32;     //ICMP 报文默认数据字段长度
    const int MAX_ICMP_PACKET_SIZE = 1024; //ICMP 报文最大长度(包括报头)
    const DWORD DEF_ICMP_TIMEOUT = 100;    //回显应答超时时间
    int OnlineNum = 0;
    //=======================================================================

    cout << "请输入路由器IP:";
    cin >> Scan_ip;

    //初始化 Windows sockets 网络环境
    WSADATA wsa;
    WSAStartup(MAKEWORD(2, 2), &wsa);
    const char *IpAddress;
    string IpA;

    cout <<endl<<" 开始扫描:"<<endl;
    cout << "----------------------------------------------------------------" << endl;
    cout << "     耗时         机器IP           序号           状态          |" << endl;
    cout << "----------------------------------------------------------------" << endl;

    for (int i = 1; i < 254; i++) {
        IpA = GetIP(Scan_ip)+ num2str(i);
        IpAddress = IpA.c_str();
        // cout << IpA << endl;
        //得到 IP 地址
        u_long ulDestIP = inet_addr(IpAddress);
        //转换不成功时按域名解析
        if (ulDestIP == INADDR_NONE) {
            hostent *pHostent = gethostbyname(IpAddress);
            if (pHostent) {
                ulDestIP = (*(in_addr *) pHostent->h_addr).s_addr;
            } else {
                cout << "输入的 IP 地址或域名无效!" << endl;
                WSACleanup();
                return 0;
            }
        }
        //填充目地端 socket 地址
        sockaddr_in destSockAddr;
        ZeroMemory(&destSockAddr, sizeof(sockaddr_in));
        destSockAddr.sin_family = AF_INET;
        destSockAddr.sin_addr.s_addr = ulDestIP;
        //创建原始套接字
        SOCKET sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED);
        //接收超时
        setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char *) &iTimeout, sizeof(iTimeout));
        //发送超时
        setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char *) &iTimeout, sizeof(iTimeout));
        //填充 ICMP 报文中每次发送时不变的字段
        char IcmpSendBuf[sizeof(ICMP_HEADER) + DEF_ICMP_DATA_SIZE];//发送缓冲区
        memset(IcmpSendBuf, 0, sizeof(IcmpSendBuf)); //初始化发送缓冲区
        char IcmpRecvBuf[MAX_ICMP_PACKET_SIZE]; //接收缓冲区
        memset(IcmpRecvBuf, 0, sizeof(IcmpRecvBuf)); //初始化接收缓冲区
        ICMP_HEADER *pIcmpHeader = (ICMP_HEADER *) IcmpSendBuf;
        pIcmpHeader->type = ICMP_ECHO_REQUEST; //类型为请求回显
        pIcmpHeader->code = 0; //代码字段为 0
        pIcmpHeader->id = (USHORT) GetCurrentProcessId(); //ID 字段为当前进程号
        memset(IcmpSendBuf + sizeof(ICMP_HEADER), 'E', DEF_ICMP_DATA_SIZE);//数据字段
        USHORT usSeqNo = 0; //ICMP 报文序列号
        int iTTL = 1; //TTL 初始值为 1
        BOOL bReachDestHost = FALSE; //循环退出标志
        int iMaxHot = DEF_MAX_HOP; //循环的最大次数
        DECODE_RESULT DecodeResult; //传递给报文解码函数的结构化参数


        while (!bReachDestHost && iMaxHot--) {
            //设置 IP 报头的 TTL 字段
            setsockopt(sockRaw, IPPROTO_IP, IP_TTL, (char *) &iTTL, sizeof(iTTL));
            //  cout << iTTL << flush; //输出当前序号
            //填充 ICMP 报文中每次发送变化的字段
            ((ICMP_HEADER *) IcmpSendBuf)->cksum = 0; //校验和先置为 0
            ((ICMP_HEADER *) IcmpSendBuf)->seq = htons(usSeqNo++); //填充序列号
            ((ICMP_HEADER *) IcmpSendBuf)->cksum = checksum((USHORT *) IcmpSendBuf,
                                                            sizeof(ICMP_HEADER) + DEF_ICMP_DATA_SIZE); //计算校验和
            //记录序列号和当前时间
            DecodeResult.usSeqNo = ((ICMP_HEADER *) IcmpSendBuf)->seq; //当前序号
            DecodeResult.dwRoundTripTime = GetTickCount(); //当前时间
            //发送 TCP 回显请求信息
            sendto(sockRaw, IcmpSendBuf, sizeof(IcmpSendBuf), 0, (sockaddr *) &destSockAddr, sizeof(destSockAddr));
            //接收 ICMP 差错报文并进行解析处理
            sockaddr_in from; //对端 socket 地址
            int iFromLen = sizeof(from); //地址结构大小
            int iReadDataLen; //接收数据长度
            while (true) {
                //接收数据
                iReadDataLen = recvfrom(sockRaw, IcmpRecvBuf, MAX_ICMP_PACKET_SIZE, 0, (sockaddr *) &from, &
                        iFromLen);
                if (iReadDataLen != SOCKET_ERROR)//有数据到达
                {
                    //对数据包进行解码
                    if (DecodeIcmpResponse(IcmpRecvBuf, iReadDataLen, DecodeResult, ICMP_ECHO_REPLY, ICMP_TIMEOUT)) {
                        //到达目的地,退出循环
                        if (DecodeResult.dwIPaddr.s_addr == destSockAddr.sin_addr.s_addr)
                            bReachDestHost = true;
                        break;
                    }
                } else if (WSAGetLastError() == WSAETIMEDOUT & Show_All) //接收超时,输出*号
                {
                    cout << '\t' << "请求超时 " << DEF_MAX_HOP - iMaxHot << "次" << endl;
                    break;
                } else {
                    break;
                }
            }
            iTTL++; //递增 TTL 值
        }
        if (WSAGetLastError() != WSAETIMEDOUT) {
            cout << "         " << IpA << "            " << i << "            在线" << endl;
            OnlineNum++;
            if (Show_All)
                cout << "================================================================" << endl;

        } else if (WSAGetLastError() == WSAETIMEDOUT & Show_All) {
            cout << "         " << IpA << "             " << i << "           离线" << endl;
            cout << "================================================================" << endl;
        }
    }

    cout << "================================================================" << endl;
    cout << endl << "检测到 " << OnlineNum << " 台设备在线" << endl<< endl;
    system("pause");
    WSACleanup();
    return 0;
}

4.导入ws2_32库到Clion?:

导入ws2_32库到Clion项目-CSDN博客

?2024 HNUST计算机网络课程设计-(????)?芒果酱-参考文章

(代码可以参考,?? ? ? ? ?? 但同学们要认真编写哦)
-------------------------------------------------------------------------
1、网络聊天程序的设计与实现
C++ Socket 多线程 网络聊天室 支持用户端双向交流(2023)-CSDN博客
2、Tracert 与 Ping 程序设计与实现
Tracert 与 Ping 程序设计与实现(2024)-CSDN博客
3、滑动窗口协议仿真
滑动窗口协议仿真(2024)-CSDN博客
4、OSPF 路由协议原型系统设计与实现
OSPF 路由协议原型系统设计与实现-CSDN博客
5、基于 IP 多播的网络会议程序
基于 IP 多播的网络会议程序(2024)-CSDN博客
6、编程模拟 NAT 网络地址转换
编程模拟 NAT 网络地址转换(2024)-CSDN博客
7、网络嗅探器的设计与实现
网络嗅探器的设计与实现(2024)-转载-CSDN博客
8、网络报文分析程序的设计与实现
网络报文分析程序的设计与实现(2024)-CSDN博客
9、简单 Web Server 程序的设计与实现
简单 Web Server 程序的设计与实现 (2024)-CSDN博客
10、路由器查表过程模拟

计算机网络 - 路由器查表过程模拟 C++(2024)-CSDN博客

文章来源:https://blog.csdn.net/weixin_46337464/article/details/135424544
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。