校验和

更新时间:2024-04-25 15:24:09 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

计算机网络课程设计之协议编程

实验一 帧封装

实验目的:

? 编写程序,根据给出的原始数据,组装一个IEEE 802.3格式的帧(题目)默认的输入文件为二进制原始数据(文件名分别为input1和input2))。 ? 要求程序为命令行程序。比如,可执行文件名为framer.exe,则命令行形式如下:framer inputfile outputfile,其中,inputfile为原始数据文件,outputfile为输出结果。

? 输出:对应input1和input2得结果分别为output1和output2。

试验要求:

? 编写程序,根据给出的原始数据,组装一个IEEE 802.3格式的帧(题目)默认的输入文件为二进制原始数据(文件名分别为input1和input2))。 ? 要求程序为命令行程序。比如,可执行文件名为framer.exe,则命令行形式如下:framer inputfile outputfile,其中,inputfile为原始数据文件,outputfile为输出结果。

输出:对应input1和input2得结果分别为output1和output2

验设计相关知识:

帧:来源于串行线路上的通信。其中,发送者在发送数据的前后各添加特殊的字符,使它们成为一个帧。Ethernet从某种程度上可以被看作是机器之间的数据链路层连接。

按802.3标准的帧结构如下表所示(802.3标准的Ethernet帧结构由7部分组成) 前导码 7B 帧前定界符 1B 802.3标准的帧结构 目的地址 (2/6B) 源地址 (2/6B) 长度字段 (2B) 数据字段 (长度可变) 校验字段 (4B) 其中,帧数据字段的最小长度为46B。如果帧的LLC数据少于46B,则应将数据字段填充至46B。填充字符是任意的,不计入长度字段值中。

在校验字段中,使用的是CRC校验。校验的范围包括目的地址字段、源地址字段、长度字段、LLC数据字段。

循环冗余编码(CRC)是一种重要的线性分组码、编码和解码方法,具有简单、检错和纠错能力强等特点,在通信领域广泛地用于实现差错控制。CRC校验码的检错能力很强,不仅能检查出离散错误,还能检查出突发错误。

利用CRC进行检错的过程可简单描述如下:在发送端根据要传送的k位二进制码序列,以一定的规则产生一个校验用的r位监督码(CRC码),附在原始信息的后边,构成一个新的二进制码序列(共k+r位),然后发送出去。在接收端,根据信息码和CRC码之间所遵循的规则进行检验,以确定传送中是否出错。这个规则在差错控制理论中称为“生成多项式”。

CRC的基本实现 前导码 帧前定界符 目的地址 源地址 7B 1B (2/6B) (2/6B) 长度字段 (2B) 数据字段 (长度可变) 校验字段 (4B) 循环冗余校验码的特点:(1)CRC校验码可检测出所有单个错误。(2)CRC校验码可检测出所有奇数位错误。(3)CRC校验码可检测出所有双位的错误(4)CRC校验码可检测出所有小于、等于校验位长度的突发错误。(5)CRC校验码可

以[1-(1/2)k-1]的概率检测出长度为(K+1)位的突发错误

实验分析:

? 填充帧头部字段

要完成一次帧封装的过程,首先要完成的就是帧头部的装入,这一过程只要将签到吗、定界符、目的地址、源地址、长度字段的相应数值按顺序写入就可以了。其中,长度字段的值即为要发送的数据的实际长度。 ? 填充数据字段

在填充数据字段的过程中要注意的主要问题是数据字段的长度。802.3标准中规定了帧数据字段的最小长度为46B,最大长度为1500B。如果数据不足46B,则需要通过填充0来补足;若数据长度超过1500B,则的大奖超过部分封装入下一个帧进行发送。 ? CRC校验

帧封装的最后一步就是对数据进行校验,并将校验结果记入帧校验字段。

程序流程图:

开始以二进制、可读写方式打开输出文件写入前导码和这界定符获取当前文件指针写入目的地址和源地址打开输入数据文件、获得稳健长度length,并将长度值写入输出文件将输入数据文件的内容填入数据字段数据字段长度=46BN填充(46-length)字节‘0’Y关闭输入数据文件

