博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++实现ping功能<转>
阅读量:7244 次
发布时间:2019-06-29

本文共 8830 字,大约阅读时间需要 29 分钟。

今天接到需求要实现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 
放在#include
前面或者直接去掉#include
#include
#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; //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 = 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 
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 
#include
#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函数初始化失败。。。

主要参考:

 

原贴地址:https://www.cnblogs.com/ranjiewen/p/5704627.html
你可能感兴趣的文章
ASP.NET Core MVC之ViewComponents(视图组件)知多少?
查看>>
在天河二号上对比Julia,Python和R语言
查看>>
Docker容器学习梳理--私有仓库Registry使用
查看>>
arcgis地图服务之 identify 服务
查看>>
取汉子拼音首字母的C#方法
查看>>
C语言 · 求先序遍历
查看>>
java oracle thin 和 oci 连接方式实现多数据库的故障切换
查看>>
使用spring利用HandlerExceptionResolver实现全局异常捕获
查看>>
字符串 上
查看>>
jmeter设置全局变量
查看>>
MySQLi基于面向对象的编程
查看>>
CAAnimation 动画支撑系统
查看>>
读vue-0.6-text-parser.js源码
查看>>
对map进行排序
查看>>
C#趣味程序---百鸡百钱
查看>>
原创:微信小程序页面跳转展示缓冲提示
查看>>
如何设断点????-----使用WinDbg调试SQL Server查询
查看>>
sql 高性能存储过程分页
查看>>
Java -- 异常的捕获及处理 -- 异常类的继承结构
查看>>
外链建设的主要门户渠道
查看>>