计算机网络——Tracert与Ping程序设计与实现
一、实验目的二、总体设计1. 基本原理2. 设计步骤
三、详细设计1. 程序流程图2. 实验代码
四、实验结果
一、实验目的
了解Tracert程序的实现原理,并调试通过。
二、总体设计
1. 基本原理
tracert(跟踪路由)是路由跟踪实用程序,用于确定IP数据包访问目标所采取的路径。tracert 有一个固定的时间等待响应(ICMP TTL到期消息)。如果这个时间过了,它将打印出一系列的*号表明:在这个路径上,这个设备不能在给定的时间内发出ICMP TTL到期消息的响应。然后,Tracert给TTL记数器加1,继续进行。
2. 设计步骤
(1)加载套接字,创建套接字库; 使用Socket的程序在使用Socket之前必须调用WSAStartup函数,以后应用程序就可以调用所请求的Socket库中的其他Socket函数了。 (2)用inet_addr()将输入的点分十进制的IP地址转换为无符号长整型数,转换不成功时,按域名解析得到IP地址; gethostbyname()是查找主机名最基本的函数,如果调用成功,就返回一个指向hosten结构的指针,该结构中含有对应于给定主机名的主机名字和地址信息,用来承接域名解析的结构。 (3)设置发送接收超时时间,即请求超时,设置接收、发送超时的套接字; (4)构造ICMP回显请求消息,并以TTL递增顺序发送报文,填充ICMP报文中每次发送时不变的字段,构造ICMP头; (5)设置IP报头的TTL字段,填充ICMP报文中每次发送变化的字段,记录序列号和当前时间; (6)指定对方信息,发送TCP回显请求信息; sendto()函数利用数据表的方式进行数据传输,指定哪个socket发送给对方 (7)接收ICMP差错报文并进行解析:如果有数据到达,解析数据包,如果到达目的地址,输出IP地址;如果没有数据到达,输出接收超时,递增TTL值,TTL增为最大时,若还没有到达目的地址,退出循环,输出目的地址不在线; recvform()利用数据报方式进行数据传输,当recvfrom()返回时,(sockaddr*)&from包含实际存入from中的数据字节数。Recvfrom函数返回接收到的字节数或当出现错误时返回-1,并置相应的errno。 (8)重复(2)-(7),实现查找一个范围内的IP地址。
三、详细设计
1. 程序流程图
2. 实验代码
#include
#include
#include
#include
#include
using namespace std;
#pragma comment(lib, "Ws2_32.lib")
const int ipAddressSize = 14;
//int count11=0;
//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 位片偏移: 标志:MF 1是否还有分配 0 没有分片了
// DF 0 可以分片
// 片偏移:分片后的相对于原来的偏移
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 报头,一共八个字节,前四个字节为:类型(1字节)、代码(1字节)和检验和(2字节)。后四个字节取决于类型
typedef struct
{
BYTE type; //8 位类型字段:标识ICMP的作用
BYTE code; //8 位代码字段
USHORT cksum; //16 位校验和
USHORT id; //16 位标识符
USHORT seq; //16 位序列号
} ICMP_HEADER;
//报文解码结构
//接收到的数据缓存是字符数组char bufRev[],因此需要通过特定的解析(也就是拆成一段一段的)获取想要的信息
//把信息封装到结构体中,就比较方便的得到序列号、往返时间和目的IP了。
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);
}
//对数据包进行解码
// 1)接收到的Buf 2)接收到的数据长度 3)解析结果封装到Decode 4)ICMP回显类型 5)TIMEOUT时间
BOOL DecodeIcmpResponse2(char * pBuf, int iPacketSize, DECODE_RESULT &DecodeResult, BYTE
ICMP_ECHO_REPLY, BYTE ICMP_TIMEOUT)
{
//查找数据报大小合法性
//pBuf的首地址,就是IP报的首地址
IP_HEADER *pIpHdr = (IP_HEADER*)pBuf;
int iIpHdrLen = pIpHdr->hdr_len * 4;
if(iPacketSize type == ICMP_ECHO_REPLY) // ICMP 回显应答报文
{
usID = pIcmpHdr->id;//报文 ID
usSquNo = pIcmpHdr->seq;//报文序列号
}
else if (pIcmpHdr->type == ICMP_TIMEOUT)//ICMP超时差错报文
{
// 如果是TIMEOUT ,那么在ICMP数据包中,会夹带一个IP报(荷载IP)
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_un.S_addr = pIpHdr->sourceIP;
DecodeResult.dwRoundTripTime = GetTickCount() - DecodeResult.dwRoundTripTime;
//处理正确收到的 ICMP 数据包
if (pIcmpHdr->type == ICMP_ECHO_REPLY || pIcmpHdr->type == ICMP_TIMEOUT)
{
// 输出往返时间信息
if (DecodeResult.dwRoundTripTime)
cout
// 执行,单线程执行,实现后改成多线程
//得到IP地址
u_long ulDestIP = inet_addr(nextIpAddress);//inet_addr()的功能是将一个点分十进制的IP转换成一个无符号长整型数
//转换不成功时按域名解析
if (ulDestIP == INADDR_NONE)
{
//gethostbyname()是查找主机名最基本的函数
//如果调用成功,就返回一个指向hosten结构的指针
//该结构中含有对应于给定主机名的主机名字和地址信息,用来承接域名解析的结构
hostent * pHostent = gethostbyname(nextIpAddress);
if (pHostent)//调用成功
{
//得到IP地址
//套了两层,IP和ICMP,ICMP是套在IP里面的
//h_addr返回主机IP地址
//in_addr返回报文的IP地址
//sin_addr.s_addr指向IP地址
ulDestIP = (*(in_addr*)pHostent->h_addr).s_addr;
}
else
{
cout
//接收数据
//recvfrom()利用数据报方式进行数据传输
//当recvfrom()返回时,(sockaddr*)&from包含实际存入from中的数据字节数。
//Recvfrom()函数返回接收到的字节数或当出现错误时返回-1,并置相应的errno。
iReadDataLen = recvfrom(sockRaw, IcmpRecvBuf, MAX_ICMP_PACKET_SIZE, 0, (sockaddr*)&from, &
iFromLen);
if (iReadDataLen != SOCKET_ERROR) // 有数据到达
{
//解析数据包
// 1)接收到的Buf 2)接收到的数据长度 3)解析结果封装到Decode 4)ICMP回显类型 5)TIMEOUT时间
if (DecodeIcmpResponse2(IcmpRecvBuf, iReadDataLen, DecodeResult, ICMP_ECHO_REPLY, DEF_ICMP_TIMEOUT))
{
// 到达目的地,退出循环
//返回报文的IP地址等于输入的IP地址
if (DecodeResult.dwIPaddr.S_un.S_addr == destSockAddr.sin_addr.S_un.S_addr)
{
bReachDestHost = true;
// 输出 IP 地址
//inet_ntoa()功能是将网络地址转换成“.”点隔的字符串格式。
cout
break;
}
}
iTTL++;//递增TTL值
}
cout
if (nowIp[i] == '.')
{
z[idxIp][idxj] = '\0';
idxIp++;
idxj = 0;
continue;
}
z[idxIp][idxj++] = nowIp[i];
}
z[idxIp][idxj] = '\0';
//for (int i = 0; i < 4; i++)
//{
// puts(z[i]);
//}
//cout = 0; i--)
{
if (strcmp("254", z[i]) == 0)
{
strcpy(z[i], "1"); // 这里让ip 1-254
}
else
{
int x;
x = atoi(z[i]) + 1;
//stringstream ss;
//ss |