计算机网络课程设计之Tracert与Ping程序设计与实现

计算机⽹络课程设计之Tracert与Ping程序设计与实现
⼀、预备知识
ICMP
ICMP的报⽂是封装在IP数据部分中的。按照我的理解,ICMP就是在⽹络层中,反馈⼀些转发、访问等操作时的附带信息。
ICMP分为两种,ICMP差错报告报⽂(IP传输时的反馈)和ICMP询问报⽂(主动发起检查)。具体类型值和作⽤如下:
3 终点不可达
11 时间超过
12 参数问题: IP⾸部数据有问题
5 改变路由: 规定发送到某⽬的的IP,经过某路由
8或0 回送请求或回答:向某台主机询问,主机必须给出某种回答
13或14 时间戳请求或回答:向某台主机询问时间。
ICMP的应⽤之tracert
⽤于测试到达某IP地址所需的TTL(跳数),往返时间。
原理:
  源主机向⽬的主机发送⼀连串IP数据报,数据报封装的是⽆法交付的UDP(使⽤错误的端⼝号,好坏的)。
  第⼀个数据包的⽣存时间TLL设置为1,当P1达到路径上的第⼀个路由时,路由器R1就收下,然后把TLL减1,这时TLL为0,R1就丢弃数据报,然后向源主机发送⼀个ICMP时间超过的差错报告报⽂。⼀直做下去,直到最后⼀个数据报到达⽬的主机,这是数据报的TTL是1。由于已经到达了⽬的地,那么主机收下数据报,且不做减⼀操作(TLL为1)。但是数据报的错误的,因此⽬的主机就会发⼀个ICMP终点不可达差错报告报⽂。
下图的三个时间是因为每⼀次都发送三个相同的数据报。
ICMP的应⽤之ping
向⽬的主机发送询问时间请求(ICMP中的13), ⽬的主机收到请求时,发回当前时间戳(ICMP中的14),因此利⽤时间戳可以计算出往返时间。
⼆、实验部分:Tracert 与 Ping 程序设计与实现
参照附录 2,了解 Tracert 程序的实现原理,并调试通过。然后参考 Tracert 程序和教材 4.4.2 节,编写⼀个 Ping 程序,并能测试本局域⽹的所有机器是否在线,运⾏界⾯如图 1 所⽰的 QuickPing 程序。
实现之前带着的疑问?
1) 报⽂的具体组成?如何将ICMP数据部分 + ICMP数据头组成ICMP数据报。再将ICMP数据报加⼊IP数据中,最后让IP数据部分加上IP 数据头构成IP数据报?
聂绀弩刑事档案2) IP报⽂通过什么⽅法解析,可以得到IP数据报的头和数据部分。然后数据部分如何解析出ICMP报⽂的头和数据部分?
江西理工大学学报3)包装好的IP报⽂通过什么通道传输。
疑问解答
完整的代码我放到最后,在visual stdio下,关闭sdk检查,完美运⾏,下⾯对于程序的个⼈理解。
问题⼀:报⽂的组成
这⼀步是为了将希望传递的信息封装成char sendRev[],数据缓冲区也就是字符串数组。不过socket帮我们封装了⼀个⽅法,让我们不⽤具体构造到字符数组。这⾥后⾯再说。
通过参数构造成ICMP头部结构体,然后再加上想要的ICMP数据部分。再构造出IP头部,把ICMP报⽂加到IP数据部分之前。这样完整的IP 数据报就完成了。
通过结构体构造出ICMP数据头
//ICMP 报头,⼀共⼋个字节,前四个字节为:类型(1字节)、代码(1字节)和检验和(2字节)。后四个字节取决于类型
typedef struct
{
BYTE type; //8 位类型字段:标识ICMP的作⽤
BYTE code; //8 位代码字段
USHORT cksum; //16 位校验和
USHORT id; //16 位标识符
USHORT seq; //16 位序列号
} ICMP_HEADER;
通过结构体构造出IP数据头
//IP 报头,标准IPV4占20字节
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;
问题⼆:IP数据报的解析
接收到的数据缓存是字符数组 char bufRev[],因此需要通过特定的解析(也就是拆成⼀段⼀段的)获取想要的信息。
另外为了⽅便存取信息,这⾥⼜写了⼀种DECODE_RESULT,解码信息的结构体。把信息封装到结构体中,就⽐较⽅便的得到序列号、往返时间和⽬的IP了。
//报⽂解码结构
typedef struct
{
USHORT usSeqNo; //序列号
DWORD dwRoundTripTime; //往返时间
in_addr dwIPaddr; //返回报⽂的 IP 地址
}DECODE_RESULT;
这⾥还需知识储备,就是字符串转结构体指针这种写法。它会把字符数组中的内容按顺序赋值到结构体中。
char 占1个字节
int 占4个字节
unsigned char a[] = "0123456789abcdefghijk";  //⽆符号字符数组
struct A        //结构体A,⼀个int 三个char 再接⼀个int
{
unsigned int a;
unsigned char b;
unsigned char c;
unsigned char d;
unsigned int e;
} *pp;
pp = (A*) a;
cout<< (*pp).a <<' '<<(*pp).b <<' '<<(*pp).c <<' '<<endl;
有了上⾯的知识储备,那么如何解析IP数据报(字符数组)就⽐较好理解了,通过特定的地址偏移,就能把字符数组赋值到IP、ICMP结构体中了
具体的解析函数,⼤部分都打上了注释
湖南城市学院图书馆// 1)接收到的Buf 2)接收到的数据长度 3)解析结果封装到Decode 4)ICMP类型 ECHO_REPLY(是⼀个常量,放到全局也⾏) 5)ICMP类型 TIMEOUT BOOL DecodeIcmpResponse(char * pBuf, int iPacketSize, DECODE_RESULT &DecodeResult, BYTE
ICMP_ECHO_REPLY, BYTE ICMP_TIMEOUT)
{
//查数据报⼤⼩合法性
//pBuf的⾸地址,就是IP报的⾸地址,因此偏移0
IP_HEADER *pIpHdr = (IP_HEADER*)pBuf;
int iIpHdrLen = pIpHdr->hdr_len * 4;
if(iPacketSize < (int)(iIpHdrLen + sizeof(ICMP_HEADER)))
return FALSE;
// 根据 ICMP 报⽂类型提取 ID 字段和序列号字段
打火机组装
//ICMP字段包含在 IP数据段的起始位置,因此偏移IP头长度,得到的就是ICMP头
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)
{
// 如果是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 << " " << DecodeResult.dwRoundTripTime << "ms" << flush;
else
cout << " " << "<1ms" << flush;
}
return true;
}
崔莎问题三:传输的通道
最后这个问题也是我⽐较困惑的,原因是Socket把底层封装好了,我们只需把参数(发送的IP结构、ICMP结构、⽬的主机地址结构体)填好,传递到sendto()函数⾥⾯,就能把IP数据报发送到⽬的主机。通过调⽤recv就能得到⽬的主机的反馈。⽬前我也没到更加底层的分析,因此也只停留在会⽤⽽已。
说⼀下这个程序⼲了什么事,如何使⽤
效果

本文发布于:2024-09-21 13:21:09,感谢您对本站的认可!

本文链接:https://www.17tex.com/xueshu/14408.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:数据   时间   部分
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议