tnblog
首页
视频
资源
登录

447行代码实现ping的功能

3722人阅读 2019/8/25 16:33 总访问:2588117 评论:7 收藏:1 手机
分类: 传说中的c
/*
	导入库文件
*/
#pragma comment( lib,"ws2_32.lib")
//加载头文件
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

//定义常量
//表示要记录的路由
#define IP_RECORD_ROUTE 0x7
//默认数据报的大小
#define DEF_PACKET_SIZE 32
//最大的ICMP数据报大小
#define MAX_PACKET 1024
//最大的ip头长度
#define MAX_IP_HDR_SIZE 60
//icmp报文类型,回显请求
#define ICMP_ECHO 8
//ICMP报文类型,回显应答
#define ICMP_ECHO 0
//最小的ICMP数据报大小
#define ICMP_ECHOREPLY 8
//自定义函数
void InitPing();

typedef	struct _iphdr {
	unsigned int h_len : 4; 		 //IP报头的长度
	unsigned int version:4;			 //IP的版本号
	unsigned char tos;				 //服务类型
	unsigned short total_len;		 //数据报的总长度
	unsigned short ident;			 //唯一的标识符
	unsigned short frag_flags;		 //分段标志
	unsigned char ttl;				 //生存期
	unsigned char proto;			 //协议类型(TCP,UDP)
	unsigned short checksum;		 //校验和
	unsigned int sourceIP;			 //源ip地址
	unsigned int destIP;			 //目的ip地址
}IpHeader;

//ICMP报头的字段的数据结构
typedef struct _icmphdr 
{
	BYTE i_type;			 //ICMP报文类型
	BYTE i_code;			 //该类型中的代码号
	USHORT i_cksum;			 //校验和
	USHORT i_id;			 //唯一的标识符
	USHORT i_seq;			 //序列号
	USHORT timestamp;		 //时间戳
}IcmpHeader;
//IP选项头字段数据结构
typedef struct _ipoptionhdr {
	unsigned char code;		 //选项类型
	unsigned char len;		 //选项头长度
	unsigned char ptr;		 //地址偏移长度
	unsigned long addr[9];	 //记录的ip地址列表

}IpOptionHeader;
//定义全局变量
SOCKET m_socket;
IpOptionHeader IpOption;
SOCKADDR_IN DestAddr;
SOCKADDR_IN SourceAddr;
char* icmp_data;
char* recvbuf;
USHORT seq_no;
char* lpdest;
int datasize;
BOOL RecordFlag;
double PacketNum;
BOOL SucessFlag;

//下面开始函数
//初始化函数
void InitPing() 
{
	WSADATA wsaData;
	icmp_data = NULL;
	seq_no = 0;
	recvbuf = NULL;
	RecordFlag = FALSE;
	lpdest = NULL;
	datasize = DEF_PACKET_SIZE;
	PacketNum = 5;
	SucessFlag = FALSE;

	//winsock初始化 WSAAccept 对winsocket进行加载
	if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
	{
		//如果初始化不成功则报错
		printf("WSAStartup() failed: %d\n",GetLastError());
		return;
	}
	m_socket = INVALID_SOCKET;
}


/*显示信息函数*/
void UserHelp() {
	printf("UserHelp: ping -r <host> [data size] \n");
	printf("               -r        record route \n");
	printf("               -n        record amount \n");
	printf("               host        remote machine to ping \n");
	printf("               datasize        can be up to 1KB\n");

}
/*获取ping选项函数*/
void GetArgments(int argc,char** argv) {
	int i;
	int j;
	int exp;
	int len;
	int m;
	//如果没有指定目的地址和任何选项
	if (argc==1)
	{
		printf("\nPlease specify the destination IP address and the ping option as follow!\n");
		for (i = 0; i < argc; i++)
		{
			len = strlen(argv[i]);
			if (argv[i][0] == '-')
			{
				//选项指令要获取记录条数
				if (isdigit(argv[i][1]))
				{
					PacketNum = 0;
					for (j = len-1,exp=0; j >= 1; j--,exp++)
					{
						//根据argv[i][j]中的ASCII值计算要获取的记录条数(十进制数)
						PacketNum += ((double)(argv[i][j] - 48)) * pow(10, exp);
					}
				}
				else
				{
					switch (tolower(argv[i][1]))
					{
						//选项指令要获取路由信息
					case 'r':
						RecordFlag = TRUE;
						break;
						//没有按要求提供选项
					default:
						UserHelp();
						break;
					}
				}
			}
			//参数是数据报大小或者IP地址
			else if(isdigit(argv[i][0]))
			{
				for (m = 0; m < len; m++)
				{
					if (!isdigit(argv[i][m]))
					{
						//是IP地址
						lpdest = argv[i];
						break;
					}
					//是数据报大小
					else if (m == len - 1)
						datasize = atoi(argv[i]);
				}
			}
			//参数是主机名
			else
			{
				lpdest = argv[i];
			}
			
		}
	}
}

