一、题目
Ping
二、环境
win10 codeblocks
三、代码实现
#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
using namespace std;
#pragma comment(lib, "Ws2_32.lib")
//IP 报头
typedef struct
{
unsigned char hdr_len:4; //4 位头部长度
unsigned char version:4; //4 位版本号
unsigned char tos; //8 位服务类型
unsigned short total_len; //16 位总长度
unsigned short identifier; //16 位标识符
unsigned short frag_and_flags; //3 位标志加 13 位片偏移
unsigned char ttl; //8 位生存时间
unsigned char protocol; //8 位上层协议号
unsigned short checksum; //16 位校验和
unsigned long sourceIP; //32 位源 IP 地址
unsigned long destIP; //32 位目的 IP 地址
} IP_HEADER;
//ICMP 报头
typedef struct
{
BYTE type; //8 位类型字段
BYTE code; //8 位代码字段
USHORT cksum; //16 位校验和
USHORT id; //16 位标识符
USHORT seq; //16 位序列号
} ICMP_HEADER;
//报文解码结构
typedef struct
{
USHORT usSeqNo; //序列号
DWORD dwRoundTripTime; //往返时间
in_addr dwIPaddr; //返回报文的 IP 地址
}DECODE_RESULT;
//计算网际校验和函数
USHORT checksum(USHORT *pBuf,int iSize)
{
unsigned long cksum=0;
while(iSize>1)
{
cksum+=*pBuf++;
iSize-=sizeof(USHORT);
}
if(iSize)
{
cksum+=*(UCHAR *)pBuf;
}
cksum=(cksum>>16)+(cksum&0xffff);
cksum+=(cksum>>16);
return (USHORT)(~cksum);
}
//对数据包进行解码
BOOL DecodeIcmpResponse(char * pBuf,int iPacketSize,DECODE_RESULT &DecodeResult,BYTE
ICMP_ECHO_REPLY,BYTE ICMP_TIMEOUT)
{
//检查数据报大小的合法性
IP_HEADER* pIpHdr = (IP_HEADER*)pBuf;
int iIpHdrLen = pIpHdr->hdr_len * 4;
if (iPacketSize < (int)(iIpHdrLen+sizeof(ICMP_HEADER)))
return FALSE;
//根据 ICMP 报文类型提取 ID 字段和序列号字段
ICMP_HEADER *pIcmpHdr=(ICMP_HEADER *)(pBuf+iIpHdrLen);
USHORT usID,usSquNo;
if(pIcmpHdr->type==ICMP_ECHO_REPLY) //ICMP 回显应答报文
{
usID=pIcmpHdr->id; //报文 ID
usSquNo=pIcmpHdr->seq; //报文序列号
}
else if(pIcmpHdr->type==ICMP_TIMEOUT)//ICMP 超时差错报文
{
char * pInnerIpHdr=pBuf+iIpHdrLen+sizeof(ICMP_HEADER); //载荷中的 IP 头
int iInnerIPHdrLen=((IP_HEADER *)pInnerIpHdr)->hdr_len*4; //载荷中的 IP 头长
ICMP_HEADER * pInnerIcmpHdr=(ICMP_HEADER *)(pInnerIpHdr+iInnerIPHdrLen);//载荷中的 ICMP 头
usID=pInnerIcmpHdr->id; //报文 ID
usSquNo=pInnerIcmpHdr->seq; //序列号
}
else
{
return false;
}
//检查 ID 和序列号以确定收到期待数据报
if(usID!=(USHORT)GetCurrentProcessId()||usSquNo!=DecodeResult.usSeqNo)
{
return false;
}
//记录 IP 地址并计算往返时间
DecodeResult.dwIPaddr.s_addr=pIpHdr->sourceIP;
DecodeResult.dwRoundTripTime=GetTickCount()-DecodeResult.dwRoundTripTime;
//处理正确收到的 ICMP 数据报
if (pIcmpHdr->type == ICMP_ECHO_REPLY ||pIcmpHdr->type == ICMP_TIMEOUT)
{
//输出往返时间信息
if(DecodeResult.dwRoundTripTime)
cout<<" 在线 ping: "<<DecodeResult.dwRoundTripTime<<"<"<<"ms"<<">"<<flush;
else
cout<<" 在线 "<<"<1ms"<<">"<<flush;
}
return true;
}
int main()
{
//初始化 Windows sockets 网络环境
WSADATA wsa;
WSAStartup(MAKEWORD(2,2),&wsa);
char IpAddress[255];
//cout<<"请输入起始IP地址或域名:";
//cin>>IpAddress;
//for(int i=51;i<=81;i++) {
for(int i=30;i<=81;i++) {
//string s="10.1.12.";
string s="10.152.110.";
s+=to_string(i);
//得到 IP 地址
strcpy(IpAddress, s.c_str());
u_long ulDestIP = inet_addr(IpAddress);
cout<<IpAddress ;
//转换不成功时按域名解析
if (ulDestIP == INADDR_NONE) {
hostent *pHostent = gethostbyname(IpAddress);
if (pHostent) {
ulDestIP = (*(in_addr *) pHostent->h_addr).s_addr;
} else {
cout << "输入的 IP 地址或域名无效!" << endl;
WSACleanup();
//return 0;
}
}
//填充目地端 socket 地址
sockaddr_in destSockAddr;
ZeroMemory(&destSockAddr, sizeof(sockaddr_in));
destSockAddr.sin_family = AF_INET;
destSockAddr.sin_addr.s_addr = ulDestIP;
//创建原始套接字
SOCKET sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0,
WSA_FLAG_OVERLAPPED);
//超时时间
int iTimeout = 3000;
//接收超时
setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char *) &iTimeout, sizeof(iTimeout));
//发送超时
setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char *) &iTimeout, sizeof(iTimeout));
//构造 ICMP 回显请求消息,并以 TTL 递增的顺序发送报文
//ICMP 类型字段
const BYTE ICMP_ECHO_REQUEST = 8; //请求回显
const BYTE ICMP_ECHO_REPLY = 0; //回显应答
const BYTE ICMP_TIMEOUT = 11; //传输超时
//其他常量定义
const int DEF_ICMP_DATA_SIZE = 32; //ICMP 报文默认数据字段长度
const int MAX_ICMP_PACKET_SIZE = 1024;//ICMP 报文最大长度(包括报头)
const DWORD DEF_ICMP_TIMEOUT = 3000; //回显应答超时时间
const int DEF_MAX_HOP = 50; //最大跳站数
//填充 ICMP 报文中每次发送时不变的字段
char IcmpSendBuf[sizeof(ICMP_HEADER) + DEF_ICMP_DATA_SIZE];//发送缓冲区
memset(IcmpSendBuf, 0, sizeof(IcmpSendBuf)); //初始化发送缓冲区
char IcmpRecvBuf[MAX_ICMP_PACKET_SIZE]; //接收缓冲区
memset(IcmpRecvBuf, 0, sizeof(IcmpRecvBuf)); //初始化接收缓冲区
ICMP_HEADER *pIcmpHeader = (ICMP_HEADER *) IcmpSendBuf;
pIcmpHeader->type = ICMP_ECHO_REQUEST; //类型为请求回显
pIcmpHeader->code = 0; //代码字段为 0
pIcmpHeader->id = (USHORT) GetCurrentProcessId(); //ID 字段为当前进程号
memset(IcmpSendBuf + sizeof(ICMP_HEADER), 'E', DEF_ICMP_DATA_SIZE);//数据字段
USHORT usSeqNo = 0; //ICMP 报文序列号
int iTTL = 30; //TTL 初始值为 1
BOOL bReachDestHost = FALSE; //循环退出标志
int iMaxHot = DEF_MAX_HOP; //循环的最大次数
DECODE_RESULT DecodeResult; //传递给报文解码函数的结构化参数
while (!bReachDestHost && iMaxHot--) {
int flag=0;
//设置 IP 报头的 TTL 字段
setsockopt(sockRaw, IPPROTO_IP, IP_TTL, (char *) &iTTL, sizeof(iTTL));
//cout<<iTTL<<flush; //输出当前序号
//填充 ICMP 报文中每次发送变化的字段
((ICMP_HEADER *) IcmpSendBuf)->cksum = 0; //校验和先置为 0
((ICMP_HEADER *) IcmpSendBuf)->seq = htons(usSeqNo++); //填充序列号
((ICMP_HEADER *) IcmpSendBuf)->cksum = checksum((USHORT *) IcmpSendBuf,
sizeof(ICMP_HEADER) + DEF_ICMP_DATA_SIZE); //计算校验和
//记录序列号和当前时间
DecodeResult.usSeqNo = ((ICMP_HEADER *) IcmpSendBuf)->seq; //当前序号
DecodeResult.dwRoundTripTime = GetTickCount(); //当前时间
//发送 TCP 回显请求信息
sendto(sockRaw, IcmpSendBuf, sizeof(IcmpSendBuf), 0, (sockaddr *) &destSockAddr, sizeof(destSockAddr));
//接收 ICMP 差错报文并进行解析处理
sockaddr_in from; //对端 socket 地址
int iFromLen = sizeof(from); //地址结构大小
int iReadDataLen; //接收数据长度
while (1) {
//接收数据
iReadDataLen = recvfrom(sockRaw, IcmpRecvBuf, MAX_ICMP_PACKET_SIZE, 0, (sockaddr *) &from, &
iFromLen);
if (iReadDataLen != SOCKET_ERROR)//有数据到达
{
//对数据包进行解码
if (DecodeIcmpResponse(IcmpRecvBuf, iReadDataLen, DecodeResult, ICMP_ECHO_REPLY, ICMP_TIMEOUT)) {
//到达目的地,退出循环
if (DecodeResult.dwIPaddr.s_addr == destSockAddr.sin_addr.s_addr)
bReachDestHost = true;
//输出 IP 地址
cout << "\t 目的IP为:" << inet_ntoa(DecodeResult.dwIPaddr) << endl;
break;
}
} else if (WSAGetLastError() == WSAETIMEDOUT) //接收超时,输出*号
{
flag=1;
cout << " 不在线" << endl;
break;
} else {
break;
}
}
//iTTL++; //递增 TTL 值
if(flag==1)
break;
}
}
}
四、实验结果