CRC计算流程图:

序源代码:

#include

#pragma comment(lib,\指定连接到网络应用和internet

#define IO_RCVALL _WSAIOW(IOC_VENDOR,1)

typedef struct IP_HEAD {

union //定义联合 {

unsigned char Version; unsigned char HeadLen; };

unsigned char ServiceType; unsigned short TotalLen; unsigned short Identifier; union {

unsigned short Flags;

unsigned short FragOffset; };

unsigned char TimeToLive; unsigned char Protocol;

unsigned short HeadChecksum; unsigned int SourceAddr; unsigned int DestinAddr; unsigned char Options;

}ip_head; //定义IP头部的数据结构

void main(int argc,char *argv[]) {

using namespace std;

ofstream outfile(\if(argc!=2) {

cout<

WSADATA WSAData;

if(WSAStartup(MAKEWORD(2,2), &WSAData)!=0) {

cout<

SOCKET sock=socket(AF_INET,SOCK_RAW,IPPROTO_IP); //三个参分别为通信发生的区字段,套接字的类型,与IP协议 if(sock==INVALID_SOCKET) {

cout<

BOOL flag=TRUE;

if(setsockopt(sock,IPPROTO_IP,IP_HDRINCL,(char *) &flag,sizeof(flag))==SOCKET_ERROR) {

cout<

char hostName[128];//获取主机名

if(gethostname(hostName,100)==SOCKET_ERROR) {

cout<

hostent *pHostIP; //获取本地IP

if((pHostIP=gethostbyname(hostName))==NULL) {

cout<

sockaddr_in host_addr;//

host_addr.sin_family=AF_INET; host_addr.sin_port=htons(6000);

host_addr.sin_addr=*(in_addr *)pHostIP->h_addr_list[0];

if(bind(sock,(PSOCKADDR)&host_addr,sizeof(host_addr))==SOCKET_ERROR) {

cout<

DWORD dwBufferLen[10]; DWORD dwBufferInLen=1; DWORD dwBytesReturned=0;

if(WSAIoctl(sock , IO_RCVALL ,&dwBufferInLen , sizeof(dwBufferInLen) , &dwBufferLen,sizeof(dwBufferLen),&dwBytesReturned,NULL,NULL)==SOCKET_ERROR) {

cout<

cout<

int packsum=atoi(argv[1]); //字符串转换为整形 for(int i=0;i

if(recv(sock,buffer,65535,0)>0) //四个参数分别是套接字描述符,缓冲区的地址,缓冲区大小,附加标志 {

ip_head ip=*(ip_head *)buffer; cout<<\

cout<<\版本:\获取头部长度字段

cout<<\头部长度:\获取头部长度字段 cout<服务类型:Priority\Service\优先级子域和TOS子域 cout<<\总长度:\获取总长度字段 cout<<\标识符:\获取标识字段 cout<<\标志位:\\获得标志字段 cout<<\片偏移:\获取分段偏移字段 cout<<\生存周期:\获取生存时间字段 cout<<\协议:Protocol\获取协议字段

cout<<\头部校验和:\获取头校验和字段

cout<<\原地址:\获取源IP地址字段

cout<<\目的IP地址:\获取目的IP地址字段

outfile<<\

outfile<<\版本:\

outfile<<\头部长度:\ outfile<<\服务类型:Priority\Service\ outfile<<\总长度:\ outfile<<\标识符:\ outfile<<\标志位:\

\ outfile<<\片偏移:\ outfile<<\生存周期:\ outfile<<\协议:Protocol\ outfile<<\头部校验和:\

outfile<<\原地址:\

outfile<<\目的IP地址:\ } }

closesocket(sock); WSACleanup(); }

程序运行结果:

程序编译运行后:

以命令行形式运行程序ipparse:

同时在程序所在的文件夹中生成了名为logfile的txt文件,里面记录了上面显示的内容。

试验小结:

IP数据报的格式说明了IP协议都具有什么功能,因为完全不知道如何使用套接字socket()函,查阅了相关资料,了解了IP数据报的各种位与协议的概念和意义,通过解析IP数据包这个实验,基本掌握了用套接字编程来实现获取并解析IP数据包的方法。

实验三 发送TCP数据包

实验目的:

? 设计一个发送TCP数据包的程序,并根据本设计说明TCP数据包的结构以及TCP协议与IP协议的关系,使大家对TCP协议的工作原理有更深入的认识。 ?

实验要求:

本程序的功能是填充一个TCP数据包,并发送给目的主机。

? 以命令行形式运行:SendTCP source_ip source_port dest_ip dest_port 其中SendTCP为程序名;source_ip为源IP地址; source_port为源端口; dest_ip为目的IP地址; dest_port为目的端口。 ? 其他的TCP头部参数自行设定。

? 数据字段为“This is my homework of network!”. ? 成功发送后在屏幕上输出“send OK”。

课程设计分析:

? ? ? ?

使用原始套接字

定义IP头部、TCP头部和伪头部的数据结构 填充数据包 发送数据包

设计思想:

本课程设计的目标是发送一个TCP数据包,可以利用原始套接字来完成这个

工作。整个程序由初始化原始套接字和发送TCP数据包两个部分组成。 创建一个原始套接字,并设置IP头选项 SOCKET sock;

sock = socket(AF_INET,SOCK_RAW,IPPROTO_IP); 或者:

sock=WSASoccket(AF_INET,SOCK_RAW,IPPROTO_IP,NULL,0,WSA_FLAG_OVERLAPPED);

设置SOCK_RAW标志,表示我们声明的是一个原始套接字类型。

为使用发送接收超时设置,必须将标志位置位置为WSA_FLAG_OVERLAPPED。在本课程设计中,发送TCP包时隐藏了自己的IP地址,因此我们要自己填充IP头,设置IP头操作选项。其中flag设置为ture,并设定 IP_HDRINCL 选项,表明自己来构造IP头。注意,如果设置IP_HDRINCL 选项,那么必须具有 administrator权限,要不就必须修改注册表:

HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Services\\Afd\\Parameter\\ 修改键:DisableRawSecurity(类型为DWORD),把值修改为 1。如果没有,就添加。

BOOL Flag=TRUE;

setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char *)&Flag, sizeof(Flag)); int timeout=1000;

setsockopt(sock, SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout, sizeof(timeout)); 在这里我们使用基本套接字SOL_SOCKET,设置SO_SNDTIMEO表示使用发送超时设置,超时时间设置为1000ms。 构造IP头和TCP头

这里, IP头和TCP头以及TCP伪部的构造请参考下面它们的数据结构。

计算校验和的子函数

在填充数据包的过程中,需要调用计算校验和的函数checksum两次,分别用于校验IP头和TCP头部(加上伪头部),其实现代码如下:

USHORT checksum(USHORT *buffer, int size) {

unsigned long cksum=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); }

程序流程图:

开始构造原始套接字,并初始化

源程序代码:

#include #include #include #include #include #include #include

填充IP头部计算IP头部检验和构造TCP伪头部填充TCP头部计算TCP头部校验和发送TCP数据报结束

#include

#pragma comment(lib,\

#define IPVER 4 //IP协议预定 #define MAX_BUFF_LEN 65500 //发送缓冲区最大值

typedef struct ip_hdr //定义IP首部 {

UCHAR h_verlen; //4位首部长度,4位IP版本号 UCHAR tos; //8位服务类型TOS USHORT total_len; //16位总长度(字节) USHORT ident; //16位标识 USHORT frag_and_flags; //3位标志位 UCHAR ttl; //8位生存时间 TTL UCHAR proto; //8位协议 (TCP, UDP 或其他) USHORT checksum; //16位IP首部校验和 ULONG sourceIP; //32位源IP地址 ULONG destIP; //32位目的IP地址 }IP_HEADER;

typedef struct tsd_hdr //定义TCP伪首部 {

ULONG saddr; //源地址 ULONG daddr; //目的地址 UCHAR mbz; //没用 UCHAR ptcl; //协议类型 USHORT tcpl; //TCP长度 }PSD_HEADER;

typedef struct tcp_hdr //定义TCP首部 {

USHORT th_sport; //16位源端口 USHORT th_dport; //16位目的端口 ULONG th_seq; //32位序列号 ULONG th_ack; //32位确认号

UCHAR th_lenres; //4位首部长度/6位保留字 UCHAR th_flag; //6位标志位 USHORT th_win; //16位窗口大小 USHORT th_sum; //16位校验和 USHORT th_urp; //16位紧急数据偏移量 }TCP_HEADER;

//CheckSum:计算校验和的子函数

USHORT checksum(USHORT *buffer, int size) {

unsigned long cksum=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); }

int main(int argc, char* argv[]) {

WSADATA WSAData; SOCKET sock;

IP_HEADER ipHeader; TCP_HEADER tcpHeader; PSD_HEADER psdHeader;

char Sendto_Buff[MAX_BUFF_LEN]; //发送缓冲区 unsigned short check_Buff[MAX_BUFF_LEN]; //检验和缓冲区

const char tcp_send_data[]={\

BOOL flag; int rect,nTimeOver;

if (argc!= 5) {

printf(\ return false; }

if (WSAStartup(MAKEWORD(2,2), &WSAData)!=0) {

printf(\ return false;

}

if((sock=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0,

WSA_FLAG_OVERLAPPED))==INVALID_SOCKET)

{

printf(\ return false; } flag=true;

if(setsockopt(sock,IPPROTO_IP,IP_HDRINCL,(char*)&flag,sizeof(flag))==

SO CKET_ERROR)

{

printf(\ return false; }

nTimeOver=1000; if

(setsockopt(sock,

SOL_SOCKET,

SO_SNDTIMEO,

(char*)&nTimeOver,

sizeof(nTimeOver))==SOCKET_ERROR)

{

printf(\ return false; }

//填充IP首部

ipHeader.h_verlen=(IPVER<<4 | sizeof(ipHeader)/sizeof(unsigned long)); ipHeader.tos=(UCHAR)0; ipHeader.total_len=htons((unsigned

short)sizeof(ipHeader)+sizeof(tcpHeader)+sizeof(tcp_send_data)); ipHeader.ident=0; //16位标识 ipHeader.frag_and_flags=0; //3位标志位

ipHeader.ttl=128; //8位生存时间

ipHeader.proto=IPPROTO_UDP; //协议类型 ipHeader.checksum=0; //检验和暂时为0

ipHeader.sourceIP=inet_addr(argv[1]); //32位源IP地址 ipHeader.destIP=inet_addr(argv[3]); //32位目的IP地址

//计算IP头部检验和

memset(check_Buff,0,MAX_BUFF_LEN);

memcpy(check_Buff,&ipHeader,sizeof(IP_HEADER));

ipHeader.checksum=checksum(check_Buff,sizeof(IP_HEADER));

//构造TCP伪首部

psdHeader.saddr=ipHeader.sourceIP; psdHeader.daddr=ipHeader.destIP; psdHeader.mbz=0;

psdHeader.ptcl=ipHeader.proto;

psdHeader.tcpl=htons(sizeof(TCP_HEADER)+sizeof(tcp_send_data));

//填充TCP首部

tcpHeader.th_dport=htons(atoi(argv[4])); //16位目的端口号 tcpHeader.th_sport=htons(atoi(argv[2])); //16位源端口号 tcpHeader.th_seq=0; //SYN序列号 tcpHeader.th_ack=0; //ACK序列号置为0 //TCP长度和保留位

tcpHeader.th_lenres=(sizeof(tcpHeader)/sizeof(unsigned long)<<4|0);

tcpHeader.th_flag=2; //修改这里来实现不同的标志位探测,2是SYN,1是//FIN,16是

ACK探测 等等

tcpHeader.th_win=htons((unsigned short)16384); //窗口大小 tcpHeader.th_urp=0; //偏移大小

tcpHeader.th_sum=0; //检验和暂时填为0

//计算TCP校验和

memset(check_Buff,0,MAX_BUFF_LEN);

memcpy(check_Buff,&psdHeader,sizeof(psdHeader));

memcpy(check_Buff+sizeof(psdHeader),&tcpHeader,sizeof(tcpHeader)); memcpy(check_Buff+sizeof(PSD_HEADER)+sizeof(TCP_HEADER),

tcp_send_data,sizeof(tcp_send_data));

tcpHeader.th_sum=checksum(check_Buff,sizeof(PSD_HEADER)+

sizeof(TCP_HEADER)+sizeof(tcp_send_data));

//填充发送缓冲区

memset(Sendto_Buff,0,MAX_BUFF_LEN);

memcpy(Sendto_Buff,&ipHeader,sizeof(IP_HEADER)); memcpy(Sendto_Buff+sizeof(IP_HEADER),&tcpHeader,

sizeof(TCP_HEADER));

memcpy(Sendto_Buff+sizeof(IP_HEADER)+sizeof(TCP_HEADER),

tcp_send_data,sizeof(tcp_send_data));

int datasize=sizeof(IP_HEADER)+sizeof(TCP_HEADER)+

sizeof(tcp_send_data);

//发送数据报的目的地址 SOCKADDR_IN dest; memset(&dest,0,sizeof(dest)); dest.sin_family=AF_INET;

dest.sin_addr.s_addr=inet_addr(argv[3]); dest.sin_port=htons(atoi(argv[4]));

rect=sendto(sock,Sendto_Buff,datasize, 0,(struct sockaddr*)&dest, sizeof(dest)); if (rect==SOCKET_ERROR) {

printf(\ return false; } else

printf(\ closesocket(sock); WSACleanup(); return 1; }

实验运行结果:

所以进入dos,并进入到该可执行文件的目录下后在命令提示行下输入:sendtcp 192.168.18.3 12 192.168.18.7 25,回车运行,运行截图如下:

本实验小结:

在对TCP数据包头部进行填充时,首先需要我们去充分了解它的数据结构,在这个过程中可以了解相应字节上应该存放的内容和它们的功能。由于本次的课程设计只要求填充一个TCP数据包,然后将其发送出去,使用的数据发送函数是

sendto(),成功地完成sendto()调用只能保证数据已经从本地发送出去,并不意味着数据传送到达目的地。

课程设计总结

在此次的计算机网络课程设计中,我们一共做了三个程序设计的实验,帧封装、IP数据包解析和发送TCP数据包。

在编写程序的过程中,用到了很多的函数,这些函数的运用使得程序简便而且正确的运行出来。为了正确的实现这些函数,查阅了很多相关的资料,从中获得了大量的有用的信息,收获也颇丰富。

在这次的课程设计中,动手能力得到了很大的提高,而且将这学期所学的网络的知识和以前所学的编程的知识充分的联系起来,对这门课的认识又提高了一层。

sendto(),成功地完成sendto()调用只能保证数据已经从本地发送出去,并不意味着数据传送到达目的地。

课程设计总结

在此次的计算机网络课程设计中,我们一共做了三个程序设计的实验,帧封装、IP数据包解析和发送TCP数据包。

在编写程序的过程中,用到了很多的函数,这些函数的运用使得程序简便而且正确的运行出来。为了正确的实现这些函数,查阅了很多相关的资料,从中获得了大量的有用的信息,收获也颇丰富。

在这次的课程设计中,动手能力得到了很大的提高,而且将这学期所学的网络的知识和以前所学的编程的知识充分的联系起来,对这门课的认识又提高了一层。

本文来源:https://www.bwwdw.com/article/na7p.html

Top