ping命令的实现

这些天在学习windows网络编程,昨天看到了探测网络中的在线设备,其中刚好有一个实现ping命令的实例,就照着


了下代码,结果老是超时,后来到网上问才知道少了计算检验和,于是网上找了下关于检验和的知识。


校验和计算:

   为了计算一份数据报的校验和码。首先把校验和字段置为0。然后,对首部中每一个16bit进行二进制反码求和,结果存在校验和字段中。当受到到一份ip数据报后,同样对首部中每个16bit进行二进制反码的求和。由于接收方在计算过程中包含了发送方存在首部中的检验和,因此,如果首部在传输过程中没有任何差错,那么接收方计算的结果应该为全1.

Icmp 校验和的计算:

TCP/ip协议对校验和计算方法:对16位的数据进行累加计算,并返回求反的计算结果。

需要注意的是对奇数个字节数据的计算。是将最后的有效数据作为最高位的字节,低字节填充为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); 
}

#include<winsock2.h>
#include<stdio.h>
#include<iostream>
using namespace std;

#define ICMP_ECHO 8
#define ICMP_ECHOREPLY 0
#define ICMP_MIN 8
#define DEF_PACKET_SIZE 32
#define MAX_PACKET 1024

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 checksun;
	unsigned int sourceIP;
	unsigned int destIP;
}IpHeader;

typedef struct _ihdr
{
	BYTE i_type;
	BYTE i_code;
	USHORT i_cksum;
	USHORT i_id;
	USHORT i_seq;
	ULONG timestamp;
}IcmpHeader;

void fill_icmp_date (char* icmp_data, int datasize);
int decode_resp (char* buf, int bytes, struct sockaddr_in* from, DWORD tid);
int ping (const char* ip, DWORD timeout);
USHORT checksum(USHORT *buffer, int size);

int main(int argc, char* argv[])
{
	if (argc != 2)
	{
		printf ("参数数量不正确。请指定要ping的IP地址。\n");
		return -1;
	}
	/*char argv[2][20];
	argv[1] = "192.168.1.102";*/
	printf ("ping %s\n", argv[1]);
	int ret = ping (argv[1], 500);
	if (ret >= 0)
		printf ("%s在线,执行ping操作用时%dms。\n", argv[1], ret);
	else
	{
		switch (ret)
		{
		case -1:
			printf ("ping 超时...\n");
			break;

		case -2:
			printf ("创建Socket 出错...\n");
			break;

		case -3:
			printf ("设置Socket的接收超时选项出错...\n");
			break;

		case -4:
			printf ("设置Socket的发送超时选项出错...\n");
			break;

		case -5:
			printf ("获取域名出错,可能是IP地址不正确...\n");
			break;

		case -6:
			printf ("未能为ICMP数据包分配到足够的空间...\n");
			break;

		case -7:
			printf ("发送ICMP数据包出错...\n");
			break;

		case -8:
			printf ("发送ICMP数据包的数量不正确...\n");
			break;

		case -9:
			printf ("接收ICMP数据包出错...\n");
			break;

		case -1000:
			printf ("初始化Windows Sockets环境出错...\n");
			break;

		default:
			printf ("未知的错误");
			break;
		}
	}
	printf ("\n");
	system ("pause");
	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); 
}

void fill_icmp_data (char* icmp_data, int datasize)
{
	IcmpHeader* imcp_hdr;
	char* datapart;
	
	imcp_hdr = (IcmpHeader*)icmp_data;
	imcp_hdr->i_type = ICMP_ECHO;
	imcp_hdr->i_code = 0;
	imcp_hdr->i_id = (USHORT)GetCurrentThreadId();
	imcp_hdr->i_cksum = 0;
	imcp_hdr->i_seq = 0;
	
	datapart = icmp_data + sizeof(IcmpHeader);
	memset (datapart, 'E', datasize - sizeof(IcmpHeader));
}

int decode_resp (char* buf, int bytes, struct sockaddr_in* from, DWORD tid)
{
	IpHeader* iphdr;
	IcmpHeader* icmphdr;
	unsigned short iphdrlen;
	
	iphdr = (IpHeader*)buf;
	iphdrlen = iphdr->h_len * 4;
	if (bytes < iphdrlen + ICMP_MIN)
		return -1;
	
	icmphdr = (IcmpHeader*)(buf + iphdrlen);
	if (icmphdr->i_type != ICMP_ECHOREPLY)
		return -2;

	if (icmphdr->i_id != (USHORT)tid)
		return -3;

	int time = GetTickCount() - icmphdr->timestamp;
	if (time >= 0)
		return time;
	else
		return -4;
}

