课设一个简单FTP服务器的实现.docx
《课设一个简单FTP服务器的实现.docx》由会员分享,可在线阅读,更多相关《课设一个简单FTP服务器的实现.docx(30页珍藏版)》请在冰豆网上搜索。
课设一个简单FTP服务器的实现
课程设计任务书
专业:
计算机科学与技术学号:
2153626学生姓名(签名):
设计题目:
一个简单FTP服务器的实现
一、设计实验条件
1208实验室
二、设计任务及要求
设计要求:
任选一门自己熟悉的程序设计语言,利用Socket网络编程机制实现一个简单FTP服务器。
要求实现的功能包括:
上传、下载、选择数据传输模式,改变目录等,并给出相应的提示。
三、设计报告的内容
1.设计题目与设计任务
1.1设计题目
一个简单FTP服务器的实现。
1.2设计任务
任选一门自己熟悉的程序设计语言,利用Socket网络编程机制实现一个简单FTP服务器。
要求实现的功能包括:
上传、下载、选择数据传输模式,改变目录等,并给出相应的提示。
2.前言
2.1FTP协议
Ftp服务是最常用的网络服务之一,虽然在www风行的今天,Ftp已经远不如以前使用得广泛,但是在许多大学等科研单位,Ftp仍然是最常用的文件交换方式。
FTP(FileTransferProtocol,文件传输协议)是TCP/IP协议组中的协议之一。
FTP协议包括两个组成部分,其一为FTP服务器,其二为FTP客户端。
其中FTP服务器用来存储文件,用户可以使用FTP客户端通过FTP协议访问位于FTP服务器上的资源。
Ftp协议是基于TCP协议的,因此,在一个Ftp会话开始前,客户端和服务器必须首先建立一个TCP连接,这个TCP连接通常被称作控制连接,客户端通过此连接向服务器发送FTP命令,服务器处理命令后,将返回一个响应码。
一个Ftp会话过程中,始终有一个控制连接,如果客户端请求文件,则会有一个数据连接,但FTP协议规定:
只要关闭了控制连接,数据连接(如果有)也必须关闭。
2.2Socket
Socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open–>读写write/read–>关闭close”模式来操作。
Socket就是该模式的一个实现,Socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。
说白了Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。
在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
Socket是应用层与TCP/IP协议族通信的中间软件抽象层。
套接字API最初是作为UNIX操作系统的一部分而开发的,所以套接字API与系统的其他I/O设备集成在一起。
特别是,当应用程序要为因特网通信而创建一个套接字(socket)时,操作系统就返回一个小整数作为描述符(descriptor)来标识这个套接字。
然后,应用程序以该描述符作为传递参数,通过调用函数来完成某种操作(例如通过网络传送数据或接收输入的数据)。
2.3传输模式
FTP的传输有两种方式:
ASCII传输模式和二进制数据传输模式。
ASCII传输模式:
假定用户正在拷贝的文件包含的简单ASCII码文本,如果在远程机器上运行的是不同的操作系统,当文件传输时ftp通常会自动地调整文件的内容以便于把文件解释成另外那台计算机存储文本文件的格式。
即ASCII模式下会转换文件,不能说是不同系统对回车换行解释不同,而是不同的系统有不同的行结束符。
UNIX系统下行结束符是一个字节,即十六进制的0A,而Windows的系统是两个字节,即十六进制的0D0A,所以当你用ASCII方式从UNIX的FTPServer下载文件到Windows系统上时(不管是二进制或者文本文件),每检测到一个字节是0A,就会自动插入一个0D,所以如果你的文件是二进制文件,比如可执行文件、压缩包什么的,就肯定不能用了。
如果你的文件就是UNIX下的文本文件,你用ASCII模式是正确的,要是误用了Binary模式,你在Windows上看这个文件是没有换行的,里面是一个个的黑方块。
二进制传输模式:
在二进制传输中,保存文件的位序,以便原始和拷贝的是逐位一一对应的。
即使目的地机器上包含位序列的文件是没意义的。
例如,macintosh以二进制方式传送可执行文件到Windows系统,在对方系统上,此文件不能执行。
如果你在ASCII方式下传输二进制文件,即使不需要也仍会转译。
这会使传输稍微变慢,也会损坏数据,使文件变得不能用。
(在大多数计算机上,ASCII方式一般假设每一字符的第一有效位无意义,因为ASCII字符组合不使用它。
如果你传输二进制文件,所有的位都是重要的。
)如果你知道这两台机器是同样的,则二进制方式对文本文件和数据文件都是有效的。
2.4设计目的
21世纪是网络的时代,是信息的时代,是多媒体的时代。
Intertnet技术的迅猛发展与普及,推动了世界范围的信息传输和信息交流。
Internet如此流行,其中FTP功不可没。
成千上万的数据、软件分布在世界各地,有了ftp,足不出户,就能轻而易举地得到想要的。
FTP文件传送服务,主要用于存放大量的网络公用软件,常用工具盒技术文档,以及一些著名FTP服务的景象,现在,已经有许多互联网站点都建立了可供大众访问的资料库,这些资料都可以被通过FTP获取。
建立匿名FTP服务器,可以使用户有机会接触到世界上最大的信息库,这个信息库是日积月累起来的,并且还在不断增长,永不关闭,涉及到几乎所有主题。
而且,这一切是免费的。
Internet之所以能延续到今天,是因为人们使用通过标准协议提供标准服务的程序。
匿名FTP是Internet网上发布软件的常用方法。
Internet上的很多程序是由个人创造和维护的,他们通过匿名FTP把它们分发给世界各地的人们。
也可以找到电子杂志、用户网讨论组的档案、技术文件等等。
2.5设计意义
互联网的一大特点是实现信息共享,其中文件传输是信息共享的十分重要的内容之一。
FTP是实现文件传输服务的最主要的规范,并且当需要考虑到文件传输安全、传输质量、访问控制等诸多因素时,FTP服务器就成了解决文件传输问题的关键所在。
在这种情形下,就需要有一个良好的FTP服务器平台来满足用户日益增长的服务需求。
因此,研究FTP服务器相关技术及实现具有重要的意义。
3.设计主体
3.1系统简介
(1)常用命令
FTP的主要操作都是基于各种命令基础之上的。
常用的命令有:
(1)设置传输模式,它包括ASCⅡ(文本)和BINARY二进制模式。
(2)目录操作,改变或显示远程计算机的当前目录(cd、dir/ls命令)。
(3)连接操作,open命令用于建立同远程计算机的连接;close命令用于关闭连接。
(4)发送操作,put命令用于传送文件到远程计算机。
(5)获取操作,get命令用于接收一个文件。
(2)工作原理
服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。
在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。
客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
(3)常用函数
accept()函数
TCP服务器监听到客户端请求之后,就会调用accept()函数取接收请求,这样连接就建立好了。
之后就可以开始网络I/O操作了,即类同于普通文件的读写I/O操作。
accept默认会阻塞进程,直到有一个客户连接建立后返回,它返回的是已连接socket描述字(一个连接套接字),它代表着一个网络已经存在的点点连接。
一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。
内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,相应的已连接socket描述字就被关闭。
read()、write()等函数
read函数是负责从fd中读取内容.当读成功时,read返回实际所读的字节数,如果返回的值是0表示已经读到文件的结束了,小于0表示出现了错误。
如果错误为EINTR说明读是由中断引起的,如果是ECONNREST表示网络连接出了问题。
write函数将buf中的nbytes字节内容写入文件描述符fd,成功时返回写的字节数。
失败时返回-1,并设置errno变量。
在网络程序中,当我们向套接字文件描述符写时有俩种可能。
1)write的返回值大于0,表示写了部分或者是全部的数据。
2)返回的值小于0,此时出现了错误。
我们要根据错误类型来处理。
如果错误为EINTR表示在写的时候出现了中断错误。
如果为EPIPE表示网络连接出现了问题(对方已经关闭了连接)。
close()函数
在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。
close一个TCPsocket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程,该描述字不能再由调用进程使用。
(4)Socket中TCP的建立(三次握手)
TCP协议通过三个报文段完成连接的建立,这个过程称为三次握手(three-wayhandshake),过程如下图所示。
第一次握手:
建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;SYN:
同步序列编号(SynchronizeSequenceNumbers)。
第二次握手:
服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:
客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
一个完整的三次握手也就是:
请求---应答---再次确认。
当客户端调用connect时,触发了连接请求,向服务器发送了SYNJ包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYNJ包,调用accept函数接收请求向客户端发送SYNK,ACKJ+1,这时accept进入阻塞状态;客户端收到服务器的SYNK,ACKJ+1之后,这时connect返回,并对SYNK进行确认;服务器收到ACKK+1时,accept返回,至此三次握手完毕,连接建立。
3.2功能设计
3.2.1基本部分
Client.java(客户端)
成员变量:
Stringcmd="";//从标准输入流接收字符串,放在cmd中
Sockets;//
BufferedReaderbr;//传输数据
DataOutputStreamdos;//数据输出流
DataInputStreamdis;//数据输入流
client方法
publicclient(StringserName){
catalogue();
try{
booleanflag=true;
while(flag){
s=newSocket(serName,8888);
//从标准IO中获得输入的命令
System.out.println("连接上!
!
");
br=newBufferedReader(newInputStreamReader(System.in));
//将输入的命令放到BufferedReader类变量br中
cmd=br.readLine();//将br的内容读出放到cmd中
//发送命令
DataOutputStreamdos=newDataOutputStream(newBufferedOutputStream(s.getOutputStream()));//向服务器发送相关命令,如get,put,cd,dir
//将字符集转换成字节序列
byte[]buf=cmd.getBytes();//使用平台默认的字符集将此String解码为字节序列,并将结果存储到一个新的字节数组中。
//返回:
结果字节数组
dos.writeUTF(cmd);//用UTF编码将一个字符串写入基础输入流,即写到服务端的输入流
dos.flush();//清空此数据输出流
dos.close();//关闭输出流
s.close();//关闭socket
//进行对于应得操作:
if(cmd.equals("get"))
get(serName);
elseif(cmd.equals("put"))
put(serName);
elseif(cmd.equals("cd"))
cd(serName);
elseif(cmd.equals("dir"))
dir(serName);
elseif(cmd=="quit")
flag=false;
}
}catch(UnknownHostExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}catch(IOExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}finally{
if(br!
=null){
try{
br.close();
}catch(IOExceptione){
e.printStackTrace();
}
}
if(s!
=null){
try{
s.close();
}catch(IOExceptione){
e.printStackTrace();
}
}
}
}
主函数:
publicstaticvoidmain(String[]args){
newclient("127.0.0.1")
}
Server.java(服务器端)
成员变量
ServerSocketss=null;//服务器端socket对象
//服务器套接字等待请求通过网络传入。
它基于该请求执行某些操作,然后可能向请求者返回结果。
Stringdir="";
Stringcmd="";
DataOutputStreamdos;//数据输出流
StringdirecFile="";
FilerootDirectory;
StringshareFile;
DataInputStreamdis;//数据输入流
ArrayListfileArrayList=newArrayList();//文件列表
privateStringshareFiledirectory;//共享文件目录字符串
Server方法
publicserver(StringshareFiledirectory){
this.shareFiledirectory=shareFiledirectory;
//建立套接字
try{
ss=newServerSocket(8888);////创建一个服务器端Socket,即ss,指定绑定的端口,并监听此端口
}catch(IOExceptione1){
e1.printStackTrace();
}
try{
while(true){
booleanflag=true;
while(flag){
Sockets=ss.accept();//阻塞式,等待客户端请求连接,即接受客户端请求
System.out.println("已有客户端连接");
dis=newDataInputStream(newBufferedInputStream(s.getInputStream()));//BufferedInputStream,利用缓冲区来提高读效率,
//从此套接字读取字节的输入流
//BufferedInputStream(InputStreamin)参数in指定需要被装饰的输入流
cmd=dis.readUTF();//从dis输入流读取若干字节,把它转换为采用UTF-8字符编码的字符串,并将其放在cmdString变量里
//UTF-8对ASCII字符采用一个字节形式的编码,对非ASCII字符则采用两个或两个以上字节形式的编码
dis.close();//关闭输入流
s.close();//关闭socket
System.out.println("输出读入的字符串:
"+cmd);
//接受后放在cmd里用于判断:
if(cmd.equals("get"))//调用get方法
get();
elseif(cmd.equals("put"))//调用put方法
put();
elseif(cmd.equals("cd"))//调用cd方法
cd();
elseif(cmd.equals("dir"))//调用dir方法
dir();
}
}
}catch(IOExceptione){
}
}
主方法
publicstaticvoidmain(String[]args){
System.out.println("等待连接");
newserver("C:
\\Users\\Thinkpad\\Desktop\\共享文件");//此为shareFiledirectory,共享文件目录
}
3.2.2显示目录
(1)算法描述
服务器端
1.调用initFileArrayList()方法,将目录下所有文件放在一个数组列表里面fileArrayList
2.从此套接字读取字节的输出流,并利用处理流进行封装
3.将数组列表中内容放入direcFile,再放入缓冲区中并输出
(2)代码实现
服务器端
publicvoiddir()throwsIOException{
rootDirectory=newFile(shareFiledirectory);//shareFiledirectory表示共享文件的路径
fileArrayList.clear();
initFileArrayList();
for(inti=0;i//System.out.println(fileArrayList.get(i).getAbsolutePath());
direcFile=direcFile+fileArrayList.get(i).getAbsolutePath()+'\n';
}
try{
Sockets=ss.accept();
dos=newDataOutputStream(newBufferedOutputStream(s.getOutputStream()));//输出文件列表
byte[]buf=direcFile.getBytes();
dos.write(buf);
dos.flush();
dos.close();
s.close();
}catch(IOExceptione){
}
}
publicvoidinitFileArrayList(){//将目录下所有文件放在一个数组列表里面fileArrayList
if(rootDirectory.isDirectory()){
//遍历目录下面的文件和子目录
File[]fileList=rootDirectory.listFiles();
System.out.println("文件个数:
"+fileList.length);
for(inti=0;i//如果是文件,添加到文件列表中
if(fileList[i].isFile()){
fileArrayList.add(newFile(fileList[i].getAbsolutePath()));
System.out.println(fileList[i].getAbsolutePath());
System.out.println("添加了"+fileList[i].getName());
}
//否则递归遍历子目录
elseif(fileList[i].isDirectory()){
System.out.println("文件");
fileList[i].mkdir();//
rootDirectory=fileList[i];
initFileArrayList();
}
}
}
}
客户端
publicvoiddir(StringserName){
System.out.println("以下是目录:
");
try{
s=newSocket(serName,8888);//创建一个客户端Socket,指定绑定的端口,并监听此端口
dis=newDataInputStream(newBufferedInputStream(s.getInputStream()));//定义数据输入流
intBUFSIZE=8912;
byte[]buf=newbyte[BUFSIZE];
intdata=0;
while(data!
=-1){//true){
if(dis!
=null){
data=dis.read(buf);//遇到输入流的末尾,返回-1
Stringstr=newString(buf);
System.out.println(str);