【C/C++】用socket實現ping功能


PING(Packet Internet Groper)相關報文格式請自行尋找資料。

以下僅給出C語言實現代碼,復制代碼到一個c/cpp文件,直接編譯即可。

 

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/proc.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <netdb.h>// gethostbyname

typedef int SOCKET;

typedef struct icmp_hdr
{
	unsigned char   icmp_type; // 消息類型
	unsigned char   icmp_code; // 代碼
	unsigned short  icmp_checksum; // 校驗和
	// 下面是回顯頭
	unsigned short  icmp_id; // 用來惟一標識此請求的ID號
	unsigned short  icmp_sequence; // 序列號
	unsigned long   icmp_timestamp; // 時間戳
} ICMP_HDR, *PICMP_HDR;

typedef struct _IPHeader// 20字節的IP頭
{
	uint8_t     iphVerLen; // 版本號和頭長度(各占4位)
	uint8_t     ipTOS; // 服務類型
	uint16_t    ipLength; // 封包總長度,即整個IP報的長度
	uint16_t    ipID;  // 封包標識,惟一標識發送的每一個數據報
	uint16_t    ipFlags; // 標志
	uint8_t     ipTTL; // 生存時間,就是TTL
	uint8_t     ipProtocol; // 協議,可能是TCP、UDP、ICMP等
	uint16_t    ipChecksum; // 校驗和
	uint32_t    ipSource; // 源IP地址
	uint32_t    ipDestination; // 目標IP地址
} IPHeader, *PIPHeader;

uint16_t checksum(uint16_t* buff, int size);

bool SetTimeout(SOCKET s, int nTime, bool bRecv = TRUE);

inline uint32_t GetIpByName(const char * name)
{
	hostent* pHostent = gethostbyname(name);
	if(pHostent == nullptr) return INADDR_NONE;
	uint32_t host = *(uint32_t*)pHostent->h_addr_list[0];
	return host;
}

int main(int args, const char ** argv)
{
	// 目的IP地址,即要Ping的IP地址
	const char * szDestIp = (args > 1)?argv[1]:"127.0.0.1";
	auto ip = inet_addr(szDestIp);
	if((int)ip == -1) ip = GetIpByName(szDestIp); // nds
	if((int)ip == -1)
	{
		printf("invalid ip address:%s\n", szDestIp);
		return -1;
	}
	uint16_t pID = (uint16_t)::time(0);
	printf("ping %s\n", szDestIp);
	// 創建原始套節字
	SOCKET sRaw = ::socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
	if(sRaw == -1)
	{
		auto eno = errno;
		if(eno == 1)
		{
			// 必須使用sudo才能建立Raw socket
			printf("Operation not permitted!\n");
		}
		else
		{
			printf("Cannot create socket! Error %d", eno);
		}
		return -1;
	}
	// 設置接收超時
	if(!SetTimeout(sRaw, 1000, TRUE))
	{
		printf("Cannot set timeout!\n");
	}
	// 設置目的地址
	sockaddr_in dest;
	dest.sin_family = AF_INET;
	dest.sin_port = htons(0);
	dest.sin_addr.s_addr = ip;
	// 創建ICMP封包
	char buff[sizeof(ICMP_HDR) + 32];
	ICMP_HDR* pIcmp = (ICMP_HDR*)buff;
	// 填寫ICMP封包數據
	pIcmp->icmp_type = 8; // 請求一個ICMP回顯
	pIcmp->icmp_code = 0;
	pIcmp->icmp_id = (uint16_t)pID;
	pIcmp->icmp_checksum = 0;
	pIcmp->icmp_sequence = 0;
	// 填充數據部分,可以為任意
	memset(&buff[sizeof(ICMP_HDR)], 'E', 32);
	// 開始發送和接收ICMP封包
	uint16_t    nSeq = 0;
	char recvBuf[1024];
	sockaddr_in from;
	socklen_t nLen = sizeof(from);
	while(TRUE)
	{
		static int nCount = 0;
		long nRet;
		if(nCount++ == 4)
			break;
		pIcmp->icmp_checksum = 0;
		pIcmp->icmp_timestamp = ::clock();
		pIcmp->icmp_sequence = nSeq++;
		pIcmp->icmp_checksum = checksum((uint16_t*)buff, sizeof(ICMP_HDR) + 32);
		nRet = (long)::sendto(sRaw, buff, sizeof(ICMP_HDR) + 32,
				0, (sockaddr *)&dest, sizeof(dest));
		if(nRet == -1)
		{
			printf(" sendto() failed: %d \n", errno);
			return -1;
		}
		nRet = (long)::recvfrom(sRaw, recvBuf, 1024, 0, (sockaddr*)&from, &nLen);
		if(nRet == -1)
		{
			printf(" recvfrom() failed: %d\n", errno);
			return -1;
		}
		// 下面開始解析接收到的ICMP封包
		auto nTick = ::clock();
		if(nRet < sizeof(IPHeader) + sizeof(ICMP_HDR))
		{
			printf(" Too few bytes from %s \n", ::inet_ntoa(from.sin_addr));
		}
		// 接收到的數據中包含IP頭,IP頭大小為20個字節,所以加20得到ICMP頭
		ICMP_HDR* pRecvIcmp = (ICMP_HDR*)(recvBuf + 20); // (ICMP_HDR*)(recvBuf + sizeof(IPHeader));
#if 0 // IP頭解析
		IPHeader * header = (IPHeader*)recvBuf;
		struct in_addr a;
		a.s_addr = header->ipSource;
		printf("source ip %s\n", inet_ntoa(a));
		a.s_addr = header->ipDestination;
		printf("dest ip %s\n", inet_ntoa(a));
#endif
		if(pRecvIcmp->icmp_type != 0)// 回顯
		{
			printf(" nonecho type %d recvd \n", pRecvIcmp->icmp_type);
			return -1;
		}
		if(pRecvIcmp->icmp_id != pID)
		{
			printf(" someone else's packet! \n");
			return -1;
		}
		printf(" %d bytes from %s:", (int)nRet, inet_ntoa(from.sin_addr));
		printf(" icmp_seq = %d. ", pRecvIcmp->icmp_sequence);
		printf(" time: %d ms", (int)nTick - (int)pRecvIcmp->icmp_timestamp);
		printf(" \n");
	}
	return 0;
}
uint16_t checksum(uint16_t* buff, int size)
{
	unsigned long cksum = 0;
	while(size>1)
	{
		cksum += *buff++;
		size -= sizeof(uint16_t);
	}
	// 是奇數
	if(size)
	{
		cksum += *(uint8_t*)buff;
	}
	// 將32位的chsum高16位和低16位相加,然后取反
	while(cksum >> 16)
		cksum = (cksum >> 16) + (cksum & 0xffff);
	return (uint16_t)(~cksum);
}

bool SetTimeout(SOCKET s, int nTime, bool bRecv)
{
	int ret = ::setsockopt(s, SOL_SOCKET, bRecv ? SO_RCVTIMEO : SO_SNDTIMEO, (char*)&nTime, sizeof(nTime));
	return ret != -1;
}

 end


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM