1.Ping程序设计具体设计任务

1.1 实验目的

PING程序是我们使用的比较多的用于测试网络连通性的程序。PING程序基于ICMP,使用ICMP的回送请求和回送应答来工作。由计算机网络课程知道,ICMP是基于IP的一个协议,ICMP包通过IP的封装之后传递。

课程设计中选取PING程序的设计,其目的是希望同学们通过PING程序的设计,能初步掌握TCP/IP网络协议的基本实现方法,对网络的实现机制有进一步的认识。

1.2 实验内容和要求

1.2.1 RAW模式的SOCKET编程

PING程序是面向用户的应用程序,该程序使用ICMP的封装机制,通过IP协议来工作。为了实现直接对IP和ICMP包进行操作,实验中使用RAW模式的SOCKET编程。

熟悉SOCKET的编程,包括基本的系统调用如SOCKET、BIND等;

1.2.2 具体内容

1)      定义数据结构

需要定义好IP数据报、ICMP包等相关的数据结构;

2)      程序实现

在WINDOWS环境下实现PING程序;

3)      程序要求

在命令提示符下输入:

PING ΧΧΧ.ΧΧΧ.ΧΧΧ.ΧΧΧ

其中ΧΧΧ为目的主机的IP地址,不要求支持域名,对是否带有开关变量也不做要求。不带开关变量时,要求返回4次响应。

返回信息的格式:

REPLY FROM ΧΧΧ.ΧΧΧ.ΧΧΧ.ΧΧΧ

REQUEST TimeOut  (无法PING通的情况)。

2.程序设计方案

2.1 原理流程框图

2.2 设计主要函数分析

本程序的步骤包括创建套接字、设置接收和发送超时值、分配内存、创建ICMP报文、发送ICMP请求报文、接收ICMP应答报文和解读ICMP报文等。当中调用多个函数从而实现主机间相互Ping通。

(1)int socketInit(char *par_host)初始化操作

通过此函数建立WSADATA结构,并存放windows socket初始化信息。并通过调用函数实现对socket的连接和启用,并记录本地及目的地数据。

(2)ICMP检查和封装算法

ICMP报文格式如下:

8位类型

8位代码

16位校验和

(不同类型和代码有不同的内容)

通过调用unsigned short cal_chksum(unsigned short *addr, int len)  此函数,实现对ICMP包的检查并返回有效字符对ICMP包调用int pack(int pack_no)实行封装。

(3)IP数据包解析函数

IP数据报格式如下:

4位版本

4位首部长度

8位区分服务

16位总长度

16位标识

3位标志

13位片偏移

8位生存时间

8位协议

16位首部检验和

32位源地址

32位目的地址

选项(如果有)

数据部分

通过int unpack(char *buf, int len)    函数对IP数据包有效字节数据进行存储,实现对数据基于ICMP包上再进行封装,并实现数据字节调用输出。

(4)main()函数

主程序中通过调用各函数,实现对socket接口的初始化和对主机间进行连通性测试。并通过函数实现对输入地址中是否包含“-t”标示从而是否无限ping对方。

2.3 程序源代码

#include<Winsock2.h>        //WINSOCK API的头文件

#include<process.h>

#include<iostream>

#include<string>

#include<math.h>

using namespace std;

#define SEND_SIZE 32 //定义包的大小

#define PACKET_SIZE 4096

#define ICMP_ECHO 8

#define ICMP_ECHOREPLY 0    //静态加入一个lib文件

#pragma comment(lib,"Ws2_32.lib")

struct icmp

{

       unsigned char icmp_type;    //类型

       unsigned char icmp_code;    //编码

       unsigned short icmp_chksum; //校验和

       unsigned short icmp_id;     //标示符

       unsigned short icmp_seq;    //顺序号

       unsigned long icmp_data;    //数据

};

struct ip

{

       unsigned char ip_hl : 4;       //报头长度

       unsigned char ip_v : 4;        //版本号

       unsigned char ip_tos;        //服务类型

       unsigned short ip_len;       //总长度

       unsigned short ip_id;        //标识

       unsigned short ip_off;       //标志

       unsigned char ip_ttl;        //生存时间

       unsigned char ip_p;          //协议号

       unsigned short ip_sum;       //报头校验和

       unsigned long ip_src;        //源IP地址

       unsigned long ip_dst;        //目的IP地址

};

