ping 127.0.0.1: 這個Ping命令被送到本地計(jì)算機(jī)的IP軟件,該命令永不退出該計(jì)算機(jī)。
localhost是個操作系統(tǒng)的網(wǎng)絡(luò)保留名,是127.0.0.1的別名。
ping www.baidu.com――對這個域名執(zhí)行Ping命令,你的計(jì)算機(jī)必須先將域名轉(zhuǎn)換成IP地址,通常是通過DNS服務(wù)器。
(一)main.cpp文件
// ConsoleApplication3.cpp : 定義控制臺應(yīng)用程序的入口點(diǎn)。 // //程序應(yīng)用: ping命令是向目的主機(jī)發(fā)送ICMP報(bào)文,檢驗(yàn)本地主機(jī)和遠(yuǎn)程的目的主機(jī)是否連接 #include <winsock2.h> #include <stdio.h> #include "ping.h" int main(void) { CPing objPing; //CPing類與對象 char *szDestIP = "127.0.0.1"; //字符IP地址 //127.0.0.1 這個Ping命令被送到本地計(jì)算機(jī)的IP軟件,該命令永不退出該計(jì)算機(jī)。localhost是個操作系統(tǒng)的網(wǎng)絡(luò)保留名,是127.0.0.1的別名。//ping www.baidu.com――對這個域名執(zhí)行Ping命令,你的計(jì)算機(jī)必須先將域名轉(zhuǎn)換成IP地址,通常是通過DNS服務(wù)器。 PingReply reply; //PingReply類與對象 printf("Pinging %s with %d bytes of data:\n", szDestIP, DEF_PACKET_SIZE); //ping 遠(yuǎn)端IP地址,32字節(jié)的數(shù)據(jù) while (TRUE) { objPing.Ping(szDestIP, &reply);//遠(yuǎn)端IP地址不為空(NULL),就返回true表示需要響應(yīng)報(bào)文。遠(yuǎn)端IP空時不需要響應(yīng)報(bào)文false。 printf("Reply from %s: bytes=%d time=%ldms TTL=%ld\n", szDestIP, reply.m_dwBytes, reply.m_dwRoundTripTime, reply.m_dwTTL); //字節(jié)數(shù),時間,TTL生存時間 Sleep(500); } return 0; }
(二)ping.h文件
(1)IP頭結(jié)構(gòu)體:
IHL:首部長度。因?yàn)镮P的頭部不是定長的,所以需要這個信息進(jìn)行IP包的解析,從而找到Data字段的起始點(diǎn)。
另外注意這個IHL是以4個字節(jié)為單位的,所以首部實(shí)際長度是IHL*4字節(jié)。
Time to Live:生存時間,這個就是TTL了。
Data:這部分是IP包的數(shù)據(jù),也就是ICMP的報(bào)文內(nèi)容。
//1.IP頭結(jié)構(gòu)體:20字節(jié) struct IPHeader { BYTE m_byVerHLen; //4位版本Version+4位首部長度IHL 1B BYTE m_byTOS; //服務(wù)類型 1B=16b USHORT m_usTotalLen; //總長度 2B=16b USHORT m_usID; //標(biāo)識 2B=16b USHORT m_usFlagFragOffset; //3位標(biāo)志+13位片偏移=16位 2B=16b BYTE m_byTTL; //TTL 生存時間 1B=8b BYTE m_byProtocol; //協(xié)議 1B=8b 為1時表示是ICMP報(bào)文 USHORT m_usHChecksum; //首部檢驗(yàn)和 2B=16b ULONG m_ulSrcIP; //源IP地址 4B=32b ULONG m_ulDestIP; //目的IP地址 4B=32b };
(2)ICMP頭結(jié)構(gòu)體:
類型Type、代碼Code、校驗(yàn)和、標(biāo)識符、序列號、ICMP數(shù)據(jù)
//ICMP報(bào)文由首部8B和數(shù)據(jù)段組成。 //首部為定長的8個字節(jié),前4個字節(jié)是通用部分(類型1B/代碼1B/校驗(yàn)和2B),后4個字節(jié)隨報(bào)文類型的不同有所差異。 //2.ICMP頭結(jié)構(gòu)體 (標(biāo)準(zhǔn)ICMP頭為8字節(jié)) struct ICMPHeader { BYTE m_byType; //類型 1B type=8表示響應(yīng)請求報(bào)文,type=0表示響應(yīng)應(yīng)答報(bào)文。 BYTE m_byCode; //代碼 1B 與type組合,表示具體的信息 USHORT m_usChecksum; //檢驗(yàn)和 2B 整個ICMP報(bào)文的檢驗(yàn)和,包括Type、Code、...、Data。 USHORT m_usID; //標(biāo)識符 2B=16bits 用于標(biāo)識本進(jìn)程 USHORT m_usSeq; //序列號 2B=16bits 用于判斷回顯應(yīng)答數(shù)據(jù)報(bào)。 ULONG m_ulTimeStamp; //時間戳(非標(biāo)準(zhǔn)ICMP頭部)4B //統(tǒng)計(jì)ping的往返時間的做法是,在ICMP報(bào)文的Data區(qū)域?qū)懭?個字節(jié)的時間戳。在收到應(yīng)答報(bào)文時,取出這個時間戳與當(dāng)前的時間對比即可。 };
(3)ICMP響應(yīng)報(bào)文結(jié)構(gòu)體:
//3.ICMP回答報(bào)文結(jié)構(gòu)體 struct PingReply { USHORT m_usSeq; //ICMP包的序列號 2B DWORD m_dwRoundTripTime;//時間差 4B (word是2字節(jié)) DWORD m_dwBytes; //數(shù)據(jù)所占字節(jié)數(shù) 4B DWORD m_dwTTL; //TTL生存時間 4B };
(4)Ping類及相關(guān)變量的定義:
//類 class CPing { //公共變量 public: CPing(); //構(gòu)造函數(shù) ~CPing(); //析構(gòu)函數(shù) 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);//計(jì)算檢驗(yàn)和 ULONG GetTickCountCalibrate(); //計(jì)算毫秒級別的時間差 private: SOCKET m_sockRaw; //需要監(jiān)聽的socket WSAEVENT m_event; //網(wǎng)絡(luò)事件對象 USHORT m_usCurrentProcID; //當(dāng)前進(jìn)程發(fā)出的報(bào)文 char *m_szICMPData; //ICMP(Internet Control Message Protocol,網(wǎng)際控制報(bào)文協(xié)議) BOOL m_bIsInitSucc; //初始化成功 private: static USHORT s_usPacketSeq; //序列號++(16位=2字節(jié)) };
(三)ping.cpp文件
#include "ping.h" #include <iostream> USHORT CPing::s_usPacketSeq = 0;
(1)char *m_szICMPData; BOOL m_bIsInitSucc;
//::表示類作用域。為避免不同的類有名稱相同的成員而采用作用域的方式進(jìn)行區(qū)分。 CPing::CPing() :m_szICMPData(NULL), m_bIsInitSucc(FALSE) { WSADATA WSAData; //WSAStartup(MAKEWORD(2, 2), &WSAData); if (WSAStartup(MAKEWORD(1, 1), &WSAData) != 0) { printf("WSAStartup() failed: %d\n", GetLastError()); /*如果初始化不成功則報(bào)錯,GetLastError()返回發(fā)生的錯誤信息*/ return; } m_event = WSACreateEvent(); //創(chuàng)建一個網(wǎng)絡(luò)事件對象(HANDLE m_event)。 //返回一個手工重置的事件對象句柄 (HANDLE hEventObject) m_usCurrentProcID = (USHORT)GetCurrentProcessId(); //當(dāng)前進(jìn)程ID /*ICMP必須使用原始套接字進(jìn)行設(shè)計(jì),要手動設(shè)置IP的頭部和ICMP的頭部并行校驗(yàn)*/ m_sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, 0); //創(chuàng)建一個監(jiān)聽的socket (SOCKET m_sockRaw) //當(dāng)IP報(bào)頭中的協(xié)議字段值為1時,就說明這是一個ICMP報(bào)文。 if (m_sockRaw == INVALID_SOCKET) //無效套接字 { std::cerr << "WSASocket() failed:" << WSAGetLastError() << std::endl; //10013 以一種訪問權(quán)限不允許的方式做了一個訪問套接字的嘗試。 } else //是ICMP報(bào)文,令初始化成功,為ICMP數(shù)據(jù)分配內(nèi)存 { WSAEventSelect(m_sockRaw, m_event, FD_READ); //調(diào)用WSAEventSelect將監(jiān)聽的socket(m_sockRaw)與該事件(m_event)進(jìn)行關(guān)聯(lián)。WSAEventSelect(套接字,網(wǎng)絡(luò)事件對象,需要關(guān)注的事件) m_bIsInitSucc = TRUE; m_szICMPData = (char*)malloc(DEF_PACKET_SIZE + sizeof(ICMPHeader)); //為ICMPData分配內(nèi)存 //ping命令的工作原理是:向網(wǎng)絡(luò)上的另一個主機(jī)系統(tǒng)發(fā)送ICMP報(bào)文,如果指定系統(tǒng)得到了報(bào)文,它將把報(bào)文一模一樣地傳回給發(fā)送者 if (m_szICMPData == NULL) { m_bIsInitSucc = FALSE; } } } CPing::~CPing() { WSACleanup(); if (NULL != m_szICMPData) { free(m_szICMPData); m_szICMPData = NULL; } }
(2)BOOL Ping(char *szDestIP, PingReply *pPingReply = NULL, DWORD dwTimeout = 2000);
BOOL CPing::Ping(char *szDestIP, PingReply *pPingReply, DWORD dwTimeout) { if (NULL != szDestIP) //遠(yuǎn)端IP非空 { return PingCore(inet_addr(szDestIP), pPingReply, dwTimeout); //項(xiàng)目 -> 屬性 -> C/C++ > SDL檢查:否。――修改VS配置,告訴它我就要用舊函數(shù)。inet_pton() or InetPton() } return FALSE; //遠(yuǎn)端IP為空,false }
(3)BOOL PingCore(DWORD dwDestIP, PingReply *pPingReply, DWORD dwTimeout);
1.//判斷初始化是否成功
2.//配置套接字SOCKET
3.//構(gòu)建ICMP包
4.//填補(bǔ)ICMP首部
5.//發(fā)送ICMP請求報(bào)文(ping請求)
6.//判斷是否需要接收響應(yīng)報(bào)文
7.//等待網(wǎng)絡(luò)事件接收響應(yīng)報(bào)文
BOOL CPing::PingCore(DWORD dwDestIP, PingReply *pPingReply, DWORD dwTimeout) { //判斷初始化是否成功 if (!m_bIsInitSucc) { return FALSE; //初始化沒成功 } //配置套接字SOCKET sockaddr_in sockaddrDest; //sockaddr_in是internet環(huán)境下的套接字地址。定義在ws2def.h中的結(jié)構(gòu)體 sockaddrDest.sin_family = AF_INET; //地址族(Address Family):網(wǎng)絡(luò)類型 sockaddrDest.sin_addr.s_addr = dwDestIP; //32位IP地址(4字節(jié)) int nSockaddrDestSize = sizeof(sockaddrDest);//大小 //構(gòu)建ICMP包 int nICMPDataSize = DEF_PACKET_SIZE + sizeof(ICMPHeader); //ICMP包長度:32+ICMP頭 ULONG ulSendTimestamp = GetTickCountCalibrate(); //發(fā)送時間戳(毫秒級) USHORT usSeq = ++s_usPacketSeq; //2字節(jié),++0 memset(m_szICMPData, 0, nICMPDataSize); //數(shù)據(jù),長度。memset()初始化內(nèi)存: //memset(* Dst, int Val, size_t Size):將指針變量Dst所指向的前Size字節(jié)的內(nèi)存單元用一個“整數(shù)”Val替換。 //填補(bǔ)ICMP首部 ICMPHeader *pICMPHeader = (ICMPHeader*)m_szICMPData; pICMPHeader->m_byType = ECHO_REQUEST; //類型。Type:8,Code:0:表示回顯請求報(bào)文(ping請求)。 Type:0,Code:0:表示回顯回答報(bào)文(ping應(yīng)答) pICMPHeader->m_byCode = 0; //代碼 pICMPHeader->m_usID = m_usCurrentProcID;//標(biāo)識符 pICMPHeader->m_usSeq = usSeq; //本報(bào)的序列號 pICMPHeader->m_ulTimeStamp = ulSendTimestamp;//發(fā)送時間戳(非標(biāo)準(zhǔn)ICMP頭部) pICMPHeader->m_usChecksum = CalCheckSum((USHORT*)m_szICMPData, nICMPDataSize);//計(jì)算檢驗(yàn)和 //TCP/IP協(xié)議棧使用的校驗(yàn)算法:對16位的數(shù)據(jù)進(jìn)行累加計(jì)算,并返回計(jì)算結(jié)果 //―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― //發(fā)送ICMP請求報(bào)文(ping請求) if (sendto(m_sockRaw, m_szICMPData, nICMPDataSize, 0, (struct sockaddr*)&sockaddrDest, nSockaddrDestSize) == SOCKET_ERROR) { return FALSE; //套接字錯誤 } //判斷是否需要接收響應(yīng)報(bào)文 if (pPingReply == NULL) { return TRUE; } char recvbuf[256] = { "\0" }; //初始化 while (TRUE) //接收響應(yīng)報(bào)文 { //等待網(wǎng)絡(luò)事件 if (WSAWaitForMultipleEvents(1, &m_event, FALSE, 100, FALSE) != WSA_WAIT_TIMEOUT) //等待事件(m_event)。 //WSAWaitForMultipleEvents(事件對象數(shù)組里邊的個數(shù)為1,事件對象數(shù)組,等待類型為FALSE表示事件數(shù)組里至少有一個信號就返回,等待的超時時間為100,當(dāng)系統(tǒng)的執(zhí)行隊(duì)列有I/O例程要執(zhí)行時不返回) { WSANETWORKEVENTS netEvent; WSAEnumNetworkEvents(m_sockRaw, m_event, &netEvent); //m_event = WSACreateEvent(); //事件發(fā)生時,調(diào)用WSAEnumNetworkEvents,檢測指定的socket上的網(wǎng)絡(luò)事件,并將關(guān)聯(lián)信息保存在netEvent中。 //WSAEnumNetworkEvents(SOCKET s,WSAEVENT hEventObject,LPWSANETWORKEVENTS lpNetworkEvents) //當(dāng)調(diào)用WSAEnumNetworkEvents函數(shù)成功后,它會將我們指定的socket和事件對象所關(guān)聯(lián)的網(wǎng)絡(luò)事件的信息保存到LPWSANETWORKEVENTS這個結(jié)構(gòu)體里去,根據(jù)這個結(jié)構(gòu)體我們就可以判斷是否是我們所關(guān)注的網(wǎng)絡(luò)事件已經(jīng)發(fā)生了。 //如果是FD_READ,讀的網(wǎng)絡(luò)事件發(fā)生了,那就調(diào)用recv函數(shù)進(jìn)行操作。 //若是FD_CLOSE,關(guān)閉的網(wǎng)絡(luò)事件發(fā)生了,就調(diào)用closesocket將socket關(guān)掉,在數(shù)組里將其置零等操作。 if (netEvent.lNetworkEvents & FD_READ) //有網(wǎng)絡(luò)事件,且可讀,那就recv收數(shù)啊 { ULONG nRecvTimestamp = GetTickCountCalibrate(); //計(jì)算開始發(fā)送的時間 int nPacketSize = recvfrom(m_sockRaw, recvbuf, 256, 0, (struct sockaddr*)&sockaddrDest, &nSockaddrDestSize); //從遠(yuǎn)端IP處收數(shù)。 if (nPacketSize != SOCKET_ERROR) { //ICMP協(xié)議是IP層的一個協(xié)議,但是由于差錯報(bào)告在發(fā)送給報(bào)文源發(fā)方時可能也要經(jīng)過若干子網(wǎng),因此牽涉到路由選擇等問題,所以ICMP報(bào)文需通過IP協(xié)議來發(fā)送。 //ICMP數(shù)據(jù)報(bào)的數(shù)據(jù)發(fā)送前需要兩級封裝:首先添加ICMP報(bào)頭形成ICMP報(bào)文,再添加IP報(bào)頭形成IP數(shù)據(jù)報(bào)。 //拆解封裝:IP數(shù)據(jù)報(bào)去掉IP報(bào)頭,剩下ICMP報(bào)文,再去掉ICMP報(bào)頭,就是ICMP數(shù)據(jù)報(bào)了。 IPHeader *pIPHeader = (IPHeader*)recvbuf; USHORT usIPHeaderLen = (USHORT)((pIPHeader->m_byVerHLen & 0x0f) * 4); //(4位版本+4位首部長度)&0xf,然后乘4 //IP頭部20字節(jié) ICMPHeader *pICMPHeader = (ICMPHeader*)(recvbuf + usIPHeaderLen); if (pICMPHeader->m_usID == m_usCurrentProcID //是當(dāng)前進(jìn)程發(fā)出的報(bào)文 && pICMPHeader->m_byType == ECHO_REPLY //是響應(yīng)報(bào)文類型(Type=0) && pICMPHeader->m_usSeq == usSeq //是本次請求報(bào)文的響應(yīng)報(bào)文 ) { pPingReply->m_usSeq = usSeq; //ICMP包的序列號 pPingReply->m_dwRoundTripTime = nRecvTimestamp - pICMPHeader->m_ulTimeStamp; //當(dāng)應(yīng)答返回時,用當(dāng)前時間減去存放在ICMP報(bào)文中的時間值,即是往返時間。 pPingReply->m_dwBytes = nPacketSize - usIPHeaderLen - sizeof(ICMPHeader);//數(shù)據(jù)大?。赫鼣?shù)據(jù)-IP頭-ICMP頭 pPingReply->m_dwTTL = pIPHeader->m_byTTL; //TTL生存時間 return TRUE; } } } } //超時 if (GetTickCountCalibrate() - ulSendTimestamp >= dwTimeout)//時間戳(非標(biāo)準(zhǔn)ICMP頭部) { return FALSE; } } }
WSA連網(wǎng)事件流程圖
(4)計(jì)算檢驗(yàn)和
//計(jì)算檢驗(yàn)和 // TCP/IP協(xié)議棧使用的校驗(yàn)算法是比較經(jīng)典的,對16位的數(shù)據(jù)進(jìn)行累加計(jì)算,并返回計(jì)算結(jié)果 USHORT CPing::CalCheckSum(USHORT *pBuffer, int nSize) //CalCheckSum((USHORT*)m_szICMPData, nICMPDataSize); { unsigned long ulCheckSum = 0; //4B=32b //(1)將檢驗(yàn)和字段置為0 while (nSize > 1) { ulCheckSum += *pBuffer++; //數(shù)值相加 //(2)把需校驗(yàn)的數(shù)據(jù)看成以16位為單位的數(shù)字組成,依次進(jìn)行求和,并存到32位的整型中 nSize -= sizeof(USHORT); //長度相減 } if (nSize) { ulCheckSum += *(UCHAR*)pBuffer; } ulCheckSum = (ulCheckSum >> 16) + (ulCheckSum & 0xffff); //高位相加 //(3)把求和結(jié)果中的高16位(進(jìn)位)加到低16位上,如果還有進(jìn)位,重復(fù) ulCheckSum += (ulCheckSum >> 16); //將溢出位加入 return (USHORT)(~ulCheckSum); //返回值:取反 //(4)將這個32位的整型按位取反,并強(qiáng)制轉(zhuǎn)換為16位整型(截?cái)?后返回 } /* 附錄:如何計(jì)算檢驗(yàn)和 ICMP中檢驗(yàn)和的計(jì)算算法為: 1、將檢驗(yàn)和字段置為0 2、把需校驗(yàn)的數(shù)據(jù)看成以16位為單位的數(shù)字組成,依次進(jìn)行二進(jìn)制反碼求和 3、把得到的結(jié)果存入檢驗(yàn)和字段中 所謂二進(jìn)制反碼求和,就是: 1、將源數(shù)據(jù)轉(zhuǎn)成反碼 2、0 + 0 = 0 0 + 1 = 1 1 + 1 = 0進(jìn)1 3、若最高位相加后產(chǎn)生進(jìn)位,則最后得到的結(jié)果要加1 在實(shí)際實(shí)現(xiàn)的過程中,比較常見的代碼寫法是: 1、將檢驗(yàn)和字段置為0 2、把需校驗(yàn)的數(shù)據(jù)看成以16位為單位的數(shù)字組成,依次進(jìn)行求和,并存到32位的整型中 3、把求和結(jié)果中的高16位(進(jìn)位)加到低16位上,如果還有進(jìn)位,重復(fù)第3步[實(shí)際上,這一步最多會執(zhí)行2次] 4、將這個32位的整型按位取反,并強(qiáng)制轉(zhuǎn)換為16位整型(截?cái)?后返回 */
(5)計(jì)算毫秒級別的時間差
//計(jì)算毫秒級別的時間差。返回值是unsigned long級別的 ULONG CPing::GetTickCountCalibrate() { static ULONG s_ulFirstCallTick = 0; static LONGLONG s_ullFirstCallTickMS = 0; SYSTEMTIME systemtime; //系統(tǒng)時間SYSTEMTIME與tm類似,不過多了一項(xiàng)wMilliseconds。 FILETIME filetime; //文件時間FILETIME與time_t類似,是64位整型,不過FILETIME是以100納秒(ns)為單位。 GetLocalTime(&systemtime); //GetLocalTime獲得當(dāng)前的本地時間,GetSystemTime函數(shù)獲得當(dāng)前的UTC時間,兩個時間存在著時差。 SystemTimeToFileTime(&systemtime, &filetime);//UTC的SYSTEMTIME時間 轉(zhuǎn)換為對應(yīng)的 本地的FILETIME時間 LARGE_INTEGER liCurrentTime; liCurrentTime.HighPart = filetime.dwHighDateTime; liCurrentTime.LowPart = filetime.dwLowDateTime; LONGLONG llCurrentTimeMS = liCurrentTime.QuadPart / 10000; if (s_ulFirstCallTick == 0) { s_ulFirstCallTick = GetTickCount(); //返回(retrieve)從操作系統(tǒng)啟動所經(jīng)過(elapsed)的毫秒數(shù)。用GetTickCount()計(jì)算毫秒級的時間差是不靠譜的! } if (s_ullFirstCallTickMS == 0) { s_ullFirstCallTickMS = llCurrentTimeMS; } return s_ulFirstCallTick + (ULONG)(llCurrentTimeMS - s_ullFirstCallTickMS);// 當(dāng)前時間ms - 第一次回應(yīng)時間ms }
到此這篇關(guān)于C++代碼實(shí)現(xiàn)網(wǎng)絡(luò)Ping功能的文章就介紹到這了,更多相關(guān)C++網(wǎng)絡(luò)Ping內(nèi)容請搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!
原文鏈接:https://blog.csdn.net/luyibing2017/article/details/119597300