c++实现判断局域网内机器是否在线

路由追踪(Tracert)

​ Tracert 程序关键是对 IP 头部生存时间(time to live)TTL 字段的使用,向目地主机发送一个 ICMP 回显请求消息,初始时 TTL 等于 1,这样当该数据报抵达途中的第一个路由器 时,TTL 的值就被减为 0,导致发生超时错误,因此该路由生成一份 ICMP 超时差错报文返回给源主机。随后,主机将数据报的 TTL 值递增 1,以便 IP 报能传送到下一个路由器,并由下一个路由器生成 ICMP 超时差错报文返回给源主机。

​ 不断重复这个过程,直到数据报达到最终的目地主机,此时目地主机将返回 ICMP 回显应答消息。这样,源主机只需对返回的每一份 ICMP 报文进行解析处理,就可以掌握数据报从源主机到达目地主机途中所经过的路由信息。

对Tracert的应用

​ Tracert通过利用IP 头部的生存时间(time to live)TTL 字段来测试机器是否在线,我们对每一个IP地址发送一个 ICMP 回显请求消息,ICMP每一次转发路由其生存时间便会减少,对返回的每一份 ICMP 报文进行解析处理,就可以掌握数据报从源主机到达目地主机途中所经过的路由信息,对其返回的时间进行判断是否超时,进而来判断是否目标ip机器在线。

​ 在本局域网中,通过对多个机器的 IPV4 地址的查询,找到每一个机器的IPV4 地址的规律,进而找出每一台机器的IPV4地址,进而进行操作,为方便查询以及缩短机器状态确定时间,本程序特地将跳转路由最大次数以及等待时间进行调整。

代码实现

构造数据结构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;  

接收数据并对数据进行解包并进行判断

while (1) {
                //接收数据
                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;
                        //输出 IP 地址
                       // cout << 'zaixian' << '\t' << inet_ntoa(DecodeResult.dwIPaddr) << endl;
                        break;
                    }
                } else if (WSAGetLastError() == WSAETIMEDOUT) //接收超时,输出*号
                {
                    cout << " *" << '\t' << "Request timed out." << endl;
                    break;
                } else {
                    break;
                }
            }

完整代码

//
// Created by 29273 on 2021-01-04.
//
#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;
}

// 起始ip: 10.1.12.100
string i2s(int &num) {
    string str;
    stringstream ss;
    ss << num;
    ss >> str;
    return str;
}

int main() {
    //初始化 Windows sockets 网络环境
    WSADATA wsa;
    WSAStartup(MAKEWORD(2, 2), &wsa);
    const char *IpAddress;
    string IpA;
    cout<<"****************************************************************"<<endl;
    cout<<"   花费时间       机器ip          机器号         状态          *"<<endl;
    cout<<"****************************************************************"<<endl;
    for (int i = 113; i < 229; i++) {
        IpA = "10.1.12." + i2s(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;
            }
        }
   //     cout << "Tracing route to " << IpAddress << " with a maximum of 30 hops.\n" << endl;
        //填充目地端 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);
        //超时时间
        int iTimeout = 300;
        //接收超时
        setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char *) &iTimeout, sizeof(iTimeout));
        //发送超时
        setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char *) &iTimeout, sizeof(iTimeout));
        //构造 ICMP 回显请求消息,并以 TTL 递增的顺序发送报文
        //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; //回显应答超时时间
        const int DEF_MAX_HOP = 5; //最大跳站数
        //填充 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 (1) {
                //接收数据
                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;
                        //输出 IP 地址
                       // cout << 'zaixian' << '\t' << inet_ntoa(DecodeResult.dwIPaddr) << endl;
                        break;
                    }
                } else if (WSAGetLastError() == WSAETIMEDOUT) //接收超时,输出*号
                {
                    cout << " *" << '\t' << "Request timed out." << endl;
                    break;
                } else {
                    break;
                }
            }
            iTTL++; //递增 TTL 值
        }
        if (WSAGetLastError() == WSAETIMEDOUT){
    cout<<"   >500ms      "<<IpA<<"          "<<i<<"         不在线      "<<endl;
    cout<<"****************************************************************"<<endl;
                }else{
    cout<<"       "<<IpA<<"          "<<i<<"        在线"<<endl;
    cout<<"****************************************************************"<<endl;

                }
    }
     WSACleanup();
    return 0;
}

运行结果部分截图
在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值