//发送包

char sendpacket[PACKET_SIZE];

//接受包

char recvpacket[PACKET_SIZE];

//网络地址

struct sockaddr_in dest_addr;

struct sockaddr_in from_addr;

int sockfd;             //Socket状态变量

int pid;           //程序标志位,取得进程识别码,在process.h下

int socketInit(char *par_host);

unsigned short cal_chksum(unsigned short *addr, int len);

int pack(int pack_no);

int unpack(unsigned char *buf, int len);

void sendPacket(void);

void recvPacket(void);

int main()

{

       int i = 0;

       char *par_host;

       char m_Input[100];

       while(1)

       {

              cout << "ping ";    

              cin.getline(m_Input,40);       //输入一个可含空格的字符数组

              //如果是输入了-t

              if (m_Input[0] == '-'&&m_Input[1] == 't' && m_Input[2] == ' ')

              {

                     char *a = m_Input;

                     par_host = a + 3;

              }

              else  par_host = m_Input;

              socketInit(par_host);                   //初始化socket以及检查socket有效性

              pid = _getpid();                    //程序随机数标志

              //如果为'-t '则循环4次,否则无限循环

              if(m_Input[0] == '-'&&m_Input[1] == 't' &&m_Input[2] == ' ')

              {

                     while(1)

                     {

                            sendPacket();

                            recvPacket();

                            Sleep(1000);

                     }

              }

              else

              {

                     for(i=0;i < 4;i++)

                     {

                            sendPacket();

                            recvPacket();

                            Sleep(1000);

                     }

              }

       }

       system("pause");

       return 0;

}

int socketInit(char *par_host)

{

       struct hostent *host;

       struct protoent *protocol;

       int timeout = 1000;                      //设置发送超时1000ms

       WORD wVersionRequested;                //定义类型,word类型:16位短整数

       WSADATA wsaData;         //建立WSADATA结构,存放windows socket初始化信息

       int err;                         //用来接收函数的返回值,即启动socket时是否出现错误

       wVersionRequested = MAKEWORD(2, 2);        //宏,高字节为2,低字节为2。514

       err = WSAStartup(wVersionRequested, &wsaData);   //启动Socket,WSAStartup函数是连接应用程序与winsock.dll的第一个调用。第一个参数是WINSOCK版本号,第二个参数指向WSADATA指针,该函数返回一个int值

       //wsaData用来存储系统传回的关于WINSOCK的资料

       if (err){

              exit(1);

       };

       if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)

              //如果16进制最低那个字节内容不等于2或最高那个字节不等于2

              //字段wVersion:Windows Sockets DLL期望调用者使用的Windows Sockets规范的版本,为WORD类型

       {

              WSACleanup();          //终止Winsock 2 DLL(Ws2_32.dll)使用

              return 0;

       }

       if ((protocol = getprotobyname("icmp")) == NULL)    //返回对应于给定协议名的包含名字和协议号

       {

              cout << "getprotobyname error" << endl;

              exit(1);

       }           //生成使用ICMP的原始套接字,这种套接字只有root才能生成

       if ((sockfd = socket(AF_INET, SOCK_RAW, protocol->p_proto))<0)       //初始化socket套接口,异常时返回-1

       {

              cout << "socket error" << endl;

              exit(1);

       }

       //回收root权限,设置当前用户权限

      if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout))<0)  //设置套接口的选项,设置接收超时时间

              cout << "failed to set recv timeout: " << endl << WSAGetLastError();          //输出并返回上次网络错误

      if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout))<0)              //设置套接口的选项,设置发送超时时间

              cout << "failed to set send timeout: " << endl << WSAGetLastError();        //输出并返回上次网络错误

       memset(&dest_addr, 0, sizeof(dest_addr));         //设置目标主机初始化,即清零

       dest_addr.sin_family = AF_INET;              //设置地址族

       if (host = gethostbyname(par_host))        // 返回对应于给定主机名的主机信息,par_host是ping的目的主机的Ip                           

       {

              //取指针地址所指的变量,放入函数 memcpy中计算得到结果

              memcpy((char *)&dest_addr.sin_addr, host->h_addr, host->h_length);

              //将获取到的IP值赋给目的地址中相应字段

              if (host = gethostbyaddr(host->h_addr, 4, PF_INET))

                     par_host = host->h_name;          //将ip解析为主机名

       }

       else if (dest_addr.sin_addr.s_addr = inet_addr(par_host) == INADDR_NONE) //查找目标IP地址失败

       {

              cout << "Unkown host " << endl << par_host;

              exit(1);

       }

}

