您的位置:首页 > 编程语言 > C语言/C++

C++ 实现 ping 功能&& 域名(URL)解析实际 IP地址

2016-10-31 22:55 441 查看

1、简述

一般情况下,我们想知道在当前电脑设备环境下,某一个网址能不能访问,最简单的方法是win + R 键 ,输入cmd,召唤cmd命令行程序,然后直接用ping命令 + 网址 来看返回的结果,那么我们是通过windows提供的工具来得到相应的结果,那我们能不能自己用代码实现呢?

答案肯定是可以的,在我们输入ping命令后,cmd.exe解析后就进行相应的操作,而我们就是去实现这个操作,下面就讲述一下如何用代码实现。

2、代码之路

我们先来看看用windows提供的cmd命令行程序通过ping命令得到的结果。

这里我们 ping 了 百度的网址,下图为 ping 了 两次得到的结果,我们发现返回来的实际IP地址不一样,这里也很容易理解,因为我们在浏览器访问一个网址,是经过DNS域名解析服务器根据主机名解析得到对应的IP地址,而百度在各个地方有多台服务器,所以这里返回的IP并不止一个。(这里简单提一下,详细请百度一下O(∩_∩)O)



我们可以直接ping + 域名(也就是网址) , 也可以 ping + IP(这里也就是域名对应的实际IP地址),我们看到,通过ping www.baidu.com ,我们可以看到实际的IP地址,这里我们也可以直接ping + IP得到返回结果。



我们也看到了通过ping可以得到域名对应的实际IP地址,那我们也可以通过代码来实现,并且能够返回域名对应的全部IP地址

域名解析实际IP地址

BOOL  GetRealIpByDomainName(char *szHost, char szIp[50][100], int *nCount)
{
WSADATA wsaData;
HOSTENT *pHostEnt;
int nAdapter = 0;
struct sockaddr_in   sAddr;
if (WSAStartup(0x0101, &wsaData))
{
printf(" gethostbyname error for host:\n");
return FALSE;
}

pHostEnt = gethostbyname(szHost);
if (pHostEnt)
{
while (pHostEnt->h_addr_list[nAdapter])
{
memcpy(&sAddr.sin_addr.s_addr, pHostEnt->h_addr_list[nAdapter], pHostEnt->h_length);

sprintf_s(szIp[nAdapter], "%s", inet_ntoa(sAddr.sin_addr));

nAdapter++;
}

*nCount = nAdapter;
}
else
{
DWORD  dwError = GetLastError();
*nCount = 0;
}
WSACleanup();
return TRUE;
}


//测试代码
void main()
{
// 返回的域名对应实际IP的个数
int nIpCount = 0;
// 返回的域名对应实际I列表
char szIpList[50][100];
// 域名
char szDomain[256] = { 0 };
char szIp[1024] = { 0 };
strcpy_s(szDomain, "www.baidu.com");
GetIpByDomainName(szDomain, szIpList, &nIpCount);

for (int i = 0; i < nIpCount; i++)
{
strcat_s(szIp, szIpList[i]);
strcat_s(szIp, "\t");
}
printf("DomainName : %s \n", szDomain);
printf("Real IPList : %s", szIp);
}


测试结果



可以看出结果与用cmd命令行程序得到的结果一致。

这里 www.baidu.com 对应两个IP。这里是我这个地区得到的IP,其他地区以cmd命令行程序 ping的结果为准。ping的前提下是本地没有配置host文件,可以在 C:\Windows\System32\drivers\etc\hosts 这个文件中查看是否将某些域名配置成了固定的IP地址。

hosts简介

hosts文件其作用就是将一些常用的网址域名与其对应的IP地址建立一个关联“数据库”,当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从Hosts文件中寻找对应的IP地址,一旦找到,系统会立即打开对应网页,如果没有找到,则系统会再将网址提交DNS域名解析服务器进行IP地址的解析。

hosts小应用

hosts文件就相当于本地网址的第一层过滤,可以把一些不想访问的网址进行过滤(比如恶意弹出的广告和网页游戏等),达到一层保护的效果

比如在hosts文件中添加 www.baidu.com 127.0.0.1 , 这样我们就访问不了百度网址了。

前几天也在某一篇文章中看到通过修改hosts文件配置可以过滤播放器的一些视频广告,具体做法详情百度一下哈 (^o^)/~。

