windows下ping程式使用C語言實現

Renaway發表於2018-02-15

vc++6.0或者更高版本vs新建win32 console專案,選簡單的Hello world專案,刪除自動生成的程式碼,增加如下程式碼,連結(F7)(不要執行)後在該專案的Debug目錄下使用命令列方式執行程式。

// iping.cpp : Defines the entry point for the console application.
//


#include "stdafx.h"


#pragma comment(lib,"ws2_32.lib")
//#pragma pack(4) //位元組對齊
#include   "winsock2.h"
#include   "stdlib.h"
#include   "stdio.h"


#define ICMP_ECHO	8 //ICMP回顯請求
#define ICMP_ECHOREPLY 0 //ICMP回顯應答
#define ICMP_MIN	8 //ICMP資料包最短為8個位元組
#define DEF_PACKET_SIZE    32   //預設資料包長度
#define DEF_PACKET_NUMBER  4   //預設傳送ICMP請求的次數
#define MAX_PACKET	1024        //資料包最大長度


//定義IP頭部 
typedef struct iphdr
{ 
	unsigned int h_len:4; // 頭部長 
	unsigned int version:4; // 版本號
	unsigned char tos; // 服務型別 
	unsigned short total_len; // 總長度
	unsigned short ident; // 標識 
	unsigned short frag_and_flags; //標誌
	unsigned char ttl; //生存時間
	unsigned char proto; // 上層協議 
	unsigned short checksum; // 校驗和
	unsigned int sourceIP; //源IP
	unsigned int destIP; //目的IP
}IpHeader; 
 
// 定義ICMP 頭部 
typedef struct icmphdr 
{ 
	BYTE i_type; //型別
	BYTE i_code; //程式碼
	USHORT i_cksum; //校驗和
	USHORT i_id; //標識
	USHORT i_seq; //序列號
	
	ULONG timestamp; //資料
}IcmpHeader; 




void fill_icmp_data(char *, int); //填充icmp資料包
USHORT checksum(USHORT *, int); //計算校驗和
int decode_resp(char *,int ,struct sockaddr_in *); //收到資料後解碼




void Usage(char *progname)//提示使用者該程式使用方法
{ 
	printf("Usage:\n"); 
	printf("%s target [number of packets] [data_size]\n",progname); 
	printf("datasize can be up to 1Kb\n"); 
} 




