1)程序预处理
导入库文件、加载头文件和定义常量。
#define _CRT_SECURE_NO_DEPRECATE
#include <stdio.h>
#include <stdlib.h>
#include <winsock.h>
#pragma comment(lib, “ws2_32.lib”)
#define ICMP_ECHOREPLY 0 //ICMP回应答复
#define ICMP_ECHOREQ 8 //ICMP回应请求
#define REQ_DATASIZE 32 //请求数据报大小
2)源代码:
Ping.h:
#define _CRT_SECURE_NO_DEPRECATE
#include <stdio.h>
#include <stdlib.h>
#include <winsock.h>
#pragma comment(lib, “ws2_32.lib”)
#define ICMP_ECHOREPLY 0 //ICMP回应答复
#define ICMP_ECHOREQ 8 //ICMP回应请求
#define REQ_DATASIZE 32 //请求数据报大小
class Ping{
private:
/*IP 报头,标准IPV4占20字节
*/
typedef struct _IPHeader
{
u_char VIHL; //4位版本号和4位首部长度
u_char ToS; //8 位服务类型
u_short TotalLen; //16 位总长度
u_short ID; //16 位标识符: 作用是分片后的重组
u_short Frag_Flags;//3 位标志加 13 位片偏移:总16位
u_char TTL; //8 位生存时间
u_char Protocol; //8 位上层协议号: 指出是何种协议
u_short Checksum; //16 位校验和
struct in_addr SrcIP; //32 位源 IP 地址
struct in_addr DestIP; //32 位目的 IP 地址
}IPHDR;
/*ICMP 报头,一共八个字节,前四个字节为:类型(1字节)、
代码(1字节和检验和(2字节)。后四个字节取决于类型*/
typedef struct _ICMPHeader
{
u_char Type; //8 位类型字段
u_char Code; //8 位代码字段
u_short Checksum; //16 位校验和
u_short ID; //16 位标识符
u_short Seq; //16 位序列号
char Data; //数据
}icmpHd;
typedef struct _ECHOREQUEST //定义ICMP回应请求
{
icmpHd icmpHd;//imcp首部
DWORD dwTime;//请求数据报的发送时间
char cData[REQ_DATASIZE];//请求数据报的数据部分大小
}ECHOREQUEST;
typedef struct _ECHOREPLY //定义ICMP回应答复
{
IPHDR ipHdr;//ip首部
ECHOREQUEST echoRequest;//icmp回应
char cFiller[256]; //数据部分
}ECHOREPLY;
public:
Ping();
u_short checksum(u_short *buffer, int len);
int pack(SOCKET s, struct sockaddr_in *lpstToAddr);
DWORD unpack(SOCKET s, LPSOCKADDR_IN lpsaFrom, u_char *pTTL);
int Waiting(SOCKET s);
void Pingtest(char pstrHost, const char param);};
Ping.cpp:
#define _CRT_SECURE_NO_DEPRECATE
#include “ping.h”
#include <stdio.h>
#include <stdlib.h>
#include <winsock.h>
#pragma comment(lib, “ws2_32.lib”)
Ping::Ping() {};
u_short Ping::checksum(u_short *buffer, int len)//校验和计算方法
{
register u_short answer;
register int sum = 0;
/使用32位累加器,进行16位的反馈计算/
while (len > 1)
{
sum += *buffer++;
len -= 2;
}
/补全奇数位/
if (len == 1)
{
u_short u = 0;
*(u_char *)(&u) = (u_char)buffer;
sum += u;
}
/将反馈的16位从高位移到低位/
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return (answer);
}
int Ping::pack(SOCKET s, struct sockaddr_in IPToAddr) //封装ICMP报文并发送回应请求方法
{
static ECHOREQUEST echoReq; //实例化为请求icmp报文
static int Id = 1; //设置初始标识id
static int Seq = 1; //设置初始序列号
int n;
/为ICMP报文初始化/
echoReq.icmpHd.Type = ICMP_ECHOREQ; //设置为请求类型
echoReq.icmpHd.Code = 0; //请求代码为0
echoReq.icmpHd.Checksum = 0; //初始化为0
echoReq.icmpHd.ID = Id++; //标识自增
echoReq.icmpHd.Seq = Seq++; //序列自增
for (n = 0; n < REQ_DATASIZE; n++)//简单填充一下要发送的ICMP报文的数据部分
{
echoReq.cData[n] = ‘1’ + n;
}
//存储发送时,操作系统启动所经过时间。
/GetTickCount()函数:GetTickCount返回(retrieve)从操作系统启动所经过(elapsed)的毫秒数,它的返回值是DWORD。/
echoReq.dwTime = GetTickCount();
echoReq.icmpHd.Checksum = checksum((u_short)&echoReq, sizeof(ECHOREQUEST)); //计算回应请求的校验和
/发送回应请求/
n = sendto(s, (LPSTR)&echoReq, sizeof(ECHOREQUEST), //sendto() 用来将数据传给对方主机.
0, (struct sockaddr*)IPToAddr, sizeof(SOCKADDR_IN)); //和参数flags 一般设0,参数to 用来指定欲传送的网络地址, 参数tolen 为sockaddr 的结果长度.
if (n == SOCKET_ERROR) //失败返回-1, 错误原因存于errno 中.
{
printf(“send to() 方法发生错误,错误原因:%d\n”, WSAGetLastError()); //将错误原因显示出来
}
return (n); //成功则返回实际传送出去的字符数,
}
DWORD Ping::unpack(SOCKET s, LPSOCKADDR_IN IPFromAddr, u_char *pTTL)//接收并进行解析报文
{
ECHOREPLY echoReply; //定义一个icmp回复
int n;
int Len = sizeof(struct sockaddr_in);
/接收应答回复/
n = recvfrom(s, (LPSTR)&echoReply, sizeof(ECHOREPLY), 0, (LPSOCKADDR)IPFromAddr, &Len);
if (n == SOCKET_ERROR) //检验接收结果
{
printf(“recvfrom() 方法发生错误,错误原因:%d\n”, WSAGetLastError());
}
*pTTL = echoReply.ipHdr.TTL; //记录返回的TTL
return(echoReply.echoRequest.dwTime); //返回应答时间
}
/等待回应答复,使用select模型进行监听是否在读取数据/
int Ping::Waiting(SOCKET s)
{
struct timeval timeout;
fd_set readfds;
readfds.fd_count = 1;
readfds.fd_array[0] = s;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
return(select(1, &readfds, NULL, NULL, &timeout));//监听是否可以从这些文件中读取数据了。
//select函数返回值:负值:select错误 正值:某些文件可读写或出错 0:等待超时,没有可读写或错误的文件
}
/PING功能的主要实现步骤/
void Ping::Pingtest(char *IputHost)
{
SOCKET rawSocket; //定义原始套接字
LPHOSTENT lpHost; //用于存放主机信息的hosten结构:
struct sockaddr_in destIP;
struct sockaddr_in srcIP;
DWORD dwTimeSent;
DWORD time;
u_char cTTL;
int Pingtimes;
int n, Min = 100000, Max = 0, average = 0;
int sent = 4, reveived = 0, lost = 0;
/*创建原始套接字,ICMP类型*/
rawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);//ipv4
if (rawSocket == SOCKET_ERROR)
{
printf("socket() 方法发生错误,错误原因:%d\n", WSAGetLastError());
return;
}
/*检测目标主机*/
lpHost = gethostbyname(IputHost); //获取域名或者32位的网络字节ip地址
//gethostbyname()返回对应于给定主机名的包含主机名字和地址信息的hostent结构的指针
if (lpHost == NULL)
{
printf("找不到目标主机,请确认目标地址输入是否正确:%s\n", IputHost);
return;
}
/*设置目标机地址*/
destIP.sin_addr.s_addr = *((u_long FAR*)(lpHost->h_addr)); //设置目标IP的32位
destIP.sin_family = AF_INET;//设置为IPv4
destIP.sin_port = 0;//假设端口为0
/*提示开始进行PING*/
printf("\n");
printf("以下是Pingtest所得到的信息:");
printf("\n正在Ping %s [%s] 具有 %d 字节的数据\n", IputHost, inet_ntoa(destIP.sin_addr), REQ_DATASIZE);
/*需要显示出点分十进制的IP地址,需要用到inet_ntoa进行转换*/
int i = 0;
if (!strcmp(param, “-t”))
{
i = 1;
}
/发起PING测试/
for (Pingtimes = 0; Pingtimes < 4||i==1; Pingtimes++) {
pack(rawSocket, &destIP); //封装和发送ICMP回应请求
/等待回复的数据/
n = Waiting(rawSocket);
if (n == SOCKET_ERROR)
{
printf(“select() 方法发生错误,错误原因:%d\n”, WSAGetLastError());
break;
}
if (!n)
{
lost++;
printf("\nREQUEST TimeOut.");
continue;//如果超时,跳过后面接收解析过程
}
/* 接收回复*/
dwTimeSent = unpack(rawSocket, &srcIP, &cTTL);
reveived++;
time = GetTickCount() - dwTimeSent; //计算花费的时间
if (time > Max)Max = time; //求最大值
if (time < Min)Min = time; //求最小值
average += time;
printf("\n REPLY FROM %s: bytes = %d time = %ldms TTL = %d",
inet_ntoa(srcIP.sin_addr), REQ_DATASIZE, time, cTTL);
}
printf("\n\n");
printf("%s 的 Ping 统计信息:\n", inet_ntoa(srcIP.sin_addr));
printf(" 数据包: 发送 = %d, 收到 = %d, 丢失 = %d (%.f%% loss),\n",
sent, reveived, lost, (float)(lost*1.0 / sent) * 100);
if (lost == 0)
{
printf(“往返行程的估计时间(以毫秒为单位):\n”);
printf(" 发送时间:最小值 = %dms, 最大值 = %dms, 平均值 = %dms\n", Min, Max, average / sent);
}
printf("\n\n");
n = closesocket(rawSocket);//关闭连接
if (n == SOCKET_ERROR)//当连接不能正常关闭时,返回null
{
printf(“closesocket() error:%d\n”, WSAGetLastError());//将
}
}
Main.cpp:
#define _CRT_SECURE_NO_DEPRECATE
#include “ping.h”
#include <stdio.h>
#include <stdlib.h>
#include <winsock.h>
using namespace std;
#pragma comment(lib, “ws2_32.lib”)
char* isParamEmpty(char *buffer, char *param);
int main()
{
char dstAddr[20];
char noparam[] = “no_param”;
char *param = NULL;
printf("\n");
printf(“请输入你要进行Ping测试的地址\n”);
/*使用gethostname()函数,必须先进行下面的初始化操作*/
WSADATA wsd;//检测输入的参数
//初始化Winsock
if (WSAStartup(MAKEWORD(1, 1), &wsd) != 0)
{
printf("加载 Winsock 失败!\n");
}
while (1) {
cout << "ping ";
dstAddr[0] = '\0';
cin.getline(dstAddr, 20);
if ((param = isParamEmpty(dstAddr, param)) == NULL) param = noparam;
Ping ping = Ping();
ping.Pingtest(dstAddr, param);
cin.clear();//用来更改cin的状态标示符的
cin.sync();//清除缓存区的数据流的
}
WSACleanup();
return 0;
}
char *isParamEmpty(char *buffer, char *param)//判断-t是否输入
{
char *temp = NULL;
temp = buffer;
while (*temp != ‘\0’)
{
if (*temp == ’ ')
{
*temp = ‘\0’;
param = ++temp;
}
temp++;
}
return param;
}
————————————————
版权声明:本文为CSDN博主「Cmy_894」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Cmy_894/article/details/107292670