今天接到需求要实现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个字节的时间戳。

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

Ping程序实现步骤

  1. 创建类型为SOCK_RAW的一个套接字,同时设定协议IPPROTO_ICMP。
  2. 创建并初始化ICMP头。
  3. 调用sendto或WSASendto,将ICMP请求发给远程主机。
  4. 调用recvfrom或WSARecvfrom,以接收任何ICMP响应。

ping.h

#pragma once//在默认windows.h会包含winsock.h,当你包含winsock2.h就会冲突,因此在包含windows.h前需要定义一个宏,#define WIN32_LEAN_AND_MEAN ;去除winsock.h
//要么将#include <winsock2.h>放在#include<windows.h>前面或者直接去掉#include<windows.h>

#include <winsock2.h>
#pragma comment(lib, "WS2_32")    // 链接到WS2_32.lib#define DEF_PACKET_SIZE 32
#define ECHO_REQUEST 8
#define ECHO_REPLY 0struct IPHeader
{BYTE m_byVerHLen; //4位版本+4位首部长度BYTE m_byTOS; //服务类型USHORT m_usTotalLen; //总长度USHORT m_usID; //标识USHORT m_usFlagFragOffset; //3位标志+13位片偏移BYTE m_byTTL; //TTLBYTE 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 = 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;
};

ping.cpp

#include "ping.h"
#include <iostream>
USHORT CPing::s_usPacketSeq = 0;CPing::CPing() :m_szICMPData(NULL),m_bIsInitSucc(FALSE)
{WSADATA WSAData;//WSAStartup(MAKEWORD(2, 2), &WSAData);if (WSAStartup(MAKEWORD(1, 1), &WSAData) != 0){/*如果初始化不成功则报错,GetLastError()返回发生的错误信息*/printf("WSAStartup() failed: %d\n", GetLastError());return;}m_event = WSACreateEvent();m_usCurrentProcID = (USHORT)GetCurrentProcessId();//setsockopt(m_sockRaw);/*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;}}*/m_sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, 0);if (m_sockRaw == INVALID_SOCKET){std::cerr << "WSASocket() failed:" << WSAGetLastError ()<< std::endl;  //10013 以一种访问权限不允许的方式做了一个访问套接字的尝试。
    }else{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, 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 CPing::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 CPing::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);
}

main.cpp

#include <winsock2.h>
#include <stdio.h>
#include "ping.h"int main(void)
{CPing objPing;char *szDestIP = "127.0.0.1";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=%d time=%ldms TTL=%ld\n", szDestIP, reply.m_dwBytes, reply.m_dwRoundTripTime, reply.m_dwTTL);Sleep(500);}return 0;
}

结果:

附录:如何计算检验和

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位整型(截断)后返回

其中也遇到了很多问题,头文件包含,WSAStartup函数初始化失败。。。

主要参考:

http://www.cnblogs.com/goagent/p/4078940.html

http://blog.sina.com.cn/s/blog_5cf5e7c401014fvq.html

http://blog.csdn.net/segen_jaa/article/details/7569727

C++实现ping功能相关推荐

  1. windows开启ping功能

    方法/步骤 1.直接定位到控制面板中,选择[windows防火墙] 2.在windows防火墙页面左侧的列表中找到[高级设置]菜单 3.进入后在左侧找到"入站规则"点击后在中间部分 ...

  2. Linux系统实现ICMP ping功能,并计算时延

    <Linux实现ICMP PING代码> <C语言实现ICMP协议,并进行PING测试> <本文源代码下载> 目录 源代码 icmpping.c icmpping. ...

  3. java ping 实现的_java实现ping功能

    一.纯Java实现ICMP的ping命令 import java.io.*; import java.net.*; import java.nio.channels.*; import java.ut ...

  4. FPGA纯verilog实现UDP通信,三速网自协商仲裁,动态ARP和Ping功能,提供工程源码和技术支持

    目录 1.前言 2.我这里已有的UDP方案 3.UDP详细设计方案 MAC层发送 MAC发送模式 ARP发送 IP层发送 IP发送模式 UDP发送 MAC层接收 ARP接收 IP层接收 UDP接收 S ...

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

    1.简述 一般情况下,我们想知道在当前电脑设备环境下,某一个网址能不能访问,最简单的方法是win + R 键 ,输入cmd,召唤cmd命令行程序,然后直接用ping命令 + 网址 来看返回的结果,那么 ...

  6. Shell脚本实现 ping功能

    Shell 脚本实现 ip ping 功能 具体代码: #!/bin/bash # ========================================================= ...

  7. 方法:如何解决用MFC实现的ping功能中把目标主机不可到达的当成ping通的问题...

    http://www.cnblogs.com/xuesongshu/ 网上查到的资料能实现ping功能,但是都有一个问题,它只检测是否存在错误,而不检测ICMP数据包是哪个机器回复的,这样造成一种错误 ...

  8. 方法:如何解决用MFC实现的ping功能中把目标主机不可到达的当成ping通的问题

    转载请注明来源: http://www.cnblogs.com/xuesongshu/ 网上查到的资料能实现ping功能,但是都有一个问题,它只检测是否存在错误,而不检测ICMP数据包是哪个机器回复的 ...

  9. C语言实现ping功能(查看设备联网状态)

    本文ping功能是利用gethostbyname()函数来实现的. gethostbyname()函数说明--用域名或主机名获取IP地址 包含头文件 #include <netdb.h> ...

  10. LINUX IP如何禁止被PING通,在Linux中如何开启和禁止系统的ping功能?

    这个非常简单,只要配置一下icmp_echo_ignore_all这个文件就行,当为0的时候,开启ping功能,当为1的时候,禁止ping功能,下面我简单介绍一下实现过程: 1.首先,允许ping功能 ...

最新文章

  1. 在Android设备部署PyTorch模型
  2. c 最大子序列和_算法总结:左神class8—跳台阶+最长递增公共子序列
  3. Blazor Day
  4. python基本对象类型
  5. 时间复杂度为O(n)的计数排序算法
  6. Python中pyserial库
  7. java 自定义泛型方法_Java中自定义泛型方法的使用
  8. C语言中puts跟printf的区别
  9. u盘启动 联想一体机_Lenovo消费台式机与一体机预装Windows 8改装Windows 7的解决方案...
  10. R for LC+cohort
  11. 【RAII】RAII 技术(内存安全解决技术/自动化解锁技术)
  12. 【附源码】计算机毕业设计SSM天气预报查询管理系统
  13. SpringCloud(9)— Elasticsearch聚合和自动补全
  14. 密码打马赛克已经不安全了!这款开源的去 “马赛克” 工具一秒还原
  15. 从0到1 CTFer成功之路》XSS---学习笔记
  16. [.NET][ASP.NET MVC 5 网站开发之美]书籍内容介绍及pdf下载
  17. 如何打开 QT助手
  18. cad相贯展开图lisp_cad怎么画相贯线? cad相贯线的画法
  19. 用ATL ActiveX 绘制任意平面函数的曲线
  20. 中国监管的出拳,矿工们何去何从?

热门文章

  1. html中php判断语句,html条件判断(html判断语句)
  2. linux thread model
  3. CSS3JavaScript 仿京东加入购物车特效
  4. 选好库存管理软件,让你的仓库松口气!
  5. DirectX实现视频播放
  6. 转:一个计算机专业毕业的银行员工工作感受
  7. 图像处理之matlab中meshgrid函数用法详解
  8. Linux下的I2S驱动学习
  9. 情人节送礼荒?荣耀30Pro流光幻境了解一下
  10. Google Talk与MSN互通