这是一个老话题了,但是我刚学会...

我们的目的是实现这么个东西:

之所以用红框框一下是因为,从baidu.com到123.125.114.144的过程是DNS解析,我们暂时先实现ping的部分。

基础知识

ping的过程是向目的IP发送一个type=8的ICMP响应请求报文,目标主机收到这个报文之后,会向源IP(发送方,我)回复一个type=0的ICMP响应应答报文。

那上面的字节、往访时间、TTL之类的信息又是从哪来的呢?这取决于IP和ICMP的头部。

IP头部:

头部内容有点多,我们关心的只有以下几个:

IHL:首部长度。因为IP的头部不是定长的,所以需要这个信息进行IP包的解析,从而找到Data字段的起始点。

    另外注意这个IHL是以4个字节为单位的,所以首部实际长度是IHL*4字节。

Time to Live:生存时间,这个就是TTL了。

Data:这部分是IP包的数据,也就是ICMP的报文内容。

ICMP响应请求/应答报文头部:

Type:类型,type=8表示响应请求报文,type=0表示响应应答报文。

Code:代码,与type组合,表示具体的信息,参考这里

Checksum:检验和,这个是整个ICMP报文的检验和,包括Type、Code、...、Data。

Identifier:标识符,这个一般填入本进程的标识符。

Sequence Number:序号

Data:数据部分

上面是标准的ICMP报文,一般而言,统计ping的往返时间的做法是,在ICMP报文的Data区域写入4个字节的时间戳。

在收到应答报文时,取出这个时间戳与当前的时间对比即可。

代码实现

 #pragma once

 #include <windows.h>

 //这里需要导入库 Ws2_32.lib,在不同的IDE下可能不太一样
//#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 CPing
{
public:
CPing();
~CPing();
BOOL Ping(DWORD dwDestIP, PingReply *pPingReply = NULL, DWORD dwTimeout = );
BOOL Ping(char *szDestIP, PingReply *pPingReply = NULL, DWORD dwTimeout = );
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;
};

[ping.h]

 #include "ping.h"

 USHORT CPing::s_usPacketSeq = ;

 CPing::CPing() :
