c++ ping 功能实现

本文介绍了一个基于C++实现的Ping命令程序,该程序利用原始套接字发送和接收ICMP包,通过多次尝试来确保网络连通性检查的准确性。文章详细解释了ICMP包的构造、发送及响应解析过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#if !defined(COMMON_FDSET_H)
#define COMMON_FDSET_H  

#include <cassert>
#include <errno.h>
#include <algorithm>

#ifdef WIN32
//#include <winsock2.h>
#include <WS2TCPIP.H>
#include <stdlib.h>
#include <io.h>
#endif



#ifndef WIN32
typedef int SOCKET;
#define INVALID_SOCKET (-1)
#define SOCKET_ERROR   (-1)
#else
//typedef SOCKET SOCKET;
#endif

int closeSocket( SOCKET fd );

class FdSet
{
public:
   FdSet() : size(0), numReady(0)
   {
      FD_ZERO(&read);
      FD_ZERO(&write);
      FD_ZERO(&except);
   }

   int select(struct timeval& tv)
   {
      return numReady = ::select(size, &read, &write, &except, &tv);
   }

   int selectMilliSeconds(unsigned long ms)
   {
      struct timeval tv;
      tv.tv_sec = (ms/1000);
      tv.tv_usec = (ms%1000)*1000;
      return select(tv);
   }

   bool readyToRead(SOCKET fd)
   {
      return (FD_ISSET(fd, &read) != 0);
   }

   bool readyToWrite(SOCKET fd)
   {
      return (FD_ISSET(fd, &write) != 0);
   }

   bool hasException(SOCKET fd)
   {
      return (FD_ISSET(fd,&except) != 0);
   }

   void setRead(SOCKET fd)
   {
      assert( FD_SETSIZE >= 8 );
      FD_SET(fd, &read);
      size = ( int(fd+1) > size) ? int(fd+1) : size;
   }

   void setWrite(SOCKET fd)
   {
      FD_SET(fd, &write);
      size = ( int(fd+1) > size) ? int(fd+1) : size;
   }

   void setExcept(SOCKET fd)
   {
      FD_SET(fd,&except);
      size = ( int(fd+1) > size) ? int(fd+1) : size;
   }


   void clear(SOCKET fd)
   {
      FD_CLR(fd, &read);
      FD_CLR(fd, &write);
      FD_CLR(fd, &except);
   }

   void reset()
   {
      size = 0;
      numReady = 0;
      FD_ZERO(&read);
      FD_ZERO(&write);
      FD_ZERO(&except);
   }

   // Make this stuff public for async dns/ares to use
   fd_set read;
   fd_set write;
   fd_set except;
   int size;
   int numReady;  // set after each select call
};


#endif 


#include <assert.h>
#include <fcntl.h>

#ifndef WIN32
#include <errno.h>
#include <unistd.h>
#endif

#include "FdSet.h"


using namespace std;


#ifdef WIN32

typedef int socklen_t;

#define EWOULDBLOCK             WSAEWOULDBLOCK
#define EINPROGRESS             WSAEINPROGRESS
#define EALREADY                WSAEALREADY
#define ENOTSOCK                WSAENOTSOCK
#define EDESTADDRREQ            WSAEDESTADDRREQ
#define EMSGSIZE                WSAEMSGSIZE
#define EPROTOTYPE              WSAEPROTOTYPE
#define ENOPROTOOPT             WSAENOPROTOOPT
#define EPROTONOSUPPORT         WSAEPROTONOSUPPORT
#define ESOCKTNOSUPPORT         WSAESOCKTNOSUPPORT
#define EOPNOTSUPP              WSAEOPNOTSUPP
#define EPFNOSUPPORT            WSAEPFNOSUPPORT
#define EAFNOSUPPORT            WSAEAFNOSUPPORT
#define EADDRINUSE              WSAEADDRINUSE
#define EADDRNOTAVAIL           WSAEADDRNOTAVAIL
#define ENETDOWN                WSAENETDOWN
#define ENETUNREACH             WSAENETUNREACH
#define ENETRESET               WSAENETRESET
#define ECONNABORTED            WSAECONNABORTED
#define ECONNRESET              WSAECONNRESET
#define ENOBUFS                 WSAENOBUFS
#define EISCONN                 WSAEISCONN
#define ENOTCONN                WSAENOTCONN
#define ESHUTDOWN               WSAESHUTDOWN
#define ETOOMANYREFS            WSAETOOMANYREFS
#define ETIMEDOUT               WSAETIMEDOUT
#define ECONNREFUSED            WSAECONNREFUSED
#define ELOOP                   WSAELOOP
#define EHOSTDOWN               WSAEHOSTDOWN
#define EHOSTUNREACH            WSAEHOSTUNREACH
#define EPROCLIM                WSAEPROCLIM
#define EUSERS                  WSAEUSERS
#define EDQUOT                  WSAEDQUOT
#define ESTALE                  WSAESTALE
#define EREMOTE                 WSAEREMOTE