//求校验和函数

USHORT CheckSum(USHORT* buffer,int size) 
{
	unsigned long cksum = 0;
	while (size>1)
	{
		cksum += *buffer++;
		size -= sizeof(USHORT);
	}
	if (size)
	{
		cksum += *(UCHAR*)buffer;
	}
	//每16位取二进制反码求和
	cksum = (cksum >> 16) + (cksum & 0xffff);
	cksum += (cksum >> 16);
	return (USHORT)(~cksum);
}
//填充ICMP数据报字段函数
void FillICMPData(char *icmp_data,int datasize) 
{
	IcmpHeader* icmp_hdr = NULL;
	char* datapart = NULL;
	icmp_hdr = (IcmpHeader*)icmp_data;
	//ICMP报文类型设置为回显请求
	icmp_hdr->i_type = ICMP_ECHO;
	icmp_hdr->i_cksum = 0;
	//获取当前经常IP作为标识符
	icmp_hdr->i_id = (USHORT)GetCurrentProcessId();
	icmp_hdr->i_cksum = 0;
	icmp_hdr->i_seq = 0;
	datapart = icmp_data + sizeof(IcmpHeader);
	//以数字0天吃剩余空间
	memset(datapart, 0, datapart - sizeof(IcmpHeader));
}
//释放资源函数
void FreeRes() 
{
	//关闭创建的套字节
	if (m_socket!=INVALID_SOCKET)
	{
		closesocket(m_socket);
	}
	//释放分配的内存
	HeapFree(GetProcessHeap(), 0, recvbuf);
	HeapFree(GetProcessHeap(), 0, icmp_data);
	//注销WSAStartup()调用
	WSACleanup();
	return;
}
//解读Ip选项头函数
void DecoideIPptions(char *buf,int bytes) 
{
	IpOptionHeader* ipopt = NULL;
	IN_ADDR inaddr;
	int i;
	HOSTENT* host = NULL;
	//获取路由信息的入口地址
	ipopt = (IpOptionHeader*)(buf + 20);
	printf(" RR:    ");
	for (i = 0; i < (ipopt->ptr / 4) - 1; i++)
	{
		inaddr.S_un.S_addr = ipopt->addr[i];
		if (i!=0)
		{
			printf("		");
			//根据IP地址获取主机名
			host = gethostbyaddr((char*)& inaddr.S_un.S_addr, sizeof(inaddr.S_un.S_addr), AF_INET);	
		}
		//如果获取到了主机名,则输出主机名
		if (host)
		{
			printf("(%-15s) %s\n",inet_ntoa(inaddr),host->h_name);
		}
		else
		{
			//否则输出ip地址
			printf("(%-15s)\n", inet_ntoa(inaddr));
		}
		return;
	}
}
//解读ICMP报头函数
void DecodeICMPHeader(char* buf,int bytes,SOCKADDR_IN* from) 
{
	IpHeader* iphdr = NULL;
	IcmpHeader* icmphdr = NULL;
	unsigned short iphdrlen;
	DWORD tick;
	static int icmpcount = 0;
	iphdr = (IpHeader*)buf;
	//计算ip报头的长度
	iphdrlen = iphdr->h_len * 4;
	tick = GetTickCount();
	//如果ip报头的长度为最大长度(基本长度是20字节),则认为有ip选项,因此需要解读ip选项
	if ((iphdrlen==MAX_IP_HDR_SIZE)&&(!icmpcount))
	{
		//解读IP选项,即路由信息
		DecoideIPptions(buf,bytes);
	}
	//如果收到的不是回显应答的报文则报错
	if (icmphdr->i_id != ICMP_ECHOREPLY)
	{
		printf("nonecho type %d recvd \n",icmphdr->i_type);
		return;
	}
	//核实用收到的ID号和发送的是否一致
	if (icmphdr->i_id != (USHORT)GetCurrentProcessId())
	{
		printf("someone else's packet! \n");
		return;
	}
	//输出信息记录信息
	printf("%d bytes from %s:",bytes,inet_ntoa(from->sin_addr));
	printf(" icmp_seq =%d. ", icmphdr->i_seq);
	printf(" icmp_seq =%d. ", tick - icmphdr->timestamp);
	printf("\n");
	icmpcount++;
	return;
}
//ping 函数
void PingTest(int timeout) 
{
	int ret;
	int readNum;
	int fromlen;
	struct hostent* hp = NULL;
	//创建原始套接字,该套接字用于ICMP
	m_socket = WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,WSA_FLAG_OVERLAPPED);
	//如果套接字创建不成功
	if (m_socket==INVALID_SOCKET)
	{
		printf("WSASocket() Failed: %d \n",WSAGetLastError());
		return;
	}
	//要求记录路由选项
	if (RecordFlag)
	{
		//IP选项每个字段都初始化为零
		ZeroMemory(&IpOption, sizeof(IpOption));
		//为每一个ICMP包设置路由选项
		IpOption.code = IP_RECORD_ROUTE;
		IpOption.ptr = 4;
		IpOption.len = 39;
		ret = setsockopt(m_socket, IPPROTO_IP, IP_OPTIONS, (char*)& IpOption, sizeof(IpOption));
		if (ret==SOCKET_ERROR)
		{
			printf("setsockopt(IP_OPTIONS) failed:%d \n",WSAGetLastError());
		}
	}
	//设置接收的超时值
	readNum = setsockopt(m_socket, SOL_SOCKET, SO_SNDTIMEO, (char*)& timeout, sizeof(timeout));
	if (readNum==SOCKET_ERROR)
	{
		printf("setsockopt(SO_RCVTIMEO) failed:%d\n ", WSAGetLastError());
		return;
	}
	//初始化目的的地址为零
	memset(&DestAddr, 0, sizeof(DestAddr));
	//设置地址族,这里表示使用IP地址族
	DestAddr.sin_family = AF_INET;
	if ((DestAddr.sin_addr.S_un.S_addr= inet_addr(lpdest)) == INADDR_NONE)
	{
		//名字解析,根据主机名获取IP地址
		if ((hp=gethostbyname(lpdest))!=NULL)
		{
			//将获取到的IP地址赋值给目的地址中相对应的字段
			memcpy(&(DestAddr.sin_addr), hp->h_addr_list, hp->h_length);
			//将获取到的地址族群赋值到目的相对应的字段中
			DestAddr.sin_family = hp->h_addrtype;
			printf("DestAddr.sin_addr = %s \n",WSAGetLastError());
		}
		//获取不成功
		else
		{
			printf("gethostbyname() faild: %d \n ",WSAGetLastError());
			return;
		}
	}
	//数据报文需要包含ICMP报头
	datasize += sizeof(IcmpHeader);
	//根据默认堆句柄,从堆中分配MAX_PACKET内存块,新分配内存将初始化为零
	icmp_data = (char*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,MAX_PACKET);
	recvbuf = (char*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,MAX_PACKET);
	//如果奉陪内存不成功
	if (!icmp_data)
	{
		printf("HeapAlloc() failed: %d \n", GetLastError());
		return;
	}
	//创建ICMP报文
	memset(icmp_data, 0, MAX_PACKET);
	FillICMPData(icmp_data, datasize);
	while (1)
	{
		static int nCount = 0;
		int writeNum;
		//超过指定的记录条数则退出
		if (nCount++ == PacketNum)
		{
			break;
		}
		//计算校验和前要把校验和字节设置为零
		((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请求
		writeNum = sendto(m_socket,icmp_data,datasize,0,(struct sockaddr*)&DestAddr,&fromlen);
		//如果接收不成功
		if (readNum==SOCKET_ERROR)
		{
			//如果超时则不成功
			if (WSAGetLastError()==WSAETIMEDOUT)
			{
				printf("timed out \n");
				continue;
			}
			//其他发送不成功的原因
			printf("sendto() failed:%d \n",WSAGetLastError());
			return;
		}
		//开始接收ICMP应答
		fromlen = sizeof(SourceAddr);
		readNum = recvfrom(m_socket,recvbuf,MAX_PACKET,0,(struct sockaddr*)&SourceAddr,&fromlen);
		if (readNum == SOCKET_ERROR)
		{
			//如果超时则不成功
			if (WSAGetLastError() == WSAETIMEDOUT)
			{
				printf("timed out \n");
				continue;
			}
			//其他发送不成功的原因
			printf("recvfrom() failed:%d \n", WSAGetLastError());
			return;
		}
		//解读收到的ICMP数据报
	}

}

int main(int argc,char* argv[])
{
	InitPing();
	GetArgments(argc,argv);
	PingTest(1000);
	//延迟1s
	Sleep(1000);
	if (SucessFlag)
	{
		printf("\n Ping end,you have got %.0f records! \n",PacketNum);
	}
	else
	{
		printf("Ping end ,no record");
	}
	FreeRes();
	getchar();
	return 0;

	/*
	
	
	*/
}



欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739

评价
这一世以无限游戏为使命!
排名
6
文章
6
粉丝
16
评论
8
{{item.articleTitle}}
{{item.blogName}} : {{item.content}}
ICP备案 :渝ICP备18016597号-1
网站信息:2018-2024TNBLOG.NET
技术交流:群号656732739
联系我们:contact@tnblog.net
欢迎加群交流技术