m_szICMPData(NULL),
m_bIsInitSucc(FALSE)
{
WSADATA WSAData;
WSAStartup(MAKEWORD(, ), &WSAData); m_event = WSACreateEvent();
m_usCurrentProcID = (USHORT)GetCurrentProcessId(); if ((m_sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, , )) != 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;
}
}
} CPing::~CPing()
{
WSACleanup(); if (NULL != m_szICMPData)
{
free(m_szICMPData);
m_szICMPData = NULL;
}
} BOOL CPing::Ping(DWORD dwDestIP, PingReply *pPingReply, DWORD dwTimeout)
{
return PingCore(dwDestIP, pPingReply, dwTimeout);
} BOOL CPing::Ping(char *szDestIP, PingReply *pPingReply, DWORD dwTimeout)
{
if (NULL != szDestIP)
{
return PingCore(inet_addr(szDestIP), pPingReply, dwTimeout);
}
return FALSE;
} BOOL CPing::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, , nICMPDataSize);
ICMPHeader *pICMPHeader = (ICMPHeader*)m_szICMPData;
pICMPHeader->m_byType = ECHO_REQUEST;
pICMPHeader->m_byCode = ;
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, , (struct sockaddr*)&sockaddrDest, nSockaddrDestSize) == SOCKET_ERROR)
{
return FALSE;
} //判断是否需要接收相应报文
if (pPingReply == NULL)
{
return TRUE;
} char recvbuf[] = {"\0"};
while (TRUE)
{
//接收响应报文
if (WSAWaitForMultipleEvents(, &m_event, FALSE, , 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, , , (struct sockaddr*)&sockaddrDest, &nSockaddrDestSize);
if (nPacketSize != SOCKET_ERROR)
{
IPHeader *pIPHeader = (IPHeader*)recvbuf;
USHORT usIPHeaderLen = (USHORT)((pIPHeader->m_byVerHLen & 0x0f) * );
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 CPing::CalCheckSum(USHORT *pBuffer, int nSize)
{
unsigned long ulCheckSum=;
while(nSize > )
{
ulCheckSum += *pBuffer++;
nSize -= sizeof(USHORT);
}
if(nSize )
{
ulCheckSum += *(UCHAR*)pBuffer;
} ulCheckSum = (ulCheckSum >> ) + (ulCheckSum & 0xffff);
ulCheckSum += (ulCheckSum >>); return (USHORT)(~ulCheckSum);
} ULONG CPing::GetTickCountCalibrate()
{
static ULONG s_ulFirstCallTick = ;
static LONGLONG s_ullFirstCallTickMS = ; SYSTEMTIME systemtime;
FILETIME filetime;
GetLocalTime(&systemtime);
SystemTimeToFileTime(&systemtime, &filetime);
LARGE_INTEGER liCurrentTime;
liCurrentTime.HighPart = filetime.dwHighDateTime;
liCurrentTime.LowPart = filetime.dwLowDateTime;
LONGLONG llCurrentTimeMS = liCurrentTime.QuadPart / ; if (s_ulFirstCallTick == )
{
s_ulFirstCallTick = GetTickCount();
}
if (s_ullFirstCallTickMS == )
{
s_ullFirstCallTickMS = llCurrentTimeMS;
} return s_ulFirstCallTick + (ULONG)(llCurrentTimeMS - s_ullFirstCallTickMS);
}

[ping.cpp]

 #include <windows.h>
#include <stdio.h>
#include "ping.h" int main(void)
{
CPing objPing; char *szDestIP = "123.125.114.144";
PingReply reply; printf("Pinging %s with %d bytes of data:\n", szDestIP, DEF_PACKET_SIZE);
while (TRUE)
{
objPing.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();
} return ;
}

执行结果

附录:如何计算检验和

ICMP中检验和的计算算法为:

1、将检验和字段置为0

2、把需校验的数据看成以16位为单位的数字组成,依次进行二进制反码求和

3、把得到的结果存入检验和字段中

所谓二进制反码求和,就是:

1、将源数据转成反码

2、0+0=0   0+1=1   1+1=0进1

3、若最高位相加后产生进位,则最后得到的结果要加1

在实际实现的过程中,比较常见的代码写法是:

1、将检验和字段置为0

2、把需校验的数据看成以16位为单位的数字组成,依次进行求和,并存到32位的整型中

3、把求和结果中的高16位(进位)加到低16位上,如果还有进位,重复第3步[实际上,这一步最多会执行2次]

4、将这个32位的整型按位取反,并强制转换为16位整型(截断)后返回

[转载请保留本文地址:http://www.cnblogs.com/goagent/p/4078940.html] 

C++实现Ping的更多相关文章

  1. 解决WINDOWS防火墙开启后Ping不通

    WINDOWS系统由于安全考虑,当开启防火墙时,默认不允许外主机对其进行ping功能,即别的电脑ping不通本机.别的主机ping不通本机是因为本机的防火墙关闭了ICMP回显功能,只要把这回显功能打开 ...

  2. Linux不能上网ping:unknown host问题怎么解决?

    Linux不能上网提示ping:unknown host 检查步骤 Linux系统跟windows平台有所不同的是,为了更好的做网络服务应用.Linux下多用于网络服务器,而且操作界面是字符界面.对于 ...

  3. Docker的ubuntu镜像安装的容器无ifconfig和ping命令的解决

    Docker的Ubuntu镜像安装的容器无ifconfig命令和ping命令 解决: apt-get update apt install net-tools       # ifconfig apt ...

  4. 使用shell脚本实现ping对应IP所对应的人名

    #!/bin/bash a=(张三 李四 王五 赵六) ..} do . $((${i}+)) >dev/>&;then ))"号"${a[${i}]}&quo ...

  5. 2、实现不同子网之间的信息交流(互相可以PING通)

    一.环境: 二个不同的虚拟子网 VMnet1: 192.168.155.0/24 VMnet8: 192.168.170.0/24 编辑 --> 虚拟网络编辑器 (查看自己的子网,相应修改就行) ...

  6. Linux虚拟机突然网络不能用了但是主机能ping㣈

    虚拟ping主机时出现: linux network is unreachable 搞了好久搞不定,之前都是好的 突然这样了. 解决办法: 第一步: "虚拟机设置"中的" ...

  7. ping环回地址和ping主机地址的区别

    ping127.0.0.1和ping本机的过程是不一样的ip输出函数先检查地址是不是环回地址1.如果是环回地址 直接交给环回驱动程序处理 返回ip输入函数2.如果不是环回地址 检查是不是广播或者多播地 ...

  8. 批量 ping 测试脚本

    是否会使用 vpn 工作,已经成为魔法师和麻瓜之间最重要的区分.使用 vpn 工作,也产生了其它一些奇奇怪怪的问题,比如,选择 vpn 服务器. 你要测试哪个 vpn 离你最近. 所以,就有了下面的脚 ...

  9. 从C++实现Ping开始说起

    在C++中实现ping功能,并不难.但真正了解ping是需要花费一番功夫的. Ping功能是在ICMP基础上实现的.IP协议并不是一个可靠的协议,它不保证数据被送达,那么,保证数据送达的工作应该由其他 ...

  10. cnentos中进行bond网卡配置,一切配置无问题,就是ping不通宿主机

    服务器网口绑定   1. ifcfg-bond0   DEVICE=bond0 ONBOOT=yes IPADDR=192.168.100.64 NETMASK=255.255.255.0   2. ...

随机推荐

  1. js表单动态添加数据并提交

    情景1:已经存在form对象了,动态为form增加对象并提交 function formAppendSubmit(){ var myform=$('#newArticleForm'); //得到for ...

  2. 《ASP.NET Web API 2框架揭秘》样章(PDF版本)

    <ASP.NET Web API 2框架揭秘>(详情请见<新作<ASP.NET Web API 2框架揭秘>正式出版>)以实例演示的方式介绍了很多与ASP.NET ...

  3. Razor Engine,实现代码生成器的又一件利器

    Razor Engine,之前仅仅是ASP.NET MVC的一种View引擎,目前已经完全成为一种可以独立使用的模版引擎,并且已经成为了CodePlex上一个开源的项目(http://razoreng ...

  4. Android sendevent/getevent 用法

    前言:最近在弄一个模拟用户点击屏幕的功能,并且不需要用户点击屏幕,后台自动给输入框赋值,类似pc端按键精灵的功能,在网上找了很多人的资料,写的都不是太详细,一知半解.还要自己慢慢研究.于是我这边写一篇 ...

  5. Visual Studio常用小技巧一:代码段+快捷键+插件=效率

    用了visual studio 5年多,也该给自己做下备忘录了.每次进新的组换新的电脑,安装自己熟悉的环境又得重新配置,不做些备忘老会忘记一些东西.工具用的好,效率自然翻倍. 1,代码段 在Visua ...

  6. 读《linux内核完全注释》的FAQ

    以下只是个人看了<linux内核完全注释>的一点理解,如果有错误,欢迎指正! 1 eip中保存的地址是逻辑地址.线性地址还是物理地址? 这个应该要分情况.eip保存的是下一条要执行的指令地 ...

  7. Oracle数据库全球化

    一.自定义数据库和各个会话的语言相关行为 1.1 全球化功能 1.2 使用全球化支持功能 二.使用数据库和NLS字符集 2.1 语言排序与选择 2.2 Locale Builder 2.3 使用时区 ...

  8. spring笔记--依赖注入之针对不同类型变量的几种注入方式

    控制反转和依赖注入讲的都是一个概念,只不过是站在了不同的角度,所谓的依赖注入: 是指在运行期,由外部容器动态地将依赖对象注入到组件中.当spring容器启动后,spring容器初始化,创建并管理bea ...

  9. 在Linux下的中断方式读取按键驱动程序

    // 在Linux下的中断方式读取按键驱动程序 //包含外部中断 休眠 加入poll机制 // 采用异步通知的方式 // 驱动程序发 ---> app接收 (通过kill_fasync()发送) ...

  10. TCP滑动窗口机制的简洁模型

    模型图: 我们可以大概看一下上图的模型: 首先是AB之间三次握手建立TCP连接.在报文的交互过程中,A将自己的缓冲区大小(窗口大小)3发送给B,B同理,这样双方就知道了对端的窗口大小. A开始发送数据 ...