unsigned short cal_chksum(unsigned short *addr, int len)          //ICMP检查算法

{

       int nleft = len;

       int sum = 0;

       unsigned short *w = addr;

       unsigned short answer = 0;

       /*把ICMP报头二进制数据以2字节为单位累加起来*/

       while (nleft>1)

       {

              sum += *w++;

              nleft -= 2;

       }

       if (nleft == 1)

       //处理ICMP报头为奇数个字节时累加最后一个

       {

              /*若ICMP报头为奇数个字节,会剩下最后一字节。把最后一个字节视为一个2字节数据的高字节,这个2字节数据的低字节为0,继续累加*/

              *(unsigned char *)(&answer) = *(unsigned char *)w;

              sum += answer;

       }

       /*校验和是以16位为单位进行求和计算的,sum是32位的,sum&0xffff是低16位,sum>>16是右移16位,取到的是高16位,相加就是高16位和低16位的和。*/

       sum = (sum >> 16) + (sum & 0xffff);

       /*这一步是有可能上面加的时候有进位到高16位的,再把高16位加进来。*/

       sum += (sum >> 16);

       /*上一步肯定不会再有进位了,即使上面sum高16位非0也不要紧,此处sum只能把低字节数赋值给answer,因为answer为16位*/

       answer = ~sum;

       return answer;

}

int pack(int pack_no)                          //封装ICMP包

{

       int packsize;

       struct icmp *icmp;

       packsize = 8 + SEND_SIZE;        //数据报大小为64字节

       icmp = (struct icmp*)sendpacket;

       icmp->icmp_type = ICMP_ECHO;

       icmp->icmp_code = 0;

       icmp->icmp_chksum = 0;

       icmp->icmp_seq = pack_no;        //发送的数据报编号

       icmp->icmp_id = pid;

       icmp->icmp_data = GetTickCount();           //记录发送时间

       icmp->icmp_chksum = cal_chksum((unsigned short *)icmp, packsize); //校验算法

       return packsize;

}

int unpack(char *buf, int len)                                           //解析IP包  

{

       struct ip *ip;

       struct icmp *icmp;

       double rtt;

       int iphdrlen;

       ip = (struct ip *)buf;

       iphdrlen = ip->ip_hl * 4;       /*求ip报头长度,即ip报头的长度标志乘4*/

       icmp = (struct icmp *)(buf + iphdrlen);        /*越过ip报头,指向ICMP报头*/

       /*确保所接收的是我所发的的ICMP的回应*/

       if ((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == pid))

       {

              len = len - iphdrlen - 8;

              rtt = GetTickCount() - icmp->icmp_data;

              cout << "Reply from " << inet_ntoa(from_addr.sin_addr) << ": bytes=" << len << " time=" << rtt << "ms TTL= " << fabs((double)ip->ip_ttl)

                     << endl;

              return 1;

       }

       return 0;

}

void sendPacket()                       //发送ICMP包

{

       int packetsize;       //设置icmp报头

       static int pack_no = 0;

       packetsize = pack(pack_no++);

       //发送数据报

       if (sendto(sockfd, sendpacket, packetsize, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr))<0)

              cout << "Destination host unreachable." << endl;

}

void recvPacket()                        //接收IP包

{

       int n, fromlen;

       int success;

       fromlen = sizeof(from_addr);

       do

       {

              if ((n = recvfrom(sockfd, recvpacket, sizeof(recvpacket), 0, (struct sockaddr *)&from_addr, &fromlen)) >= 0)

                     success = unpack(recvpacket, n);         //剥去ICMP报头

              else if (WSAGetLastError() == WSAETIMEDOUT)

              {

                     cout << "Request timed out." << endl;

                     return;

              }

       } while (!success);

}

  

3.实验结果

不带参数连接时,如图所示:

带参数-t时,如图所示:

4.思考题

1.本题目只要求实现PING的一些简单功能,在Windows命令行模式下,输入“Ping”回车,查看PING的所有功能,考虑如何实现这些功能。