C++ 实现 ping 功能

以下代码实现了我们的 cmd命令行程序中的ping 功能,但是只能够ping + IP 地址,不能够进行ping + 域名 ,需要配合上面的代码进行使用

parseurl.h

#pragma once

#include <winsock2.h>
#include<stdlib.h>

#pragma comment(lib, "Ws2_32.lib")

#define DEF_PACKET_SIZE 32
#define ECHO_REQUEST 8
#define ECHO_REPLY 0

struct IPHeader
{
BYTE m_byVerHLen; //4位版本+4位首部长度
BYTE m_byTOS; //服务类型
USHORT m_usTotalLen; //总长度
USHORT m_usID; //标识
USHORT m_usFlagFragOffset; //3位标志+13位片偏移
BYTE m_byTTL; //TTL
BYTE m_byProtocol; //协议
USHORT m_usHChecksum; //首部检验和
ULONG m_ulSrcIP; //源IP地址
ULONG m_ulDestIP; //目的IP地址
};

struct ICMPHeader
{
BYTE m_byType; //类型
BYTE m_byCode; //代码
USHORT m_usChecksum; //检验和
USHORT m_usID; //标识符
USHORT m_usSeq; //序号
ULONG m_ulTimeStamp; //时间戳(非标准ICMP头部)
};

struct PingReply
{
USHORT m_usSeq;
DWORD m_dwRoundTripTime;
DWORD m_dwBytes;
DWORD m_dwTTL;
};

class ParseUrl
{
public:
ParseUrl();
~ParseUrl();
BOOL Ping(DWORD dwDestIP, PingReply *pPingReply = NULL, DWORD dwTimeout = 2000);
BOOL Ping(char *szDestIP, PingReply *pPingReply = NULL, DWORD dwTimeout = 2000);
private:
BOOL PingCore(DWORD dwDestIP, PingReply *pPingReply, DWORD dwTimeout);
USHORT CalCheckSum(USHORT *pBuffer, int nSize);
ULONG GetTickCountCalibrate();
private:
SOCKET m_sockRaw;
WSAEVENT m_event;
USHORT m_usCurrentProcID;
char *m_szICMPData;
BOOL m_bIsInitSucc;
private:
static USHORT s_usPacketSeq;
};


parseurl.cpp

#include "stdafx.h"
#include "parseurl.h"

USHORT ParseUrl::s_usPacketSeq = 0;

ParseUrl::ParseUrl() :
m_szICMPData(NULL),
m_bIsInitSucc(FALSE)
{
WSADATA WSAData;
WSAStartup(MAKEWORD(1, 1), &WSAData);

m_event = WSACreateEvent();
m_usCurrentProcID = (USHORT)GetCurrentProcessId();

if ((m_sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, 0)) != SOCKET_ERROR)
{
WSAEventSelect(m_sockRaw, m_event, FD_READ);
m_bIsInitSucc = TRUE;

m_szICMPData = (char*)malloc(DEF_PACKET_SIZE + sizeof(ICMPHeader));

if (m_szICMPData == NULL)
{
m_bIsInitSucc = FALSE;
}
}
}

ParseUrl::~ParseUrl()
{
WSACleanup();

if (NULL != m_szICMPData)
{
free(m_szICMPData);
m_szICMPData = NULL;
}
}

BOOL ParseUrl::Ping(DWORD dwDestIP, PingReply *pPingReply, DWORD dwTimeout)
{
return PingCore(dwDestIP, pPingReply, dwTimeout);
}

BOOL ParseUrl::Ping(char *szDestIP, PingReply *pPingReply, DWORD dwTimeout)
{
if (NULL != szDestIP)
{
return PingCore(inet_addr(szDestIP), pPingReply, dwTimeout);
}
return FALSE;
}

