#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;
}
Windows网络编程——实现ping命令
于 2023-12-01 00:15:43 首次发布