首先Ping命令会构建一个固定格式的ICMP请求数据包,然后由ICMP协议将这个数据包连同地址“10.10.165.140”一起交给IP层协议并以此作为目的IP地址,本机IP地址作为源地址,再加上其他数据信息,构建一个IP数据包。通过在映射表中查找出IP地址10.10.165.140所对应的物理地址,一并交给数据链路层。数据链路层在上一层传递下来的数据包封装构建一个数据帧,并加上必要控制信息传递到物理层,再根据相应协议将它们传送出去。基于此去设计实现ping各种功能。

2.如果一台主机能ping通自己但网络不通,可能是什么原因?

(1)Windows服务器的网络服务功能还没启动

(2)计算机的TCP/IP协议没有与网卡有效的绑定;

(3)可能计算机的网卡安装不正确,也可能是没连通

3.考虑Netstat、Traceroute、ipconfig等网络测试应用程序的工作原理以及使用。

Netstat的工作原理及使用:

(1)显示本地或与之相连的远程机器的连接状态,包括TCP、IP、UDP、ICMP协议的使用情况,了解本地机开放的端口情况.

(2)检查网络接口是否已正确安装,如果在用netstat这个命令后仍不能显示某些网络接口的信息,则说明这个网络接口没有正确连接,需要重新查找原因。

(3)通过加入“-r”参数查询与本机相连的路由器地址分配情况。

(4)检查一些常见的木马等黑客程序,因为任何黑客程序都需要通过打开一个端口来达到与其服务器进行通信的目的。

Traceroute程序的设计是利用ICMP及IP header的TTL。首先,traceroute送出一个TTL是1的IP 数据报到目的地,当路径上的第一个路由器收到这个数据报时,它将TTL减1。此时,TTL变为0了,所以该路由器会将此数据报丢掉,并送回一个「ICMP time exceeded」消息,traceroute 收到这个消息后,便知道这个路由器存在于这个路径上,接着traceroute 再送出另一个TTL是2 的datagram,发现第2 个路由器...... traceroute 每次将送出的数据报的TTL 加1来发现另一个路由器,这个重复的动作一直持续到某个数据报抵达目的地。当数据报到达目的地后,该主机并不会送回ICMP time exceeded消息,因为它已是目的地了。

Ipconfig的工作原理及使用:

(1)查找目标主机的IP地址及其它有关TCP/IP协议的信息。

(2)当用户的网络中设置的是DHCP(动态IP地址配置协议)时,利用Ipconfig可以让用户很方便地了解到所用IPconfig机的IP地址的实际配置情况。它可侦查到本机上所有网络适配的IP地址分配情况,运行”Ipconfig”命令后,窗口中显示了主机名、DNS服务器、节点类型以及主机的相关信息如网卡类型、MAC地址、IP地址、子网掩码以及默认网关等。配置不正确的IP地址或子网掩码是接口配置的常见故障。

其中配置不正确的IP地址有两种情情况:

(1)网号部分不正确。

(2)主机部分不正确。

