Windows网络编程——实现ping命令

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <WinSock2.h>
#pragma comment(lib, "Ws2_32.lib")

using namespace std;

#define ICMP_MIN 8					// ICMP包的最小长度为8个字节,只包含包头
#define DEF_PACKET_SIZE 32			// 约定发送用户数据的长度
#define MAX_PACKET 1024				// 执行ping操作时缓冲区大小
#define ICMP_ECHOREQUEST 8			// 表示ICMP包为请求包
#define ICMP_ECHOREPLY 0			// 表示ICMP包为应答包
#define endl '\n'

//数据包头结构
typedef struct iphdr {
	unsigned int h_len : 4;				// IP头长度
	unsigned int version : 4;			// IP协议版本
	unsigned char tos;					// 服务类型(TOS)
	unsigned short total_len;			// 包的总长度
	unsigned short ident;				// 包的唯一标识
	unsigned short frag_and_flags;		// 标识
	unsigned char ttl;					// 生存时间(TTL)
	unsigned char proto;				// 传输协议 (TCP, UDP等)
	unsigned short checksum;			// IP校验和
	unsigned int sourceIP;	//源IP地址
	unsigned int destIP;	//目的IP地址
}IpHeader;
// 执行ping操作时,定义发送IP数据包中包含的ICMP数据头结构
typedef struct _ihdr {
	BYTE i_type;			// 类型
	BYTE i_code;			// 编码
	USHORT i_cksum;			// 检验和
	USHORT i_id;			// 识别号
	USHORT i_seq;			// 报文序列号
	ULONG timestamp;		// 时间戳
}IcmpHeader;

// 计算ICMP包的校验和
USHORT checksum(USHORT* buffer, int size)
{//求buffer存储空间中前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回应包
int decode_resp(char* buf, int bytes, DWORD tid)
{
	IpHeader* iphdr;//IP数据包头
	IcmpHeader* icmphdr;//ICMP包头
	unsigned short iphdrlen;//IP数据包头长度
	iphdr = (IpHeader*)buf;
	iphdrlen = iphdr->h_len * 4;// number of 32-bit words *4 = bytes
	if (bytes < iphdrlen + ICMP_MIN) return -1;
	
	//定位到ICMP包头的起始位置
	icmphdr = (IcmpHeader*)(buf + iphdrlen);
	//如果ICMP包的类型不是回应包,则不处理
	if (icmphdr->i_type != ICMP_ECHOREPLY) return -2;

	//发送的ICMP包ID和接收到的ICMP包ID应该对应
	if (icmphdr->i_id != (USHORT)tid) return -3;

	//返回发送ICMP包和接收回应包的时间差 
	int time = GetTickCount() - (icmphdr->timestamp);
	if (time >= 0) return time;
	return -4;
}
// 填充ICMP请求包
void fill_icmp_data(char* icmp_data, int datasize) {//对教材P142代码进行了优化
	//将icmp_data指向的datasize字节的空间设置成ICMP请求包;datasize:ICMP请求包大小
	IcmpHeader* icmp_hdr;
	char* datapart;
	// 将缓冲区转换为IcmpHeader结构
	icmp_hdr = (IcmpHeader*)icmp_data;//指针强制转换
	// 填充各字段的值
	icmp_hdr->i_type = ICMP_ECHOREQUEST;			// 将类型设置为ICMP请求包
	icmp_hdr->i_code = 0;							// 将编码设置为0	
	icmp_hdr->i_id = (USHORT)GetCurrentThreadId();	// 将编号设置为当前线程的编号	
	icmp_hdr->i_seq = 0;							// 将序列号设置为0
	DWORD startTime = GetTickCount();//GetTickCount程序已经执行的时间(毫秒)
	icmp_hdr->timestamp = startTime;//设置时间戳	
	datapart = icmp_data + sizeof(IcmpHeader);		// 定义到数据部分(指针运算)	
	// 在数据部分随便填充一些数据
	memset(datapart, 'E', datasize - sizeof(IcmpHeader));
	icmp_hdr->i_cksum = checksum((USHORT*)icmp_data, datasize);//设置成员i_cksum
	//i_cksum成员的赋值必须在ICMP包所有数据都设置好之后进行
}

int ping(const char* ip, DWORD timeout)
{
	WSADATA wsaData;
	SOCKET sockRaw = NULL;
	struct sockaddr_in dest, from;//远程主机通信的地址
	struct hostent* hp;//保存远程主机信息
	int datasize;
	char* icmp_data = NULL;
	char* recvbuf = NULL;
	int ret = -1;
	//初始化SOCKET
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		closesocket(sockRaw);
		WSACleanup();
		return -1000;
	}
	//创建原始套接字
	sockRaw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
	if (sockRaw == INVALID_SOCKET)
	{
		closesocket(sockRaw);
		WSACleanup();
		return -2;
	}
	//设置套接字的接收超时选项
	int bread = setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
	if (bread == SOCKET_ERROR)
	{
		closesocket(sockRaw);
		WSACleanup();
		return -3;
	}
	//设置套接字的发送超时选项
	bread = setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout));
	if (bread == SOCKET_ERROR)
	{
		closesocket(sockRaw);
		WSACleanup();
		return -4;
	}
	memset(&dest, 0, sizeof dest);
	unsigned int addr = 0;
	hp = gethostbyname(ip);
