基于UDP的局域网文件传输.docx
《基于UDP的局域网文件传输.docx》由会员分享,可在线阅读,更多相关《基于UDP的局域网文件传输.docx(68页珍藏版)》请在冰豆网上搜索。
基于UDP的局域网文件传输
课程设计说明书
课程名称:
软件课程设计
设计题目:
可信赖的UDP协议设计及实现
院系:
电子与信息工程系
班级:
电信0608班
设计者:
周鹏里
学号:
012006013510
指导教师:
蒋洪波
设计时间:
2009年6月28日(星期日)
华中科技大学
可信赖的UDP协议设计及实现
1项目描述
1.1功能需求
⏹构建简单的C/S环境,即一个服务器,一个客户机
⏹从服务器可以正确下载文件到客户机
⏹如果有中断的情况(例如客户机网线被拔出),当回复正常之后(网线又插入)也可以恢复文件的传输过程
⏹协议方式采用自定义修改后的UDP协议
1.2UDP和TFTP协议基本原理
在OSI七层参考模型中的第四层运输层包含两种不同的运输协议,即面向连接的TCP协议和无连接的UDP协议。
本软件设计采用的就是UDP协议。
用户数据报协议UDP是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。
IETFRFC768是UDP的正式规范。
UDP协议基本上是IP协议与上层协议的接口。
只在IP的数据报服务上增加了端口和差错检测的功能。
虽然UDP用户数据报只能提供不可靠的交付,但UDP确有其特殊的优点,例如:
(1)发送数据展子虔不需要建立连接,当发送数据结束时也没有连接需要释放,因此减少了开销和发送数据前的时延。
(2)UDP不使用拥塞控制,也不保证可靠交付,因此主机不需要维持具有许多参数的复杂的连接状态表。
(3)UDP用户数据报只有8个字节的首部开销,比TCP的20个字节要短。
(4)由于UDP没有拥塞控制,因此网络出现的拥塞不会使源主机的发送速率降低。
这对某些实时应用是很重要的。
很多的实时应用(如IP电话,实时视频会议等)要求源主机以恒定的速率发送数据,并且允许在网络发生拥塞时丢失一些数据,但却不允许数据有太大的时延。
UDP正好符合这种要求。
基于以上UDP传输的特点,故实现局域网的文件传输采用UDP协议是最快速恰当的协议。
快速性是UDP本身的特点,然而局域网数据传输的可靠性是十分高的,能够很好的弥补UDP不可靠交付的缺点,使其不可靠的概率十分小,本系统只对中断的情况(例如客户机网线被拔出)进行差错处理。
文件传送协议FTP是因特网上使用最广泛的文件传送协议。
当文件传送采用用UDP协议时被称为简单文件传输协议TFTP。
简单文件传输协议TFTP(TrivialFileTransferProtocol)是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务。
它基于UDP协议实现,此协议设计的时候是进行小文件传输的。
因此它不具备通常的FTP的许多功能,它只能从文件服务器上获得或写入文件,不能列出目录,不进行认证,它传输8位数据。
传输中有三种模式:
netascii,这是8位的ASCII码形式,另一种是octet,这是8位源数据类型;最后一种mail已经不再支持,它将返回的数据直接返回给用户而不是保存为文件。
1.3项目总体设计
按照项目的功能需求构建简单的C/S环境,即一个服务器(Server),一个客户机(Client)。
服务器给用户提供上传(upload)和下载(download)服务,并统一管理上传和提供下载的文件,这些文件统一存放在服务器主机E盘source文件夹下。
客服端能够通过IP地址和默认端口连接到服务器,然后通过输入相应命令选择对应的服务,下载和要上传的文件统一存放在客户端主机的E盘down文件夹下。
服务器和客户端都包含初始化、上传文件、下载文件和出错处理的功能模块,为了便于管理系统,服务器还增加了任务请求和出错信息等服务记录模块。
Figure1:
系统模块图
由于我们小组三个人,我担任组长,当系统模块图建立起来后,就便于分工合作了。
我将数据包构造、服务记录和命令解析分离出来,仅建立函数接口的原型,并写出要求达到的处理效果,其内部实现过程交给其余两位组员实现,我只负责该部分最后的功能校验和嵌入。
而本人负责UDP协议的主体部分,包括服务器客户端的帧的发送和接收、帧接收后的处理、出错处理和数据包发送的方式。
此特别说明下数据包发送的方式采用停止等待协议,其实现过程如下图:
(a)正常情况(b)数据帧丢失
(c)确认帧丢失(d)帧超时
Figure2:
数据包发送模式图
由于服务器和客户端在作为收发数据帧的主体在下载和上传文件时恰好相反,所以在下载文件时,上图的sender是服务器receiver是客户端,当上传文件时sender是客户端receiver是服务器。
以上四种情况在程序中都已考虑到并作了相应的处理。
2系统描述
本系统实现了文件下载和文件上传两种类型的传输功能,辅助有服务记录和断点续传的功能。
它们都是基于socket编程实现的。
系统任何传输起自一个读取或写入文件的请求,这个请求也是连接请求。
如果服务器批准此请求,则服务器打开连接,数据以定长512字节传输。
每个数据包包括一块数据,服务器发出下一个数据包以前必须得到客户对上一个数据包的确认(停等协议)。
如果一个数据包的大小小于512字节,则表示传输结束。
如果数据包在传输过程中丢失,发出方会在超时后重新传输最后一个未被确认的数据包(丢包处理)。
通信的双方都是数据的发出者与接收者,一方传输数据接收应答,另一方发出应答接收数据。
大部分的错误将会导致连接中断,错误由一个错误的数据包引起。
这个包不会被确认,也不会被重新发送,因此另一方无法接收到。
如果错误包丢失,则使用超时机制。
错误主要是由下面三种情况引起的:
不能满足请求,收到的数据包内容错误,而这种错误不能由延时或重发解释,对需要资源的访问丢失(如硬盘满)。
初始连接时候需要发出WRQ(请求写入)或RRQ(请求读取),收到一个确定应答,一个确定可以写出的包或应该读取的第一块数据。
通常确认包包括要确认的包的包号,每个数据包都与一个块号相对应,块号从1开始而且是连续的。
因此对于写入请求的确定是一个比较特殊的情况,因此它的包的包号是0。
如果收到的包是一个错误的包,则这个请求被拒绝。
在断点续传时,当发送方发出数据包后,网线被拔出之后,无论接受方接收到数据包没有,发送方都不会接收到确认帧,此时发送方会重复的发送未被确认的数据包,接收方也会等待这一个或者下一个数据包的来临,当是数据包丢失时会等待此数据帧来临,而当是确认帧丢失时,则会等待下一个数据包。
这样故障恢复后,接收方可能会接收到两个相同的数据包(确认帧丢失情况),所以接收会判断接收到的新数据包的编号是否与上一个相同,不相同则写入文件,但无论是否相同,都会发送确认帧,使发送方发送下一个数据包。
这样就能保证发送文件与接收文件大小一致。
为了防止系统长时间无响应,系统设置了超时结束机制,这与断点续传是一对矛盾。
当把超时时间设置的比路由重启的时间和网线滑落手动接上的时间略长就能很好的解决这个矛盾了。
在本系统中服务器超时设为60s客服端设为30s,这是考虑到服务器一般会比客服端更加繁忙。
3数据结构描述
本系统采用简单包的形式传递数据,TFTP支持一下五种类型的包:
Opcodeoperation(Opcode)
1RRQ:
Readrequest
2WRQ:
Writerequest
3DATA:
Data
4ACK:
Acknowledgment
5ERROR:
Error
各个数据包的帧结构如下:
Figure3:
RRQ/WRQ包
RRQ和WRQ包(代码分别为1和2)的格式如上所示。
文件名是NETASCII码字符,以0结束。
而MODE域包括了字符串"netascii","octet"或"mail",名称不分大小写。
接收到NETASCII格式数据的主机必须将数据转换为本地格式。
OCTET模式用于传输文件,这种文件在源机上以8位格式存储。
如果机器收到OCTET格式文件,返回时必须与原来文件完全一样。
我们的实现建立在发送方和接收方都在相同模式(octet)的情况下。
Figure4:
DATA包
数据在数据包中传输,其格式如上图所示。
数据包的代码为3,它还包括有一个数据块号和数据。
数据块号域从1开始编码,每个数据块依次加1,这样接收方可以确定这个包是新数据还是已经接收过的数据。
数据域从0字节到512字节。
如果数据域是512字节则它不是最后一个包,如果小于512字节则表示这个包是最后一个包。
除了ACK和用于中断的包外,其它的包均得到确认。
发出新的数据包等于确认上次的包。
WRQ和DATA包由ACK或ERROR数据包确认,而RRQ数据包由DATA或ERROR数据包确认。
WRQ数据包被ACK数据包确认,WRQ数据包的包号为0。
Figure5:
ACK包
ACK包的操作码为4,其中的包号为要确认的数据包的包号。
Figure6:
ERROR包
ERROR包的操作码是5,它的格式如上所示。
此包可以被其它任何类型的包确认。
错误码指定错误的类型。
还可以附带错误信息。
此外本系统使用socket编程相关的结构体介绍如下:
<1>WSAData机构体如下
功能是:
存放windowssocket初始化信息。
.
structWSAData{
WORDwVersion;
WORDwHighVersion;
charszDescription[WSADESCRIPTION_LEN+1];
charszSystemStatus[WSASYSSTATUS_LEN+1];
unsignedshortiMaxSockets;
unsignedshortiMaxUdpDg;
charFAR*lpVendorInfo;
};
wVersion为你将使用的Winsock版本号,wHighVersion为载入的Winsock动态库支持的最高版本,注意,它们的高字节代表次版本,低字节代表主版本。
szDescription与szSystemStatus由特定版本的Winsock设置,实际上没有太大用处。
iMaxSockets表示最大数量的并发Sockets,其值依赖于可使用的硬件资源。
iMaxUdpDg表示数据报的最大长度;然而,获取数据报的最大长度,你需要使用WSAEnumProtocols对协议进行查询。
最大数量的并发Sockets并不是什么神奇的数字,它是由可用的物理资源来决定的.。
lpVendorInfo是为Winsock实现而保留的制造商信息,这个在Windows平台上并没有什么用处.。
<2>SOCKETsocket(
intaf,
inttype,
intprotocol
);
应用程序调用socket函数来创建一个能够进行网络通信的套接字。
第一个参数指定应用程序使用的通信协议的协议族,对于TCP/IP协议族,该参数置PF_INET;第二个参数指定要创建的套接字类型,流套接字类型为SOCK_STREAM、数据报套接字类型为SOCK_DGRAM;第三个参数指定应用程序所使用的通信协议。
该函数如果调用成功就返回新创建的套接字的描述符,如果失败就返回INVALID_SOCKET。
套接字描述符是一个整数类型的值。
每个进程的进程空间里都有一个套接字描述符表,该表中存放着套接字描述符和套接字数据结构的对应关系。
该表中有一个字段存放新创建的套接字的描述符,另一个字段存放套接字数据结构的地址,因此根据套接字描述符就可以找到其对应的套接字数据结构。
每个进程在自己的进程空间里都有一个套接字描述符表,但是套接字数据结构都是在操作系统的内核缓冲里。
<3>structsockaddr_in{
shortsin_family;
u_shortsin_port;
structin_addrsin_addr;
charsin_zero[8];
};
sin_family指代协议族,在socket编程中只能是AF_INET
sin_port存储端口号(使用网络字节顺序)
sin_addr存储IP地址,使用in_addr这个数据结构
sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。
s_addr按照网络字节顺序存储IP地址
4软件设计
4.1模块划分
客户端:
主要划分为五个部分:
1、公用数据定义。
2、输出时光标定位,使进度信息始终显示在同一行同一位置。
3、四种数据包的制作。
4、输入命令处理模块:
对键盘来的命令字符串进行解析,得到相应的命令。
5、命令相应模块:
对解析得到的命令进行响应。
主要的两个响应是下载文件及上传文件。
A.下载文件:
从连接到的服务器下载指定的文件。
B.上传文件:
向连接的服务器上传指定的文件。
Figure7:
客户端详细模块图
使用到的重要函数的原型如下:
intmain(intargc,char*argv[])
函数主要完成系统初始化,启动socket,显示提示和版本信息等功能。
此处main函数输入参数没有实际意义。
intstripcmd(char*s,charcmd[][256]);
调用此函数分开输入的命令字符串得到命令代码及参数。
voidparsecmd(char*s);
解析命令,回调相应命令处理函数。
intgetcmdsubscript(char*s);
获取命令数组的下标。
voidgetfile(charcmd[][256],intpcount);
调用此函数从服务器端下载文件。
voidputfile(charcmd[][256],intpcount);
调用此函数向服务器端上传文件。
服务器端:
主要分了五个部分:
1、公用数据定义。
2、输出时光标定位,使进度信息始终显示在同一行同一位置。
3、四种数据包的制作。
4、记录log模块:
用于记录服务器的运行情况。
5、主体功能部分:
下载模块:
对来自客户端的读请求做出响应,向客户端发送文件。
上传模块:
对来自客户端的写请求做出响应,接收来自客户端的文件。
Figure8:
服务器详细模块图
使用到的重要函数的原型如下:
intmain(intargc,char*argv[])
函数主要完成系统初始化,启动socket,监听socket和显示版本信息等功能。
此处main函数输入参数没有实际意义。
voidrecord(inta,structsockaddr_in*sin,char*file)
调用此函数向log文件写入新记录。
voiddownload(structsockaddr_insour_addr,charbuffer[]);
调用此函数对读请求做出响应。
voidupload(structsockaddr_insour_addr,charbuffer[]);
调用此函数对写请求做出响应。
4.2设计流程图
4.3源程序
服务器端(SERVER):
1.定义部分(DEFINE):
/*版本信息*/
#ifndefMAKEWORD
#defineMAKEWORD(l,h)((WORD)(((BYTE)(l))|(((WORD)(BYTE)(h))<<8)))
#endif
#defineWSA_MAJOR_VERSION1
#defineWSA_MINOR_VERSION1
#defineWSA_VERSIONMAKEWORD(WSA_MAJOR_VERSION,WSA_MINOR_VERSION)
#defineTFTP_RRQ1/*读请求(RRQ)*/
#defineTFTP_WRQ2/*写请求(WRQ)*/
#defineTFTP_DATA3/*数据(DATA)*/
#defineTFTP_ACK4/*ACK(ACK)*/
#defineTFTP_ERROR5/*Error(ERROR)*/
#defineTFTP_OCTET1
#defineTFTP_WSTAT_FIRSTACK0
#defineTFTP_WSTAT_NEXTACK1
#defineTFTP_WSTAT_LASTACK2
#defineMAX_RETRY6/*最大重复次数*/
#defineTFTP_NOTEND_DATALEN512+2+2/*数据块长度*/
#defineNot_defined0
#defineFile_not_found1
#defineAccess_violation2
#defineDisk_full3
#defineIllegal_TFTP_operation4
#defineUnknown_port5
#defineFile_already_exists6
#defineNo_such_user7
#defineTime_out8
#defineRead_file_Error9
#defineCannot_create_file10
2..日志函数部分部分(LOG):
charlog[100];
//记录时间变量
chardatetime[20];
//服务器参数
#include"time.h"
#include"stdlib.h"
#include"stdio.h"
inttimeout=2,retran=3;
voidrecord(inta,structsockaddr_in*sin,char*file)
{
chartem[60];
time_tt=time(0);
strftime(datetime,sizeof(datetime),"%y/%m/%d%X",localtime(&t));
strcat(log,datetime);
ZeroMemory(&tem,sizeof(tem));
if(a==1)
sprintf(tem,"收到%s上传文件%s的请求!
\n",inet_ntoa(sin->sin_addr),file);
if(a==2)
sprintf(tem,"%s上传文件%s完毕!
\n",inet_ntoa(sin->sin_addr),file);
if(a==3)
sprintf(tem,"收到%s下载文件%s的请求!
\n",inet_ntoa(sin->sin_addr),file);
if(a==4)
sprintf(tem,"%s下载文件%s完毕!
\n",inet_ntoa(sin->sin_addr),file);
if(a==0)
sprintf(tem,"出错!
操作中断。
\n",inet_ntoa(sin->sin_addr),file);
strcat(log,tem);
FILE*write;
if((write=fopen("e:
\\source\\log.txt","a+"))==NULL)
printf("Openfilefaile!
\n");
fwrite(&log,strlen(log),1,write);
fclose(write);
ZeroMemory(&log,sizeof(log));
3.制作包函数部分(MAKEPACK):
#include"define.h"
intmakeack(unsignedshortnum,char*buffer,intsize);
intmakedata(unsignedshortnum,char*data,intdatasize,char*buffer,intbufsize);
intmakeerr(unsignedshortnum,char*buffer);
/*************************************************************/
intmakeack(unsignedshortnum,char*buffer,intsize)
{
intpos=0;
buffer[pos]=0;
pos++;
buffer[pos]=TFTP_ACK;
pos++;
buffer[pos]=(char)(num>>8);
pos++;
buffer[pos]=(char)num;
pos++;
returnpos;
}
intmakedata(unsignedshortnum,char*data,intdatasize,char*buffer,intbufsize)
{
intpos=0;
buffer[pos]=0;
pos++;
buffer[pos]=TFTP_DATA;
pos++;
buffer[pos]=(char)(num>>8);
pos++;
buffer[pos]=(char)num;
pos++;
memcpy(&buffer[pos],data,datasize);
pos=pos+datasize;
returnpos;
}
intmakeerr(unsignedshortnum,char*buffer)
{
intpos=0;
buffer[pos]=0;
pos++;
buffer[pos]=TFTP_ERROR;
pos++;
buffer[pos]=(char)(num>>8);
pos++;
buffer[pos]=(char)num;
pos++;
returnpos;
}
4.光标定位函数部分(POSITION):
#include
#include
staticintpos(void);
staticintcsrlin(void);
staticvoidlocate(intln,intcol);
staticvoidcolor(intfg,intbg);
staticintbgcolor(void);
staticintfgcolor(void);
staticvoidcls(void);
staticintinkey(void);
#ifndef_wing_con_h
#define_wing_con_h
#include
staticintpos()
{
CONSOLE_SCREEN_BUFFER_INFOCCI;
GetConsoleScreenBufferInfo(
GetStdHandle(STD_OUTPUT_HANDLE),
&CCI
);
returnCCI.dwCursorPosition.X+1;
}
staticintcsrlin()
{
CONSOLE_SCREEN_BUFF