#else
	#define WSANOTINITIALISED  EPROTONOSUPPORT
#endif


int closeSocket( SOCKET fd )
{
#if defined(WIN32)
	return closesocket(fd);
#else 
	int ret = ::close(fd);
	return ret;
#endif
}




 
#ifndef __SocketPing_H__
#define __SocketPing_H__

#include "FdSet.h"


#define ICMP_ECHO       8 
#define ICMP_ECHOREPLY  0 

#define ICMP_MIN        8 // minimum 8 byte icmp packet (just header) 
#define ICMP_MAGIC      2010

#define STATUS_FAILED   0xFFFF 
#define DEF_PACKET_SIZE 32 
#define MAX_PACKET      1024 

/* The IP header */ 
typedef struct ip_hdr 
{ 
	unsigned int   h_len:4;         // length of the header 
	unsigned int   version:4;       // Version of IP 
	unsigned char  tos;             // Type of service 
	unsigned short total_len;       // total length of the packet 
	unsigned short ident;           // unique identifier 
	unsigned short frag_and_flags;  // flags 
	unsigned char  ttl; 
	unsigned char  proto;           // protocol (TCP, UDP etc) 
	unsigned short checksum;        // IP checksum 
	unsigned int   sourceIP; 
	unsigned int   destIP; 
}IpHeader; 

// 
// ICMP header 
// 
typedef struct _ihdr
{ 
	BYTE i_type; 
	BYTE i_code; /* type sub code */ 
	USHORT i_cksum; 
	USHORT i_id; 
	USHORT i_seq; 
	/* This is not the std header, but we reserve space for time */ 
	ULONG timestamp; 
}IcmpHeader; 


//IP option header--use with socket option IP_OPTIONS   
typedef struct _ipoptionhdr  
{  
    unsigned char code;     // Option type   
    unsigned char len;      // Length of option hdr   
    unsigned char ptr;      // Offset into optons   
    unsigned long addr[9];  // List of IP addrs   
} IpOptionHeader;

// ping
bool Ping(const char * ip, int try_count = 4, int timeout = 3000);

#endif /*__SocketPing_H__*/

#include "SocketPing.h"


/*lint -e830*/
static USHORT MakeIcmpID()
{
	static CMutex   g_mutex;
	CGuard   guard(&g_mutex);
	static USHORT iIcmpID = 1;
	return iIcmpID++;
}

static USHORT checksum(USHORT *buffer, int size) 
{ 
	unsigned long cksum=0; 

	while(size >1) 
	{ 
		cksum += *buffer++; 
		size  -= sizeof(USHORT); 
	} 

	if(size ) 
	{ 
		cksum += *(UCHAR*)buffer; 
	} 

	cksum  = (cksum >> 16) + (cksum & 0xffff); 
	cksum += (cksum >>16); 
	return (USHORT)(~cksum); 
}

/* 
Helper function to fill in various stuff in our ICMP request. 
*/ 
static void fill_icmp_data(char * icmp_data, int datasize, USHORT icmp_id)
{ 
	IcmpHeader *icmp_hdr; 
	char *datapart; 

	icmp_hdr = (IcmpHeader*)icmp_data; 

	icmp_hdr->i_type  = ICMP_ECHO; 
	icmp_hdr->i_code  = 0; 
	icmp_hdr->i_id    = icmp_id;//ICMP_MAGIC; 
	icmp_hdr->i_cksum = 0; 
	icmp_hdr->i_seq   = 0; 

	datapart = icmp_data + sizeof(IcmpHeader); 
	// 
	// Place some junk in the buffer. 
	// 
	memset(datapart,'E', datasize - sizeof(IcmpHeader)); 
} 

/* 
The response is an IP packet. We must decode the IP header to locate 
the ICMP data
//成功返回0;失败返回-1;如果收到别人回复返回1
*/ 
static int decode_resp(char *buf, int bytes,struct sockaddr_in *from, const char * ip, USHORT icmp_id) 
{ 
	IpHeader * iphdr = (IpHeader *)buf; 

	unsigned short iphdrlen = (unsigned short)iphdr->h_len * 4 ; // number of 32-bit words *4 = bytes 

	if (bytes < iphdrlen + ICMP_MIN) 
	{ 
		SOCKETDEV_ERROR("Ping [%s] too few bytes", ip); 
		return -1;
	} 

	IcmpHeader *icmphdr = (IcmpHeader*)(buf + iphdrlen); 

	if (icmphdr->i_type != ICMP_ECHOREPLY) 
	{ 
		SOCKETDEV_ERROR("Ping [%s], no echo reply", ip, icmphdr->i_type); 
		return -1; 
	} 

	if (icmphdr->i_id != icmp_id) 
	{
		//SOCKET_LOG_ERROR("Ping [%s] error magic packet", ip); 
		return 1; 
	} 

	return 0;
} 