int ping (const char* ip, DWORD timeout)
{
	WSADATA wsaData;
	SOCKET sockRaw = NULL;
	struct sockaddr_in dest, from;
	struct hostent* hp;
	int datasize;
	char* dest_ip = NULL;
	char* icmp_data = NULL;
	char* recvbuf = NULL;
	USHORT seq_no = 0;
	int ret = -1;

	if (WSAStartup (MAKEWORD(2, 1), &wsaData) != 0)
	{
		ret = -100;
		goto FIN;
	}

	sockRaw = WSASocket (AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED);
	if (sockRaw == INVALID_SOCKET)
	{
		ret = -2;
		goto FIN;
	}

	int bread = setsockopt (sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
	if (bread == SOCKET_ERROR)
	{
		ret = -3;
		goto FIN;
	}

	bread = setsockopt (sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout));
	if (bread == SOCKET_ERROR)
	{
		ret = -4;
		goto FIN;
	}
	memset (&dest, 0, sizeof(dest));

	unsigned int addr = 0;
	hp = gethostbyname (ip);
	if (!hp)
		addr = inet_addr (ip);
	if ((!hp) && (addr == INADDR_NONE))
	{
		ret = -5;
		goto FIN;
	}

	/*配置远程通信地址*/
	if (hp != NULL)
		memcpy (&(dest.sin_addr), hp->h_addr_list[0], hp->h_length);
	else
		dest.sin_addr.S_un.S_addr = addr;
	if (hp)
		dest.sin_family = hp->h_addrtype;
	else
		dest.sin_family = AF_INET;
	dest_ip = inet_ntoa (dest.sin_addr);

	/*准备发送数据*/
	datasize = DEF_PACKET_SIZE;
	datasize += sizeof(IcmpHeader);
	char icmp_dataStack[MAX_PACKET];
	char recvbufStack[MAX_PACKET];
	icmp_data = icmp_dataStack;
	recvbuf = recvbufStack;
	//memset (recvbuf, 0, MAX_PACKET);

	if (!icmp_data)
	{
		ret = -6;
		goto FIN;
	}
	memset (icmp_data, 0, MAX_PACKET);
	fill_icmp_data (icmp_data, datasize);
	((IcmpHeader*)icmp_data)->i_cksum = 0;
	DWORD startTime = GetTickCount();
	((IcmpHeader*)icmp_data)->timestamp = startTime;
	((IcmpHeader*)icmp_data)->i_seq = seq_no++;
	((IcmpHeader*)icmp_data)->i_cksum = checksum ((USHORT*)icmp_data, datasize);

	/*发送数据*/
	int bwrote;
	bwrote = sendto (sockRaw, icmp_data, datasize, 0, (struct sockaddr*)&dest, sizeof(dest));
	if (bwrote == SOCKET_ERROR)
	{
		if (WSAGetLastError() == WSAETIMEDOUT)
		{
			ret = -7;
		    goto FIN;
		}
	}
	if (bwrote < datasize)
	{
		ret = -8;
		goto FIN;
	}

	LARGE_INTEGER ticksPerSecond;
	LARGE_INTEGER start_tick;
	LARGE_INTEGER end_tick;
	double elapsed;
	QueryPerformanceFrequency (&ticksPerSecond);
	QueryPerformanceCounter (&start_tick);
	int fromlen = sizeof(from);
	while (1)
	{
		bread = recvfrom (sockRaw, recvbuf, MAX_PACKET, 0, (struct sockaddr*)&from, &fromlen);
		if (bread == SOCKET_ERROR)
		{
			if (WSAGetLastError() == WSAETIMEDOUT)
			{
				ret = -1;
				goto FIN;
			}
			ret = -9;
			goto FIN;
		}

		/*对回应的IP数据包进行解析,定位ICMP数据*/
	    int time = decode_resp (recvbuf, bread, &from, GetCurrentThreadId());
		if (time >= 0)
		{
			QueryPerformanceCounter (&end_tick);
			elapsed = ((double)(end_tick.QuadPart - start_tick.QuadPart) / ticksPerSecond.QuadPart);
			ret = (int)(elapsed * 1000);
			goto FIN;
		}
		else if (GetTickCount() - startTime >= timeout || GetTickCount() < startTime)
		{
			ret = -1;
			goto FIN;
		}
	}

FIN:
	closesocket (sockRaw);
	WSACleanup ();
	return ret;
}

这个程序的测试IP是通过命令行传递的 ,我的测试环境是VS2010,直接打开项目属性 --> 配置属性 --> 调试 --> 命令参数,在命令参数那一栏中输入要测试的Ip地址即可。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值