计算机网络课程设计.docx
《计算机网络课程设计.docx》由会员分享,可在线阅读,更多相关《计算机网络课程设计.docx(38页珍藏版)》请在冰豆网上搜索。
计算机网络课程设计
《计算机网络》
课程设计报告
姓名:
学号:
班级:
专业:
指导教师:
时间:
贵州大学计算机科学与信息学院
1课程设计目的……………………………………………………1
2课程设计题目描述和要求…………………………………………1
3设计内容……………………………………………………………1
4总结…………………………………………………………………29
5参考书目……………………………………………………………29
6附录…………………………………………………………………30
一、课程设计目的
1.熟悉原始套接字编程;
2.了解网络的结构;
3.了解网络传输底层协议。
二、课程设计题目描述和要求
1.题目描述:
追踪从主机到达某个ip所要经过的路由个数和相应的路由地址。
2.要求
1)使用Socket编程实现Tracert工具;
2)撰写课程设计报告。
三、课程设计报告内容
1.协议分析
1)1P数据报
IP(InternetProtocol)是一种具有不可靠,无连接交付机制的协议,是TCP/IP互联网设计中最基本的部分。
IP数据报分为首部和数据区,首部分为固定部分和可变部分。
数据区
首部
图4-2.IP数据报
如图4-3所示:
IP数据报的首部包含了IP的版本、首部长度、服务类型、协议、源地址和目的地址以及一些其他首部信息,而数据区则存放了要发送的具体内容。
32位
0
4
8
1631
版本
首部长度
服务类型
总长度
标识
标志
片偏移量
寿命
协议
首部校验和
源站IP地址
目的站IP地址
长度可变的任选字段
填充
数据……
图4-3.IP报文首部格式
IP数据报首部的固定部分中的各字段:
版本:
占4bit,指评协议的版本。
通信双方使用的IP协议的版本必须一致。
目前使用的IP协议版本为4。
以前的3个版本目前已不使用。
长度:
占4bit(IPv4),以32位为一个单位。
最小值是5,最大值是16。
也就是说IP首部最大只有60字节,而最小为20字节。
通常的IP报文首部不带选项,所以一般为20字节。
当IP分组的首部长度不是4字节的整数倍时,必须利用最后一个填充字段加以填充。
这样,数据部分永远在4字节的整数倍时开始,这样在实现起来会比较方便。
首部长度限制为60字节的缺点是有时(如采用源站选路时)不够用。
但这样做的用意是要用户尽量减少额外开销。
服务类型:
占8bit,用来获得更好的服务。
服务类型字段的前三个比特表示优先级,它可使数据报具有8个优先级中的一个。
第4个比特是D比特,表示要求有更低的时延。
第5个比特是T比特,表示要求有更高的吞吐量。
第6个比特是R比特,表示要求有更高的可靠性,即在数据报传送的过程中,被结点交换机丢弃的概率要更小些。
第7个比特是C比特,是新增加的,表示要求选择费用更低廉的路由。
最后一个比特目前尚未使用。
总长度:
总长度指首部和数据之和的长度,单位为字节。
总长度字段为16bit.因此数据报的最大长度为65535字节。
这在当前是够用的。
当很长的数据报要分片进行传送时,“总长度”不是指未分片前的数据报长度,而是指分片后每片的首部长度与数据长度的总和。
标识:
标识字段是为了使分片后的各数据报片最后能准确地重装成为原来的数据报。
请注意:
这里的“标识”并没有顺序号的意思,因为IP是无连接服务,数据报不存在按序接收的问题。
标志:
占3bit。
目前只有前两个比特有意义。
标志字段中的最低位记为MF。
MF=l即表示后面还有分片的数据报。
MF=0表示这已是若干数据报片中的最后一个。
标志字段中间的一位记为DF。
只有当DF=0时才允许分片。
片偏移:
片偏移指出:
较长的分组在分片后,某个片在原分组中的相对位置。
也就是说,相对于用户数据字段的起点,该片从何处开始。
片偏移以8个字节为偏移单位。
寿命:
寿命字段记为TTL,其单位为秒。
寿命的建议值是32秒。
但也可设定为3。
4秒,或甚至255秒。
寿命又称为生存时间。
协议:
占8bit,协议字段指出此数据报携带的运输层数据是使用何种协议,以便目的主机的IP层知道应将此数据报上交给哪个进程。
常用的一些协议和相应的协议字段值(写在协议后面的括弧中)是:
UDP(17),TCP(6),ICMP
(1),GGP(3),EGP(8),IGMP
(2),IGP(9),OSPF(89),以及ISO的第4类运输协议TP4(29)。
这些协议的数据报都是通过封装在IP数据报内部发送的。
2)ICMP(InternetControlMessageProtocal)是为了让互联网中的路由器报告错误或提供有关意外情况的信息而设计的一个特殊报文机制。
它是IP协议的附属协议,是封装在IP数据报内部传送的,如图4-7所示:
IP数据报
ICMP报文
IP首部
图4-7.ICMP封装在IP数据报内部
ICMP的报文格式如图4–8所示:
ICMP类型(8位)
ICMP代码(8位)
ICMP校验和(16位)
ICMP报文具体内容(不同类型不同代码有不同内容)
图4-8.ICMP报文格式
尽管每个ICMP报文都有自己的格式,但它们开始的三个字段都是一样的:
一个8位的报文类型(type)用来标识报文,一个8位的代码(code)用来提供有关类型的进一步信息,一个16位的校验和(checksum)。
(ICMP采用和IP相同的校验和算法,但ICMP校验和只覆盖ICMP报文)
这里我们给出ICMP报文首部的数据结构:
//ICMP首部数据结构
structICMPHEADER
{
BYTEi_type;//类型
BYTEi_code;//代码
USHORTi_cksum;//首部校验和
USHORTi_id;//标识
USHORTi_seq;//序列号
ULONGtimestamp;//时间戳(选用)
};
表4-1表示了ICMP的报文类型及其含义:
Type
Code
类别
含义
0
0
查询
回送应答
3
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
差错
差错
差错
差错
差错
差错
差错
差错
差错
差错
差错
差错
差错
差错
差错
差错
网络不可抵达
主机不可抵达
协议不可抵达
端口不可抵达
需要重新分片但设置了不分片比特
源路由失败
目的网络未知
目的主机未知
源主机被隔离
目的网络被禁止
目的主机被禁止
对所请求的服务类型,网络不可达
对所请求的服务类型,主机不可达
由于过滤,通信被禁止
主机越权
优先级失效
4
0
差错
源端被关闭
5
0
1
2
3
差错
差错
差错
差错
对网络重定向
对主机重定向
对服务类型和网络重定向
对服务类型和主机重定向
8
0
查询
请求回显
9
0
查询
路由器通告
10
0
查询
陆由器请求
11
0
1
差错
差错
传输期间数据报超时
数据报组装期间超时
12
0
1
差错
差错
IP报头损坏
缺少必要的选项
13
0
查询
时间戳请求
14
0
查询
时间戳回复
15
0
查询
信息请求(已过时)
16
0
查询
信息回复(已过时)
17
0
查询
地址掩码请求
18
0
查询
地址掩码回复
表4-1ICMP报文类型
下面给出几种常用的ICMP报文格式
回显请求和应答报文格式:
通常用来判断目的主机是否可以通过网络访问到。
类型(8或0)
代码(0)
校验和
标识符
序号
可选项
时间戳请求与应答:
网络上的主机一般是独立的,每台机器都有自己的当前时间。
时间戳请求与应答用来查询目的主机系统当前的时间。
返回值是自午夜开始到现在的毫秒数,通过这个数值来协调时间的统一。
事实上因为延时,这个值是不准确的,通常采取多次测量求平均值的办法。
类型(13或14)
代码(0)
校验和
标识符
序号
发起时间戳
接收时间戳
传送时间戳
路由器通告:
当主机自举以后必须至少知道本地网络上的一个路由器的地址才能够向其它网络发送报文。
ICMP支持路由发现方案(RouterDiscovery),允许主机发现一个路由器地址。
类型(9)
代码(0)
校验和
地址号
地址大小
(1)
寿命
路由器地址1
优先级1
路由器地址2
优先级2
…
地址掩码请求与应答:
主机可以通过向路由器发送一个地址掩码请求,并且接收发回的地址掩码应答报文来获得本地网络所使用的子网掩码。
可以直接发送请求,但如果不知道路由器的地址,也可以采用广播的方式来发送报文。
类型(17或18)
代码(0)
校验和
标识符
序号
地址掩码
创建原始套接字
原始套接字(RawSocket)可以让我们对底层的传输协议加以控制。
我们可以用socket()或WSASocket()来创建。
在创建原始套接字的时候我们可以选用ICMP协议、UDP协议、IGMP协议、IP或原始IP。
它们的标志分别是IPPROTO_ICMP、IPPROTO_UDP、IPPROTO_IGMP、IPPROTO_IP和IPPROTO_RAW。
这里我们给出ICMP协议的原始套接字的创建例子:
//原始套接字的创建
SOCKETs;
s=WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,
WSA_FLAG_OVERLAPPED);
//或者
s=socket(AF_INET,SOCK_RAW,IPROTO_ICMP);
if(s==INVALID_SOCKET)
{
//创建失败
}
需要注意的是由于原始套接字可以对底层的协议加以控制,所以在WindowsNT中必须以管理员身份(Administrator)或者拥有同等权限的用户身份登陆才可以创建原始套接字。
2.原理分析
路由器跟踪的原理
记录数据报所经过的路由器可以有很多办法。
可以通过设置IP数据报首部的记录路由器选项来记录数据报发送过程中所经过的路由器,但是这样的方法有很大的局限性。
前面已经提到IP数据报首部的最大长度不超过60字节,去掉必要的首部格式,剩下的空间只能够存放最多9个路由器地址。
这本来是为ARPANET设计的,但是对现在的网络来讲9个地址空间已经远远不能满足要求。
我们必须采取别的方法。
IP数据首部中有一项为寿命(TTLtimetolive),它规定了数据报在网络中“存活”的时间。
每当数据报经过一个节点的时候,寿命就减1,当寿命为0的时候该报文就被丢弃。
同时向源主机发送一个超时的ICMP报文。
这是为了防止迷失的报文在网络上“游荡”而设计的。
我们可以通过向目的主机发送寿命递增的ICMP报文,让沿途的路由器顺次的发回超时报文,收集这些报文并且提取了地址以后就可以知道报文发送途中所经过的路由器的地址。
也可以向目的主机发送寿命递增的UDP报文,本设计中我们向目的主机发送ECHO请求的ICMP报文。
原理是同样的。
下图表示了这个算法:
…….
timeout
timeout
timeout
D
S
R2
R5
R3
R1
R4
算法示意图
S——源端主机D——目的主机Rn——途径的第n个路由器
当数据报到达目的主机以后,目的主机会发回一个ECHO回应,这时跟踪就完成了。
3.详细实现步骤及主要代码
基于VC++
1.建MFC(exe)工程,名字为TraceRoute。
应用程序类型选择基于对话框,然后完成。
注意不要选“支持windowssocket”.
2.按图1添加和布置相应的控件。
给控件对应的ID和属性:
ID
类型
属性
IDC_STATIC
StaticText
标题:
“目的地址:
”
IDC_EDIT_ADDRESS
EditBox
IDC_STATIC
GroupBox
标题:
“跟踪结果:
”
IDC_BUTTON_TRACE
Button
标题:
“&Trace”
设置默认按钮
IDC_STATIC_SHOW
StaticText
设置staticedge
给控件添加相应的变量
ID
变量名
类型
IDC_EDIT_ADDRESS
m_strAddress
CString
IDC_STATIC_SHOW
m_strResult
CString
3.ClassView当中右键点击TraceRouteclasses,选择Newclass.在Newclass中Classtype选择GenericClass,Name填写CTracer,选择确定。
4.添加头文件和定义宏
//Tracer.h
#include"winsock2.h"
#include"ws2tcpip.h"
//必要的宏定义
#defineDEF_PACKET_SIZE32
#defineMAX_PACKET1024
#defineMAX_NOTES30
#defineICMP_MIN8
#defineICMP_ECHOREPLY0
#defineICMP_DESTUNREACH3
#defineICMP_SRCQUENCH4
#defineICMP_REDIRECT5
#defineICMP_ECHO8
#defineICMP_TIMEOUT11
#defineICMP_PARMERR12
5.为CTracer添加变量
//Tracer.h
private:
CDialog*m_pWnd;//指向主窗口的指针
char*icmpData;//指向发送报文内存空间的指针
char*icmpRcvBuf;//指向报文接收缓冲空间的指针
intm_nSeq;//报文序列号
SOCKETm_hSocket;//套接字句柄
SOCKADDR_INm_addrDest;//目的主机地址
SOCKADDR_INm_addrFrom;//存放路由地址
6.
为CTracer添加成员函数
1.构造函数:
在构造函数中完成成员变量的初始化,完成socket的初始化。
CTracer:
:
CTracer()
{
m_nSeq=1;
icmpData=NULL;
icmpRcvBuf=NULL;
m_hSocket=INVALID_SOCKET;
//初始化socket
WSADATAwsaData;
if(WSAStartup(MAKEWORD(2,2),&wsaData)!
=0)
{
AfxMessageBox("WSAStartup()出错!
");
}
}
2.析构函数:
在析构函数中关闭socket.
CTracer:
:
~CTracer()
{
//关闭Socket
if(m_hSocket!
=NULL)
closesocket(m_hSocket);
WSACleanup();
}
3.添加private函数CheckSum,返回型为USHORT。
用来计算校验和。
IP校验和的计算是把首部看成一个16位的整数序列,对每个整数分别
计算其二进制反码,然后相加,再对结果计算一次二进制反码而求得。
USHORTCTracer:
:
CheckSum(char*pBuffer,intsize)
{
USHORT*buffer=(USHORT*)pBuffer;
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);
}
4.添加private成员函数FillAddress.用来将输入的字符串格式的目的主机地址转换为能够使用的地址格式。
//由字符串转化为地址
BOOLCTracer:
:
FillAddress(char*addrDest)
{
memset(&m_addrDest,0,sizeof(m_addrDest));
m_addrDest.sin_family=AF_INET;
if(inet_addr(addrDest)==INADDR_NONE)
{
//输入的地址为计算机名字
HOSTENT*hp=NULL;
hp=gethostbyname(addrDest);
if(hp)
{
memcpy(&(m_addrDest.sin_addr),hp->h_addr,hp->h_length);
m_addrDest.sin_family=hp->h_addrtype;
}
else
{
AfxMessageBox("获取地址失败!
");
returnFALSE;
}
}
else
{
m_addrDest.sin_addr.s_addr=inet_addr(addrDest);
}
returnTRUE;
}
5.添加private成员函数FillICMPData.在分配了内存从空间以后利用此函数对ICMP报文首部进行附值。
对ICMP报文的数据区可以任选一个字符进行填充。
//填充ICMP报文首部
voidCTracer:
:
FillICMPData(char*icmpData,intsize)
{
memset(icmpData,0,size);
ICMPHEADER*icmpHeader=NULL;
icmpHeader=(ICMPHEADER*)icmpData;
icmpHeader->i_type=ICMP_ECHO;
icmpHeader->i_code=0;
icmpHeader->i_id=(USHORT)GetCurrentProcessId();
icmpHeader->i_seq=m_nSeq++;
//GetTickCount返回从0点到现在的毫秒数,作时间戳
icmpHeader->timestamp=GetTickCount();
char*datapart=icmpData+sizeof(ICMPHEADER);
memset(datapart,'*',size-sizeof(ICMPHEADER));
//填充校验和
icmpHeader->i_cksum=CheckSum(icmpData,size);
}
6.添加private成员函数SetTTL.用来设置报文的寿命,使得报文传输途中的路由器向源端发送报文超时报文。
//设置数据报的寿命
BOOLCTracer:
:
SetTTL(SOCKEThSocket,intttl)
{
intresult;
result=setsockopt(hSocket,IPPROTO_IP,IP_TTL,(LPSTR)&ttl,sizeof(ttl));
if(result==SOCKET_ERROR)
{
AfxMessageBox("设置数据报寿命失败!
");
TerminateProcess(GetCurrentProcess(),-1);
}
returnTRUE;
}
7.添加private成员函数SendData.在数据报已经生成并且进行了正确的ttl设置以后调用此函数来将报文发送出去。
//发送数据报
BOOLCTracer:
:
SendData(char*icmpData,intsize)
{
//填充ICMP报头
FillICMPData(icmpData,size);
//发送数据报
intresult;
result=sendto(m_hSocket,icmpData,size,0,(SOCKADDR*)&m_addrDest,sizeof(m_addrDest));
if(result==SOCKET_ERROR)
{
if(WSAGetLastError()==WSAETIMEDOUT)
{
((CTraceRouteDlg*)m_pWnd)->InfoAdd("发送超时");
returnTRUE;
}
AfxMessageBox("发送报文失败!
");
TerminateProcess(GetCurrentProcess(),-1);
}
returnFALSE;
}
8.添加private成员函数RecvData.用来接收主机收到的ICMP报文。
将其放入所开辟的缓冲区内,以便进行分析。
如果网络通信通信存在问题,可能出现接收超时的现象,通过一个static变量来计数。
当计数器大于5的时候判断无法接收数据报,报错后退出程序。
//接收数据报
BOOLCTracer:
:
RecvData(char*icmpRcvBuf,int*presult)
{
staticintcount=0;
//总共6次出现接收超时,判断存在连接问题。
if(count>5)
{
AfxMessageBox("连接存在问题!
");
TerminateProcess(GetCurrentProcess(),-1);
}
intfromlen=sizeof(SOCKADDR);
*presult=SOCKET_ERROR;
*presult=recvfrom(m_hSocket,icmpRcvBuf,MAX_PACKET,0,(SOCKADDR*)&m_addrFrom,&fromlen);
if(*presult==SOCKET_ERROR)
{
if(WSAGetLastError()==WSAETIMEDOUT)
{
((CTraceRouteDlg*)m_pWnd)->InfoAdd("接收超时!
");
count++;
returnTRUE;
}
AfxMessageBox("接收数据报失败!
");
TerminateProcess(GetCurrentProcess(),-1);
}
retu