01Ping程序的设计的更多相关文章

  1. Java学习笔记13---一个循环程序的设计范例

    package welcome; import java.util.Scanner; /* * 一个循环程序的设计范例 * 首先编写仅执行一次的程序(当无循环时) * 循环的设计步骤: * 1.确定程 ...

  2. Java基础-继承-编写一个Java应用程序,设计一个汽车类Vehicle,包含的属性有车轮个数 wheels和车重weight。小车类Car是Vehicle的子类,其中包含的属性有载人数 loader。卡车类Truck是Car类的子类,其中包含的属性有载重量payload。每个 类都有构造方法和输出相关数据的方法。最后,写一个测试类来测试这些类的功 能。

    #29.编写一个Java应用程序,设计一个汽车类Vehicle,包含的属性有车轮个数 wheels和车重weight.小车类Car是Vehicle的子类,其中包含的属性有载人数 loader.卡车类T ...

  3. 12个优秀用户体验的移动应用程序 UI 设计

    最美丽的,现代化的和惊人的移动 UI 设计就在这里.今天,我们挑选了12个来自 Behance 和 Dribbble 网站的优秀用户体验的手机界面设计.这些界面设计作品都是由世界各地的优秀设计师分享, ...

  4. 201521123118《java程序与设计》第4周作业总结

    1.本周学习总结 1.1 尝试使用思维导图总结有关继承的知识点 1.2 使用常规方法总结其他上课内容. 为了不必要写重复的代码,可以运用继承,用关键字extends来定义一个类,被继承的类叫做父类,继 ...

  5. Java程序算法设计视频分享,需要的来

    每年都会有人说,IT行业饱和了,根本就找不到工作,其实,我想说的是,不是工作难找,而是你自己不够好! 前几天看到一CEO在微博上吐槽: 前几天招一算法工程师我们给了8万月薪*14+奖金,人家去阿里拿5 ...

  6. ASP.NET -- WebForm -- Cookie的使用 应用程序权限设计 权限设计文章汇总 asp.net后台管理系统-登陆模块-是否自动登陆 C# 读写文件摘要

    ASP.NET -- WebForm -- Cookie的使用 ASP.NET -- WebForm --  Cookie的使用 Cookie是存在浏览器内存或磁盘上. 1. Test3.aspx文件 ...

  7. C#网络编程TCP通信实例程序简单设计

    C#网络编程TCP通信实例程序简单设计 采用自带 TcpClient和TcpListener设计一个Tcp通信的例子 只实现了TCP通信 通信程序截图: 压力测试服务端截图: 俩个客户端链接服务端测试 ...

  8. Windows Phone实用教程:利用Blend为程序添加设计时数据

    [前言] Blend自诞生那一天起就伴随这开发者如此的评价: 有VS还用Blend干啥,直接码代码就好了. Blend会生成一堆垃圾无用代码,很不爽. 对于这类我只会在心里评价,当你并不真正了解一样事 ...

  9. 中国象棋程序的设计与实现(十一)--第2次回答CSDN读者的一些问题

    最近一段时间,有不少CSDN读者朋友看了我写的中国象棋文章.其中,不少爱好者下载了中国象棋程序的初级版和高级版源码. 由于水平有限,不少同学遇到了若干问题,向我咨询,寻找解决办法. 我的处境1.如果我 ...

随机推荐

  1. POJ2063【完全背包】

    题意: 给一个初始的钱,年数, 然后给出每个物品的购买价格 与 每年获得的利益, 求在给出的年份后手上有多少钱. 思路: 背包重量还是资金. dp[0]=初始资金: 重物的重量是他的价格,获利是价值. ...

  2. combobox级联检索下拉选择框

    1.效果图 2.前端 @{ ViewBag.Title = "Index"; Layout = null; @*自动筛选下拉框*@ <script src="~/S ...

  3. Django Views and URLconfs

    碧玉妆成一树高,万条垂下绿丝绦. 不知细叶谁裁出,二月春风似剪刀. 原文尽在:http://djangobook.com/ 转载请注明出处:http://www.cnblogs.com/A-FM/p/ ...

  4. 关于ios7 以上版本 view被导航栏遮挡的问题 解决方案

    self.edgesForExtendedLayout = UIRectEdgeNone; 如果导航栏是默认带磨砂透明效果的,使用了edgesForExtendedLayout可能会出现导航栏变不透明 ...

  5. 一个Nice的生活主题博客模板

    https://www.bitcron.com/ https://api.bitcron.com/ https://chopstack.com/

  6. 金蝶Apusic中间件适配JetSpeed2过程记录

    金蝶Apusic中间件适配JetSpeed2过程记录: 1.安装金蝶并配置域,确保域运行正常. 2.参考<JetSpeed2部署至Apusic操作步骤记录>进行应用迁移. https:// ...

  7. [已读]响应式web设计

    去年冲着响应式这三个字买的,很快就读完了,因为说实话都挺浅显的内容.真正涉及到响应式的是第二和第三章(媒体查询 em 百分比图片),其他的h5与css3关系不大.

  8. Python打开目录下所有文件

    用Python打开指定目录下所有文件,统计文件里特定的字段信息. 这里是先进入2017-02-25到2017-03-03目录,然后进入特定IP段目录下,最后打开文件进行统计 import os, gl ...

  9. qconbeijing2017

    http://2017.qconbeijing.com/schedule 第一天 (2017年4月16日/星期日)   签到 专题 主题演讲 快速进化的容器生态 微服务与 DevOps 最佳实践(厂商 ...

  10. P2006 赵神牛的游戏

    题目描述 在DNF 中,赵神牛有一个缔造者,他一共有k点法力值,一共有m个技能,每个技能耗费的法力值为a[i],可以造成的伤害为b[i],而boss 的体力值为n,请你求出它放哪个技能,才可以打死bo ...