C\C++开发基于ICMP协议的PING程序

本文详细介绍了如何使用C++编写一个原始套接字的ICMP ping程序,涉及WinSock2库的使用、IP地址转换、ICMP报文结构及检验和计算等。代码示例展示了从地址解析到发送接收ICMP请求的完整过程,帮助读者理解网络编程中的ping命令实现。
摘要由CSDN通过智能技术生成

正在ping www.baidu.com的程序运行实例

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6Zi_5be06Zi_5be0MDE=,size_20,color_FFFFFF,t_70,g_se,x_16

源代码片段

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6Zi_5be06Zi_5be0MDE=,size_20,color_FFFFFF,t_70,g_se,x_16

抓包实例 

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6Zi_5be06Zi_5be0MDE=,size_20,color_FFFFFF,t_70,g_se,x_16

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6Zi_5be06Zi_5be0MDE=,size_20,color_FFFFFF,t_70,g_se,x_16

源代码

#include <iostream>
#include <WinSock2.h>
#include <WS2tcpip.h>

//套接字宏错误
#define sockerror(str) cout << #str <<"错误代码:" << WSAGetLastError() << endl;

using namespace std;


//链接WinSock2的动态库
#pragma comment(lib,"ws2_32.lib")


//将字符型数组IP地址导入int 参数:地址数组指针
unsigned int char_int_ip(unsigned char *str)
{
	unsigned int sum = 0;       //准备好容器
	for (int c = 0; c < 4; c++) // 一个IP地址就四位够用了
	{
		sum += *str++;   //让容器加上地址数组第一个字节
		if (c != 3)      //等于三的时候代表最后一位不用再位移
		{
			sum <<= 8;   //加到的结果都右移8位,刚好一个字节,重复三次再加上最后一个char字节完成
		}
	}
	return sum;  //返回结果
}

//ICMP报文格式结构体
struct ICMP_DATA
{
	unsigned char  type;  //类型描述占8位
	unsigned char  code;  //代码描述占8位
	unsigned short chec;  //检验和描述占16位
	unsigned short id;    //用来表示此请求的唯一ID号,通常设置为进程id
	unsigned short seq;   //序列号

}ICMP;

//ICMP头部检验和
u_short head(struct ICMP_DATA* picmp, int len)
{
	long sum = 0;
	unsigned short* pusicmp = (unsigned short*)picmp;
	while (len > 1)
	{
		sum += *(pusicmp++);
		if (sum & 0x80000000)
			sum = (sum & 0xffff) + (sum >> 16);
		len -= 2;
	}
	if (len)
		sum += (unsigned short)*(unsigned char*)pusicmp;
	while (sum >> 16)
		sum = (sum & 0xffff) + (sum >> 16);
	return (unsigned short)~sum;
}