#pragma region Myregion
#pragma endregion
#pragma	region
	if (hp != NULL)
	{
		dest.sin_addr.S_un.S_addr = *((ULONG*)(hp->h_addr_list[0]));
		dest.sin_family = hp->h_addrtype;
	}
	else
	{
		closesocket(sockRaw);
		WSACleanup();
		return -5;
	}
#pragma endregion
	//准备要发送的数据
	datasize = DEF_PACKET_SIZE;//ICMP请求包大小
	datasize += sizeof(IcmpHeader);
	char icmp_dataStack[MAX_PACKET];//发送数据缓冲区
	char recvbufStack[MAX_PACKET];//接收数据缓冲区
	icmp_data = icmp_dataStack;
	recvbuf = recvbufStack;
	memset(icmp_data, 0, datasize);

	//准备要发送的数据
	fill_icmp_data(icmp_data, datasize);//设置ICMP请求报文
	//发送数据
	int bwrote = sendto(sockRaw, icmp_data, datasize, 0, (struct sockaddr*)&dest, sizeof(dest));

	if (bwrote == SOCKET_ERROR)
	{
		if (WSAGetLastError() != WSAETIMEDOUT)//发送超时
		{
			closesocket(sockRaw);
			WSACleanup();
			return -7;
		}
		closesocket(sockRaw);
		WSACleanup();
		return -8;
		
	}
	if (bwrote < datasize)//发送错误
	{
		closesocket(sockRaw);
		WSACleanup();
		return -8;
	}
	int fromlen = sizeof(from);//源地址的大小
	bread = recvfrom(sockRaw, recvbuf, MAX_PACKET, 0, (struct sockaddr*)&from, &fromlen);
	if (bread == SOCKET_ERROR)
	{
		if (WSAGetLastError() == WSAETIMEDOUT)
		{
			closesocket(sockRaw);
			WSACleanup();
			return -1;
		}
		closesocket(sockRaw);
		WSACleanup();
		return -9;
	}
	// 对回应的IP数据包进行解析,定位ICMP数据
	int time = decode_resp(recvbuf, bread, GetCurrentThreadId());
	if (time < 0)
	{
		closesocket(sockRaw);
		WSACleanup();
		return -9;
	}
	ret = time;
	closesocket(sockRaw);
	WSACleanup();
	return ret;
}
int main(int argc, char* argv[])
{
	if (argc != 2)
	{
		cout << "参数数量不正确,请指定要ping的IP地址。" << endl;
		return 0;
	}
	//执行ping操作
	printf("ping %s...\n", argv[1]);
	for (int i = 1; i <= 5; i++)
	{
		int ret = ping(argv[1], 500);
		if (ret >= 0) printf("%s在线,执行ping操作用时%dms。\n", argv[1], ret);
		else if (ret == -1) cout << "ping超时。" << endl;
		else if (ret == -2) cout << "创建套接字出错。" << endl;
		else if (ret == -3) cout << "设置套接字的接收超时选项出错。" << endl;
		else if (ret == -4) cout << "设置套接字的发送超时选项。" << endl;
		else if (ret == -5) cout << "获取域名时出错,可能是IP地址不正确。" << endl;
		else if (ret == -6) cout << "未能为ICMP数据包分配到足够的空间。" << endl;
		else if (ret == -7) cout << "发送ICMP数据包出错。" << endl;
		else if (ret == -8) cout << "发送ICMP数据包的数量不正确。" << endl;
		else if (ret == -9) cout << "接收ICMP数据包出错。" << endl;
		else if (ret == -1000) cout << "初始化Windows Sockets环境出错。" << endl;
		else cout << "未知的错误。" << endl;
	}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值