void main(int argc, char **argv)
{ 
	WSADATA wsaData; //初始化windows socket需要的引數
	SOCKET sockRaw;  //原始套接字
	struct sockaddr_in dest,from; //源、目的IP地址
	struct hostent * hp; //指標指向包含主機名、地址列表等資訊的結構體
	int iRecv,iSend, datasize,times; 
	int fromlen = sizeof(from); 


	int timeout = 1000;  //超時時間1000ms=1s
	int statistic = 0;  // 用於統計  
	char *dest_ip; 
	char *icmp_data; 
	char *recvbuf; 
	unsigned int addr=0; 
	USHORT	seq_no = 0; 
	int		i;
	
	if (WSAStartup(MAKEWORD(2,1),&wsaData) != 0)
			printf("WSAStartup failed: %d\n",GetLastError()); 
		return; 
		//使用方法不對時顯示提示資訊
	if (argc <2 ) 
			Usage(argv[0]); 
		return;
		//建立原始套接字
//	sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED);
	//注:為了使用傳送接收超時設定(即設定SO_RCVTIMEO, SO_SNDTIMEO),
	//    必須將標誌位設為WSA_FLAG_OVERLAPPED !
	sockRaw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
	
	// 建立原始套接字不成功
	if (sockRaw == INVALID_SOCKET)
			printf("WSASocket() failed: %d\n", WSAGetLastError()); 
		return; 
		//設定傳送超時時間
	iRecv = setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)); 
	if(iRecv == SOCKET_ERROR)
			printf("failed to set recv timeout: %d\n",WSAGetLastError()); 
		return; 
		//設定接收資料超時時間
	timeout = 1000; 
	iRecv = setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout)); 
	if(iRecv == SOCKET_ERROR) { 
		printf("failed to set send timeout: %d\n",WSAGetLastError()); 
		return; 
		
	memset(&dest,0,sizeof(dest)); 


	//解析使用者輸入的目標地址
	hp = gethostbyname(argv[1]); 
	if (!hp)
			addr = inet_addr(argv[1]); 
		//非法輸入
	if ((!hp) && (addr == INADDR_NONE))
			printf("Unable to resolve %s\n",argv[1]); 
		return; 
		//記錄目標主機資訊的結構體
	//地址
	if (hp != NULL) 
		memcpy(&(dest.sin_addr),hp->h_addr,hp->h_length); 
	else 
		dest.sin_addr.s_addr = addr; 


	//協議族
	if (hp) 
		dest.sin_family = hp->h_addrtype; 
	else 
		dest.sin_family = AF_INET; 




	//目標IP
	dest_ip = inet_ntoa(dest.sin_addr); 
		


	//除了目標地址,還給出了Ping的次數
	if(argc>2)
	{
		times=atoi(argv[2]);
		if(times == 0)
			 times = DEF_PACKET_NUMBER;
	}
	else
		times = DEF_PACKET_NUMBER;
	
	//還給出了資料大小
	if (argc >3) 
			datasize = atoi(argv[3]); 


		//給的是0,則用預設資料包大小
		if (datasize == 0) 
			datasize = DEF_PACKET_SIZE;


		//使用者給出的資料包大小太大
		if (datasize >1024)    
		{
			printf("WARNING : data_size is too large !\n");
			datasize = DEF_PACKET_SIZE; 
		}
		else 
		datasize = DEF_PACKET_SIZE; 
	
	datasize += sizeof(IcmpHeader); 


	icmp_data = (char *)malloc(MAX_PACKET); 
	recvbuf = (char *)malloc(MAX_PACKET); 
	
	if (!icmp_data)
			printf("HeapAlloc failed %d\n",GetLastError()); 
		return; 
		
	memset(icmp_data, 0, MAX_PACKET); 


	//填充ICMP資料包,型別、程式碼、標識等
	fill_icmp_data(icmp_data,datasize); 


	//提示正在ping目標主機
	printf("\nPinging %s ....\n\n",dest_ip);


	//Ping多次
	for(i=0; i<times; i++)
			//準備ICMP包頭部資料
		((IcmpHeader *)icmp_data)->i_cksum = 0; 
		//取得以毫秒為單位的計算機啟動後經歷的時間間隔
		((IcmpHeader *)icmp_data)->timestamp = GetTickCount(); 	
		((IcmpHeader *)icmp_data)->i_seq = seq_no++; //序列號遞增
		((IcmpHeader *)icmp_data)->i_cksum = checksum((USHORT*)icmp_data,datasize);//計算校驗和
		
		//傳送ICMP資料包
		iSend = sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest,sizeof(dest)); 
		
		//傳送失敗
		if (iSend == SOCKET_ERROR)
					if (WSAGetLastError() == WSAETIMEDOUT) 
							printf("Request timed out.\n"); 
				continue; 
						printf("sendto failed: %d\n",WSAGetLastError()); 
			break; 
				if (iSend < datasize )
					printf("Only sent  %d bytes\n",iSend); 
				//接收應答資料
		iRecv = recvfrom(sockRaw, recvbuf, MAX_PACKET, 0, (struct sockaddr*)&from, &fromlen); 
		
		//接收失敗
		if (iRecv == SOCKET_ERROR)
					if (WSAGetLastError() == WSAETIMEDOUT) 
							printf("Request timed out.\n"); 
				continue; 
						printf("recvfrom failed: %d\n",WSAGetLastError()); 
			break; 
				//成功解碼
		if(!decode_resp(recvbuf,iRecv,&from))
			statistic++; //記錄成功接收響應資料包的次數
		
		Sleep(1000); 
	}


	//統計執行Ping命令的統計結果
	printf("\nPing statistics for %s \n",dest_ip);
	printf("    Packets: Sent = %d,Received = %d, Lost = %d (%2.0f%% loss)\n",times,
			statistic,(times-statistic), (float)(times-statistic)/times*100);


	free(recvbuf);
	free(icmp_data);


	closesocket(sockRaw);
	WSACleanup();


	return; 
} 




//收到響應IP資料包後,對其進行解碼
int decode_resp(char *buf, int bytes,struct sockaddr_in *from) 
{ 
	IpHeader *iphdr; 
	IcmpHeader *icmphdr; 
	unsigned short iphdrlen; 
	
	iphdr = (IpHeader *)buf; 
	iphdrlen = (iphdr->h_len) * 4 ; //頭部佔幾個節位元組 
	
	if (bytes < iphdrlen + ICMP_MIN)
			printf("Too few bytes from %s\n",inet_ntoa(from->sin_addr)); 
		//找到ICMP資料包開始的地方
	icmphdr = (IcmpHeader*)(buf + iphdrlen); 
	if (icmphdr->i_type != ICMP_ECHOREPLY)
			printf("non-echo type %d recvd\n",icmphdr->i_type); 
		return 1; 
		//是不是發給本程式的資料包
	if (icmphdr->i_id != (USHORT)GetCurrentProcessId()) 
			printf("someone else''s packet!\n"); 
		return 1; 
		printf("%d bytes from %s:", bytes, inet_ntoa(from->sin_addr)); 
	printf(" icmp_seq = %d. ",icmphdr->i_seq); 
	printf(" time: %d ms ", GetTickCount()-icmphdr->timestamp); //傳送到接收過程的經歷的時間
	printf("\n");
	return 0; 
} 


//計算校驗和
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); 
} 


//填充ICMP資料包
void fill_icmp_data(char * icmp_data, int datasize)
{ 
	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 = (USHORT)GetCurrentProcessId(); 
	icmp_hdr->i_cksum = 0; 
	icmp_hdr->i_seq = 0; 
	datapart = icmp_data + sizeof(IcmpHeader); 


	//資料區隨便填充
	memset(datapart,17, datasize - sizeof(IcmpHeader)); 
}

相關文章