BOOL ParseUrl::PingCore(DWORD dwDestIP, PingReply *pPingReply, DWORD dwTimeout)
{
//判断初始化是否成功
if (!m_bIsInitSucc)
{
return FALSE;
}

//配置SOCKET
sockaddr_in sockaddrDest;
sockaddrDest.sin_family = AF_INET;
sockaddrDest.sin_addr.s_addr = dwDestIP;
int nSockaddrDestSize = sizeof(sockaddrDest);

//构建ICMP包
int nICMPDataSize = DEF_PACKET_SIZE + sizeof(ICMPHeader);
ULONG ulSendTimestamp = GetTickCountCalibrate();
USHORT usSeq = ++s_usPacketSeq;
memset(m_szICMPData, 0, nICMPDataSize);
ICMPHeader *pICMPHeader = (ICMPHeader*)m_szICMPData;
pICMPHeader->m_byType = ECHO_REQUEST;
pICMPHeader->m_byCode = 0;
pICMPHeader->m_usID = m_usCurrentProcID;
pICMPHeader->m_usSeq = usSeq;
pICMPHeader->m_ulTimeStamp = ulSendTimestamp;
pICMPHeader->m_usChecksum = CalCheckSum((USHORT*)m_szICMPData, nICMPDataSize);

//发送ICMP报文
if (sendto(m_sockRaw, m_szICMPData, nICMPDataSize, 0, (struct sockaddr*)&sockaddrDest, nSockaddrDestSize) == SOCKET_ERROR)
{
return FALSE;
}

//判断是否需要接收相应报文
if (pPingReply == NULL)
{
return TRUE;
}

char recvbuf[256] = { "\0" };
while (TRUE)
{
//接收响应报文
if (WSAWaitForMultipleEvents(1, &m_event, FALSE, 100, FALSE) != WSA_WAIT_TIMEOUT)
{
WSANETWORKEVENTS netEvent;
WSAEnumNetworkEvents(m_sockRaw, m_event, &netEvent);

if (netEvent.lNetworkEvents & FD_READ)
{
ULONG nRecvTimestamp = GetTickCountCalibrate();
int nPacketSize = recvfrom(m_sockRaw, recvbuf, 256, 0, (struct sockaddr*)&sockaddrDest, &nSockaddrDestSize);
if (nPacketSize != SOCKET_ERROR)
{
IPHeader *pIPHeader = (IPHeader*)recvbuf;
USHORT usIPHeaderLen = (USHORT)((pIPHeader->m_byVerHLen & 0x0f) * 4);
ICMPHeader *pICMPHeader = (ICMPHeader*)(recvbuf + usIPHeaderLen);

if (pICMPHeader->m_usID == m_usCurrentProcID //是当前进程发出的报文
&& pICMPHeader->m_byType == ECHO_REPLY //是ICMP响应报文
&& pICMPHeader->m_usSeq == usSeq //是本次请求报文的响应报文
)
{
pPingReply->m_usSeq = usSeq;
pPingReply->m_dwRoundTripTime = nRecvTimestamp - pICMPHeader->m_ulTimeStamp;
pPingReply->m_dwBytes = nPacketSize - usIPHeaderLen - sizeof(ICMPHeader);
pPingReply->m_dwTTL = pIPHeader->m_byTTL;
return TRUE;
}
}
}
}
//超时
if (GetTickCountCalibrate() - ulSendTimestamp >= dwTimeout)
{
return FALSE;
}
}
}

USHORT ParseUrl::CalCheckSum(USHORT *pBuffer, int nSize)
{
unsigned long ulCheckSum = 0;
while (nSize > 1)
{
ulCheckSum += *pBuffer++;
nSize -= sizeof(USHORT);
}
if (nSize)
{
ulCheckSum += *(UCHAR*)pBuffer;
}

ulCheckSum = (ulCheckSum >> 16) + (ulCheckSum & 0xffff);
ulCheckSum += (ulCheckSum >> 16);

return (USHORT)(~ulCheckSum);
}

ULONG ParseUrl::GetTickCountCalibrate()
{
static ULONG s_ulFirstCallTick = 0;
static LONGLONG s_ullFirstCallTickMS = 0;

SYSTEMTIME systemtime;
FILETIME filetime;
GetLocalTime(&systemtime);
SystemTimeToFileTime(&systemtime, &filetime);
LARGE_INTEGER liCurrentTime;
liCurrentTime.HighPart = filetime.dwHighDateTime;
liCurrentTime.LowPart = filetime.dwLowDateTime;
LONGLONG llCurrentTimeMS = liCurrentTime.QuadPart / 10000;

if (s_ulFirstCallTick == 0)
{
s_ulFirstCallTick = GetTickCount();
}
if (s_ullFirstCallTickMS == 0)
{
s_ullFirstCallTickMS = llCurrentTimeMS;
}

return s_ulFirstCallTick + (ULONG)(llCurrentTimeMS - s_ullFirstCallTickMS);
}


