计算机网络实验报告资料.docx
《计算机网络实验报告资料.docx》由会员分享,可在线阅读,更多相关《计算机网络实验报告资料.docx(18页珍藏版)》请在冰豆网上搜索。
计算机网络实验报告资料
课程设计
课程名称
题目名称
学生学院
专业班级
学号
学生姓名
指导教师
2015年12月28日
一、课程设计目的与意义
二、课程设计的要求
三、设计说明
3.1、设计思路
3.2、设计方案
3.3、运行环境
3.4、设计重点
四、详细设计
4.1、主程序流程图
4.2、校验和函数、释放资源函数流程图
4.3、ICMP报首部函数流程图
五、程序的结果与分析
六、课程设计心得体会
附录一:
参考文献
附录二:
程序源代码
一、课程设计的目的与意义
利用ICMP数据包、C语言实现Ping命令程序,能实现基本的Ping操作,发送ICMP回显请求报文,用于测试—个主机到只一个主机之间的连通情况。
通过本程序的训练,熟悉ICMP报文结构,对ICMP有更深的理解,掌握Ping程序的设计方法,掌握网络编程的方法和技巧,从而编写出功能更强大的程序。
二、课程设计的要求
1.已知参数:
目的节点IP地址或主机名
2.设计要求:
通过原始套接字编程,实现Ping的基本功能
2.1初始化WindowsSockets网络环境;
2.2解析命令行参数,构造目的端socket地址;
2.3定义IP、ICMP报文;
2.4接收ICMP差错报文并进行解析。
三、设计说明
1.设计思路
由于Ping程序是面向用户的应用程序,该程序使用ICMP的封装机制,通过IP协议来工作。
为了实现直接对IP和ICMP包进行操作,实验中使用RAW模式的socket编程。
首先定义IP数据报首部,在IP数据报的基础上定义ICMP数据报首部,并初始化一些全局变量。
接着自定义填充ICMP数据报字段函数FillICMPData()、校验和函数checksum()、解读ICMP报首部函数DecodeICMPHeader()、释放资源函Cleanup()。
最后主函数通过调用这些函数来实现Ping命令功能。
2.设计方案
IP头与ICMP头的设置分别参照RFC791及RFC792的标准,包含所有必要信息。
主程序设置main()函数,主函数用库函数实现套接字编程用于数据包发送及接收,其中,数据包发送调用sendto(),数据包接收调用recvfrom(),由于发送数据包时可能会遇到阻塞或者目标主机不通,造成超时,因此需要在发送数据包后调用一个函数判断是否超时,此处调用库函数setsockopt()来实现超时判断;其次,校验和函数采用移位方法进行计算。
3.系统运行环境:
VC++6.0,Window7操作系统平台
4.设计中的重点
首先遇到的问题就是套接字文件的问题。
套接字所需要的文件有头文件Winsocket2.h、库文件WS2_32.LIB、动态库W32_32.DLL。
创建套接字的时候参数的以及在创建套接字之前必须首先使用WSAStartup函数、在使用完套接字之后要释放内存资源,关闭套接字这些问题都是以前未接触过的。
所以在写程序的时候需要查阅大量的资料,弄懂这些问题。
其次,在套接字问题解决之后,遇到的难题,也是比较重要的问题就是如何实现ICMP报文的发送和接受,以及怎样判断发送、接收超时或者找不到目的主机。
最后在程序调试的时候总是出现这样或那样的错误,比如头文件错误、动态库无法导入、编辑器环境不匹配等。
四、详细设计
1、本程序主要是通过main()函数调用自定义函数以及其本身的一些功能,例如:
打开socket动态库、设置接收和发送超时值、域名地址解析、分配内存、创建及初始化ICMP报文、发送ICMP请求报文、接收ICMP应答报文以及解读应答报文和输出Ping结果。
程序流程图如下:
否
是
是
否
否
2、校验和函数、释放资源函数流程图如下:
是
否是否
是
否
3、ICMP报首部函数流程图如下:
五、程序的结构与分析
运行结果截图如下:
结果分析:
1、Requesttimedout(请求超时)
(1)对方已关机,或者网络上根本没有这个地址:
比如在上图中Ping14.150.213.222
(2)对方与自己不在同一网段内,通过路由也无法找到对方,但有时对方确实是存在的,当然不存在也是返回超时的信息。
(3)对方确实存在,但设置了ICMP数据包过滤(比如防火墙设置)。
2、DestinationhostUnreachable(目标不可达)
(1)错误设置IP地址
六、课程设计心得体会
本次课程设计较好地实现了要求做到的功能,但同时也遇到不少的困难和挑战。
通过这次设计,不但加深了对Socket的原始套接字编程的理解,经过实现Ping程序,熟悉了IP、ICMP等,掌握TCP/IP网络协议的基本实现方法。
也熟悉了Window网络编程的技术。
能熟悉地使用套接字进行网络通信。
熟悉了数据通信的网络技术,同时学会了跟同学合作交流完成项目的讨论方法和解决问题的能力。
学会如果通过讨论、交流、找资料来独立解决所遇到的问题和不懂。
更多地锻炼了独立解决问题的能力。
在编写过程中,一些基本的常见的函数不会应用,这使我们小组都发现自己知识的匮乏,在以后的学习过程中得要好好的努力,多阅读一些复杂的程序,了解一个基本的函数,算法和精良的编程思想,更要多动手写一些有一定难度的程序,我们不应该害怕写程序出错,应该大胆地写出自己的想法,出现错误去解决错误就能找出自己知识的漏洞和模糊点。
我们还可以通过阅读别人错误的程序,试着帮别人查找错误,这样证书技能头脑中的规则还能发现一些初学者一番的错误,使自己少走弯路。
附录一:
参考文献
【1】计算机网络谢希仁编著电子工业出版社
【2】C程序设计谭浩强编著北京清华大学出版社
附录二:
程序源代码及部分注释
#include"stdafx.h"
#pragmacomment(lib,"ws2_32.lib")
#include//创建套接字头文件
#include
#include//标准输入输出函数
#include//实用程序库函数
#include
#include
typedefstructiphdr
{
unsignedinth_len:
4;//头长度
unsignedintversion:
4;//IP版本
unsignedcharservice;//服务类型
unsignedshorttotal_len;//包的总长度
unsignedshortident;//包标示身份
unsignedshortfrag_and_flags;//标志
unsignedcharttl;//包生命周期
unsignedcharproto;//协议类型
unsignedshortchecksum;//IP校验
unsignedintsourceIP;//源IP
unsignedintdestIP;//目标IP
}IpHeader;
#defineICMP_ECHO8//ICMP报文类型,回显请求
#defineICMP_ECHOREPLY0//ICMP报文类型,回显响应应答
#defineICMP_MIN8//最小的ICMP数据报大小
typedefstructicmphdr
{
BYTEi_type;//ICMP报文类型
BYTEi_code;//该类型中的代码号
USHORTi_cksum;//校验和
USHORTi_id;//惟一的标识符
USHORTi_seq;//序列号
ULONGtimestamp;//时间戳
}IcmpHeader;
#defineDEF_PACKET_SIZE32//默认数据报大小
#defineMAX_PACKET1024//最大的ICMP数据报大小
#defineMAX_IP_HDR_SIZE60//最大IP头长度
//初始化全局变量
intdatasize=DEF_PACKET_SIZE;
char*icmp_data=NULL;
char*recvbuf=NULL;
SOCKETm_hSocket=INVALID_SOCKET;
char*lpdest=NULL;
//填充ICMP数据报字段函数
voidFillICMPData(char*icmp_data,intdatasize)
{
IcmpHeader*icmp_hdr=NULL;
char*datapart=NULL;
icmp_hdr=(IcmpHeader*)icmp_data;
icmp_hdr->i_type=ICMP_ECHO;
icmp_hdr->i_code=0;
icmp_hdr->i_id=(USHORT)GetCurrentProcessId();//GetCurrentProcessId()获取当前进程的标示符(PID)
icmp_hdr->i_cksum=0;
icmp_hdr->i_seq=0;
datapart=icmp_data+sizeof(IcmpHeader);
}
//校验和函数
USHORTchecksum(USHORT*buffer,intsize)
{
unsignedlongcksum=0;
while(size>1)
{
cksum+=*buffer++;
size-=sizeof(USHORT);
}
if(size)
{
cksum+=*(UCHAR*)buffer;
}
cksum=(cksum>>16)+(cksum&0xffff);
cksum+=(cksum>>16);
return(USHORT)(~cksum);
}
//解读ICMP报首部函数
voidDecodeICMPHeader(char*buf,intbytes,SOCKADDR_IN*from)
{
IpHeader*iphdr=NULL;
IcmpHeader*icmphdr=NULL;
unsignedshortiphdrlen;
DWORDtick;
staticinticmpcount=0;
iphdr=(IpHeader*)buf;//从buf中获取IP数据包头指针
iphdrlen=iphdr->h_len*4;
tick=GetTickCount();
if(bytes{
printf("Toofewbytesfrom%s\r\n",inet_ntoa(from->sin_addr));
}
icmphdr=(IcmpHeader*)(buf+iphdrlen);//定位ICMP包头起始位置
if(icmphdr->i_type!
=ICMP_ECHOREPLY)
{
printf("nonechotype%dreceived\r\n",icmphdr->i_type);
}
if(icmphdr->i_id!
=(USHORT)GetCurrentProcessId())
{
printf("其他程序的回应报文!
\t错误代码%d\n",WSAGetLastError());
}
inttick0;
tick0=tick-icmphdr->timestamp;
if(tick0<1)
printf("Replyfrom%s:
bytes=%dtime<1msicmp_seq=%d\n",inet_ntoa(from->sin_addr),bytes,icmphdr->i_seq);
else
printf("Replyfrom%s:
bytes=%dtime=%dmsicmp_seq=%d\n",inet_ntoa(from->sin_addr),bytes,tick0,icmphdr->i_seq);
}
//释放资源函数
voidCleanup()
{
if(m_hSocket!
=INVALID_SOCKET)
closesocket(m_hSocket);
HeapFree(GetProcessHeap(),0,recvbuf);
HeapFree(GetProcessHeap(),0,icmp_data);
WSACleanup();
}
//主函数
voidmain()
{
WSADATAwsaData;
chara[100];
printf("ping");
gets(a);
lpdest=a;
SOCKADDR_INm_addrDest;//结构体
SOCKADDR_INm_addrFrom;
inttimeout=1000;
USHORTseq_no=0;
if(WSAStartup(MAKEWORD(2,2),&wsaData)!
=0)
{
printf("Sorry,youcannotloadsocketdll!
");
}
m_hSocket=WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,WSA_FLAG_OVERLAPPED);//创建原始套接字,该套接字用于ICMP协议
if(m_hSocket==INVALID_SOCKET)//如果套接字创建不成功
{
printf("socket创建失败!
");
}
intbread=setsockopt(m_hSocket,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout));//设置接收的超时值
if(bread==SOCKET_ERROR)
{
printf("设置socket接收超时选项错误!
");
}
timeout=1000;
bread=setsockopt(m_hSocket,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,sizeof(timeout));//设置发送的超时值
if(bread==SOCKET_ERROR)
{
printf("设置socket发送超时选项错误!
");
}
memset(&m_addrDest,0,sizeof(m_addrDest));//用0初始化目的地地址
m_addrDest.sin_family=AF_INET;//设置地址族,这里表示使用IP地址族
if((m_addrDest.sin_addr.s_addr=inet_addr(lpdest))==INADDR_NONE)//地址转化
{
structhostent*hp=NULL;
if((hp=gethostbyname(lpdest))!
=NULL)//名字解析,根据主机名获取IP地址
{
memcpy(&(m_addrDest.sin_addr),hp->h_addr,hp->h_length);//将获取到的IP值赋给目的地地址中的相应字段
m_addrDest.sin_family=hp->h_addrtype;//将获取到的地址族值赋给目的地地址中的相应字段
}
else
{
printf("不能找到名为%s的主机\t错误代码%d\n",lpdest,WSAGetLastError());//获取不成功
exit(0);
}
}
printf("Pinging%swith64bytesofdata:
\n\n",inet_ntoa(m_addrDest.sin_addr));//inet_ntoa()将网络地址转换成“.”点隔的字符串格式
datasize+=sizeof(IcmpHeader);//数据报文大小需要包含ICMP报头
//根据默认堆句柄,从堆中分配MAX_PACKET内存块,新分配内存的内容将被初始化为0
icmp_data=(char*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,MAX_PACKET);
recvbuf=(char*)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,MAX_PACKET);
if(!
icmp_data)//如果分配内存不成功
{
printf("堆分配错误!
");
}
memset(icmp_data,0,MAX_PACKET);//将已开辟内存空间 icmp_data 的首 MAX_PACKET 个字节的值设为值 0。
FillICMPData(icmp_data,datasize);//创建ICMP报文,
//开始发送或接受ICMP包
intnCount=0;
while
(1)
{
intbwrote;
if(strstr(a,"-t")==NULL&&nCount++==4)//判断a字符数组中是否包含-t参数并且已发ICMP包4次
break;//超过指定的记录条数则退出
((IcmpHeader*)icmp_data)->i_cksum=0;//计算校验和前要把校验和字段设置为0
((IcmpHeader*)icmp_data)->timestamp=GetTickCount();//获取操作系统启动到现在所经过的毫秒数,设置时间戳
((IcmpHeader*)icmp_data)->i_seq=seq_no++;//设置序列号
((IcmpHeader*)icmp_data)->i_cksum=checksum((USHORT*)icmp_data,datasize);//计算校验和
bwrote=sendto(m_hSocket,icmp_data,datasize,0,(structsockaddr*)&m_addrDest,sizeof(m_addrDest));//开始发送ICMP请求
if(bwrote==SOCKET_ERROR)//如果发送不成功
{
if(WSAGetLastError()==WSAETIMEDOUT)//如果是由于超时不成功
{
printf("Requresttimedout!
\r\n");
continue;
}
printf("目标不可达!
\t错误代码%d\n",WSAGetLastError());//其他发送不成功原因
continue;
}
if(bwrote{
printf("Wrote%dbytes\r\n",bwrote);
}
intfromlen=sizeof(m_addrFrom);//开始接收ICMP应答
bread=recvfrom(m_hSocket,recvbuf,MAX_PACKET,0,(structsockaddr*)&m_addrFrom,&fromlen);//recvfrom()用来接收远程主机经指定的socket传来的数据,并把数据传到由参数recvbuf指向的内存空间
if(bread==SOCKET_ERROR)//如果接收不成功
{
if(WSAGetLastError()==WSAETIMEDOUT)//如果是由于超时不成功
{
printf("Requresttimedout!
\r\n");
continue;
}
printf("接收数据函数调用错误!
\t错误代码%d\n",WSAGetLastError());//其他接收不成功原因
exit(0);
}
DecodeICMPHeader(recvbuf,bread,&m_addrFrom);//解读接收到的ICMP数据报
Sleep(800);
}
Cleanup();
}