static bool send_icmp_data(SOCKET sock_raw, const char * ip, USHORT icmp_id,  int try_count, int timeout)
{
	int seq_no = 0;
	char icmp_data[MAX_PACKET] = {0};

	sockaddr_in dest = {0};
	dest.sin_family = AF_INET;
	dest.sin_addr.s_addr = inet_addr( ip );

	bool success = false;
	bool needToSendPingReq = true;
	while(try_count-- > 0)
	{
		bool isToSendPingReq = needToSendPingReq;
		needToSendPingReq = true;
		if(isToSendPingReq)
		{
			memset(icmp_data,0,MAX_PACKET); 
			fill_icmp_data(icmp_data, DEF_PACKET_SIZE, icmp_id); 

			((IcmpHeader*)icmp_data)->i_cksum = 0; 
			((IcmpHeader*)icmp_data)->timestamp = GetTickCount(); 

			((IcmpHeader*)icmp_data)->i_seq = (unsigned short)seq_no++; 
			((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data, 
				DEF_PACKET_SIZE); 

			int bwrote = sendto(sock_raw, icmp_data, DEF_PACKET_SIZE, 0, (struct sockaddr*)&dest,  sizeof(dest)); 

			if (bwrote == SOCKET_ERROR)
			{ 
				if (WSAGetLastError() == WSAETIMEDOUT) 
				{ 
					SOCKETDEV_ERROR("Ping [%s] timed out", ip); 
					continue; 
				} 

				SOCKETDEV_ERROR("Ping [%s] packet send failed: %d", ip, WSAGetLastError()); 
				continue;
			} 

			if (bwrote < DEF_PACKET_SIZE )
			{ 
				continue;
			} 
		}
	
		char recvbuf[MAX_PACKET] = {0};
		struct sockaddr_in from; 
		int fromlen = sizeof(from); 

		FdSet fdset;
		fdset.setRead(sock_raw);
		timeval val;
		val.tv_sec = timeout / 1000;
		val.tv_usec = (timeout%1000)*1000;

		int bread = SOCKET_ERROR;
		bool bTimeout =false;
		do 
		{
			int selectRet = fdset.select(val);
			if(selectRet == 0)
			{//超时
				bTimeout = true;
				break;
			}
			else if(selectRet < 0)
			{//发生错误
				break;
			}
			if(fdset.readyToRead(sock_raw))
			{
				bread = recvfrom(sock_raw, recvbuf, MAX_PACKET, 0, (struct sockaddr*)&from, &fromlen); 
			}
		} while (0);

		if(bTimeout || (bread <= 0 && WSAGetLastError() == WSAETIMEDOUT))
		{
			SOCKETDEV_ERROR("Ping [%s] timed out", ip); 
			continue; 
		}

		if (bread <= 0)
		{ 
			SOCKETDEV_ERROR("Ping [%s] recv failed: %d", ip, WSAGetLastError()); 
			continue;
		} 

		int ret = decode_resp(recvbuf, bread, &from, ip, icmp_id); 
		if(ret == 0)
		{
			success = true;
			break;
		}
		else if(ret == 1)
		{
            //接收了别的ping回复,需要重新接收 
			needToSendPingReq = false;
			++ try_count;
			continue;
		}
		//失败
		continue;
	}

	return success;
}

//ping
bool Ping(const char * ip, int try_count, int timeout)
{
	SOCKET sock_raw; 

	bool success = false;
	
	USHORT icmp_id = MakeIcmpID();

	do 
	{
		sock_raw = WSASocket (AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED); 

		if (sock_raw == INVALID_SOCKET) 
		{ 
			SOCKETDEV_ERROR("WSASocket failed");
			break;
		} 

		unsigned int beginTick = GetTickCount(); //统计时间开始

		char icmp_data[MAX_PACKET] = {0};
		fill_icmp_data(icmp_data, MAX_PACKET, icmp_id); 
		success = send_icmp_data(sock_raw, ip, icmp_id, try_count, timeout);

		unsigned int endTick = GetTickCount();  //统计时间结束
		if (success)
		{
			SOCKETDEV_INFO("Ping [%s] success, use %u ms", ip, (endTick - beginTick) );
		}
		else
		{
			SOCKETDEV_ERROR("Ping [%s] failed, use %u ms", ip, (endTick - beginTick));
		}

	} while (0);

	if (sock_raw != INVALID_SOCKET)
	{
		closesocket(sock_raw);
	}

	return success;
}

/*lint +e830*/




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值