int main(int argc, char** argv)
{
	//键盘敲烂 工资过万 
	cout << "开发者:2849551294@QQ.COM" << endl;

	//先判断用户是否输入了参数
	if (argc < 2 )
	{
		cout << "请键入参数!" << endl;
		return false;
	}

	//定义所需要的变量
	SOCKADDR_IN  desaddr;     //定义套接字地址结构体变量,目的地址
	SOCKADDR_IN  fromaddr;    //接收到的地址
	char fromadd[16] = { 0 }; //为接收回复得到的地址准备
	char fromstr[72] = { 0 }; //为接收到的数据准备
	unsigned char* dnsaddr;   //为指向地址解析准备的无符号字符型指针
	addrinfo  dnsaddinfo, *saveaddr;      //为保存地址解析到的地址信息,第一个用来指定解析地址的类型,第二个保存地址数据
	unsigned int sec = 1, sockopt = 1000; //第一个为序列号准备,第二个为超时时间准备 1000等于1秒
	unsigned char* dadd = new unsigned char[4]; //为保存地址解析的结果准备的4位动态内存

	//开始构造ICMP头部
	char data[64 + 8] = { 0 };          //数组,存放要发送的64位数据与8位ICMP头部
	ICMP_DATA* icmp = (ICMP_DATA*)data; //将数组强转为ICMP头部类型 初始化ICMP头部 不然会报使用未初始化内存的错误

	icmp->type = 8; //ICMP头部类型字段
	icmp->code = 0; //ICMP头部代码字段
	icmp->chec = 0; //ICMP头部检验和字段置零
	icmp->id   = 6; //ICMP头部id字段
	icmp->seq  = 1; //ICMP头部序列号字段
	memcpy((data + 8), "zaima?zaima?zaima?zaima?zaima?zaima?zaima?zaima?zaima?zaima????", 64);//64位数据+8位头部数据
	icmp->chec = head((ICMP_DATA*)data, sizeof(data)); //计算检验和并赋值给头部检验和字段,错一点都会导致失败

	//初始化套接字
	WSADATA wsastat;//传出参数 结构体类型对象
	if (0 != WSAStartup(0X202, &wsastat)) //参数1 主版本号 副版本号 参数2 一个结构体
	{                                         //MAKEWORD将2,2分别放到short的两个字节中
		sockerror(初始化套接字失败!)//获取套接字错误代码
			system("pause");
		return false;
	}

	//解析地址
	memset(&dnsaddinfo, NULL, sizeof(dnsaddinfo));//内存初始化函数
	dnsaddinfo.ai_family = AF_INET;     //使用IPv4地址协议
	dnsaddinfo.ai_socktype = SOCK_STREAM; //套接字类型TCP
	if (0 != getaddrinfo(argv[1], NULL, &dnsaddinfo, &saveaddr)) //解析argv[1]字符串,使用addinfo指定的类型去解析,保存到addr,成功返回0
	{
		sockerror(地址解析失败...)
		return SOCKET_ERROR;
	}

	//输出地址
	dnsaddr = (unsigned char*)saveaddr->ai_addr; //用上面准备的无符号字符型指针指向解析到的地址
	dnsaddr += 4;  //不知道为什么解析到的地址前面多了2.0.0.0四位数,让指向的地址加4略过
	cout << "正在请求";
	for (int i = 0; i < 4; i++) //将解析到的地址打印
	{
		printf("%d.", *dnsaddr++); //打印地址
	}
	dnsaddr -= 4; //再让地址加4回到地址首部,后续还需要使用
	printf("\b 的应答:\n");
	delete[]dadd;  //释放掉创建的动态内存,地址解析已完结

	//开始创建套接字,并设置相关参数
	SOCKET sock = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP);  //创建原始套接字ICMP协议
	if (sock == INVALID_SOCKET)
	{
		sockerror(创建套接字失败!)//获取套接字错误代码
		system("pause");
		return false;             //失败返回false
	}
	setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&sockopt, sizeof(sockopt));//设置接收超时,1000毫秒收不到就跳过接收
	setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&sockopt, sizeof(sockopt));//设置发送超时,1000毫秒发送不成功就跳过
	desaddr.sin_family = AF_INET;  //设置目标地址协议IPv4
	desaddr.sin_addr.S_un.S_addr = htonl(char_int_ip(dnsaddr));//设置目标地址,htonl将地址转为网络字节序
	desaddr.sin_port = htons(0);//端口0 htons主机字节序


	while (true) //循环的发送接收
	{
		sendto(sock, data, sizeof(data), NULL, (sockaddr*)&desaddr, sizeof(desaddr));//发送 1.套件字 2.要发送的数据包含ICMP头 3.一般不使用 4.目的地址结构体 5.目的地址结构体大小

		ULONGLONG time, ptime = GetTickCount64(); //从这里获取时间 给ptime ,time存时间差
		int in = sizeof(fromaddr); //获取接收地址的长度给到in,为formrecv准备。因为类型原因只能这样
		recvfrom(sock, fromstr, sizeof(fromstr), NULL, (SOCKADDR*)&fromaddr, &in);接收 1.套件字 2.存储接收到的数据包含ICMP头 3.一般不使用 4.保存着接收到的地址 5.地址结构体大小
		unsigned char tll = fromstr[8]; //接收到的数据里第9为是tll值
		time = GetTickCount64() - ptime; //再获取一次时间,用这次时间减去上次的时间,得到接收花了多久
		char* ptr = (char*)inet_ntop(AF_INET, &fromaddr.sin_addr, fromadd, sizeof(fromadd)); //将接收到的二进制地址转换成点分十进制地址 ,1.IPv4协议 2.接收到的二进制地址 3.转换完成后保存到的数组 4.数组大小

		char fuhao = 0;
		if (time >= 1000) //如果接收的时间超过设置的超时时间,就是超时
		{
			cout << "请求超时..." << endl;
			continue; //超时了就退出这一次操作,从新发送接收
		}

		if (time == 0) //如果时间等于0毫秒就让它符合变成 < 
		{
			time = 1;  //太快了time会是0的,太难看让它等于1再输出
			fuhao = '<';
		}
		else //如果不是小于1的话就是等于咯
		{
			fuhao = '=';
		}
		if (fromstr[20] == 0 || fromstr[21] == 0)//类型与代码字段都等于0的话就是正常的回复消息
		{                                        //其实还有很多错误处理比如主机不可达等等,作者懒 也不检查数据包完整性了
			cout << "来自-> " << fromadd << " 的回复 " << "字节=64 " << "时间"
				<< fuhao << time << "ms" << " TLL= " << dec << (int)tll << " 序列号->" << sec++ << endl;
		}
		Sleep(500); //设置半秒,不然太快看不清
	}


	return false;
}

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值