测试ping + Ip 代码

void main()
{
ParseUrl objParseUrl;
char *szDestIP = "112.80.248.73";
PingReply reply;

printf("Pinging %s with %d bytes of data:\n\n", szDestIP, DEF_PACKET_SIZE);
for (int i = 0; i < 4; i++)
{
objParseUrl.Ping(szDestIP, &reply);
printf("Reply from %s: bytes=%ld time=%ldms TTL=%ld\n", szDestIP, reply.m_dwBytes, reply.m_dwRoundTripTime, reply.m_dwTTL);
Sleep(500);
}
}


测试结果





测试 结合域名解析实际IP地址 GetIpByDomainName方法

void pingIp(char Ip[100])
{
ParseUrl objParseUrl;
PingReply reply;

printf("\nPinging %s with %d bytes of data:\n\n", Ip, DEF_PACKET_SIZE);
for (int i = 0; i < 4; i++)
{
objParseUrl.Ping(Ip, &reply);
printf("Reply from %s: bytes=%ld time=%ldms TTL=%ld\n", Ip, reply.m_dwBytes, reply.m_dwRoundTripTime, reply.m_dwTTL);
Sleep(500);
}
}

void main()
{
int   nIpCount = 0;
char  szIpList[50][100];
char  szDomain[256] = { 0 };
char  szIp[1024] = { 0 };
strcpy_s(szDomain, "www.baidu.com");
GetIpByDomainName(szDomain, szIpList, &nIpCount);

printf("域名 : %s \n", szDomain);

for (int i = 0; i < nIpCount; i++)
{
pingIp(szIpList[i]);
strcat_s(szIp, szIpList[i]);
strcat_s(szIp, "\t");
}

printf("\n域名解析IP列表 : %s \n\n", szIp);
}


测试结果



注意

以上主要是用到了这个方法

BOOL ParseUrl::Ping(char *szDestIP, PingReply *pPingReply, DWORD dwTimeout)

这个方法中有三个参数,分别是ping的IP ,ping的返回结果以及ping超时时间,这里注意一下dwTimeout的值,在程序中我们默认dwTimeout为2s,即在2s内如果ping失败会不停地ping下去,直到2s结束,如果ping成功了直接返回,所以在ping的过程中调用Ping这个方法可能需要的时间不一样,如果很快就能ping通那么将立即返回,如果ping不通或者需要经过几次尝试才能ping通,那么就需要耗费一定的时间。所以,我们在ping一个IP的时候并不知道能否ping通,而如果我们程序在ping的时候不能阻塞很长时间,这就需要修改dwTimeout的值。

在最近工作中,我需要在界面打开时进行多个IP的ping操作,如果ping操作时间过长,界面将会卡住一段时间,所以这里就需要缩短ping超时的时间,但是并不是一味地将dwTimeout的值缩短越小越好,有时候明明能够ping通,却因为当前网络环境不好,同时dwTimeout的值设置非常小,导致可能ping不通,而dwTimeout的时间越长得到的结果可能更精确一些。所以这些都需要根据当前使用情况进行取舍,对dwTimeout的值进行合理设置

以上即为本篇文章的内容,通过两段代码结合实现了cmd命令行程序的ping功能,这里我们也可以用来判断当前设备有没有联网,在某些环境需要判断当前设备是否连接互联网是非常有必要的,在Qt QTcpSocket 对连接服务器中断的不同情况进行判定 这篇文章中,我们叙述了提供几种不同的方法检测客户端与服务器断开的几种方法,其中也提到了使用ping方法来判断本机是否联网,其中使用了Qt封装的库,直接调用即可,方便快捷,但是如果不是Qt程序,可以利用此篇文章提供的C++方法。

同时在Qt QTcpSocket 对连接服务器中断的不同情况进行判定 中也提到了一种更直接的方法,通过windows提供的IsNetworkAlive判断本地是否有网络连接,但是不能判断是否能访问互联网,这就需要通过ping 方法来判断了。可以直接在我贡献的资源中下载源码,进行使用。

代码下载

C++ 实现 ping 功能&& 域名(URL)解析实际 IP地址
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: