课设:一个简单FTP服务器的实现_第1页
课设:一个简单FTP服务器的实现_第2页
课设:一个简单FTP服务器的实现_第3页
课设:一个简单FTP服务器的实现_第4页
课设:一个简单FTP服务器的实现_第5页
已阅读5页,还剩17页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

计算机与通信工程学院课程设计 第 21 页课程设计任务书专业:计算机科学与技术 学号:2153626 学生姓名(签名): 设计题目:一个简单FTP服务器的实现一、设计实验条件1208实验室二、设计任务及要求设计要求:任选一门自己熟悉的程序设计语言,利用Socket网络编程机制实现一个简单FTP服务器。要求实现的功能包括:上传、下载、选择数据传输模式,改变目录等,并给出相应的提示。三、设计报告的内容1.设计题目与设计任务1.1设计题目一个简单FTP服务器的实现。1.2设计任务任选一门自己熟悉的程序设计语言,利用Socket网络编程机制实现一个简单FTP服务器。要求实现的功能包括:上传、下载、选择数据传输模式,改变目录等,并给出相应的提示。2.前言2.1 FTP协议Ftp服务是最常用的网络服务之一,虽然在www风行的今天,Ftp已经远不如以前使用得广泛,但是在许多大学等科研单位,Ftp仍然是最常用的文件交换方式。FTP(File Transfer Protocol,文件传输协议)是 TCP/IP 协议组中的协议之一。FTP协议包括两个组成部分,其一为FTP服务器,其二为FTP客户端。其中FTP服务器用来存储文件,用户可以使用FTP客户端通过FTP协议访问位于FTP服务器上的资源。Ftp协议是基于TCP协议的,因此,在一个Ftp会话开始前,客户端和服务器必须首先建立一个 TCP连接,这个TCP连接通常被称作控制连接,客户端通过此连接向服务器发送FTP命令,服务器处理命令后,将返回一个响应码。一个Ftp会话过程中,始终有一个控制连接,如果客户端请求文件,则会有一个数据连接,但FTP协议规定:只要关闭了控制连接,数据连接(如果有)也必须关闭。2.2 SocketSocket起源于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的FTP Server下载文件到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一个TCP socket的缺省行为时把该socket标记为以关闭,然后立即返回到调用进程,该描述字不能再由调用进程使用。(4)Socket中TCP的建立(三次握手)TCP协议通过三个报文段完成连接的建立,这个过程称为三次握手(three-way handshake),过程如下图所示。第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。一个完整的三次握手也就是: 请求-应答-再次确认。当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。3.2功能设计3.2.1 基本部分Client.java(客户端)成员变量:String cmd = ;/ 从标准输入流接收字符串,放在cmd中Socket s;/BufferedReader br;/ 传输数据DataOutputStream dos;/数据输出流DataInputStream dis;/数据输入流client方法public client(String serName) catalogue();try boolean flag = true;while (flag) s = new Socket(serName, 8888);/ 从标准IO中获得输入的命令System.out.println(连接上!);br = new BufferedReader(new InputStreamReader(System.in);/ 将输入的命令放到BufferedReader类变量br中cmd = br.readLine();/ 将br的内容读出放到cmd中/ 发送命令DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(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);else if (cmd.equals(put)put(serName);else if (cmd.equals(cd)cd(serName);else if (cmd.equals(dir)dir(serName);else if (cmd = quit)flag = false; catch (UnknownHostException e) / TODO Auto-generated catch blocke.printStackTrace(); catch (IOException e) / TODO Auto-generated catch blocke.printStackTrace(); finally if (br != null) try br.close(); catch (IOException e) e.printStackTrace();if (s != null) try s.close(); catch (IOException e) e.printStackTrace();主函数:public static void main(String args) new client()Server.java(服务器端)成员变量ServerSocket ss = null;/服务器端socket对象/ 服务器套接字等待请求通过网络传入。它基于该请求执行某些操作,然后可能向请求者返回结果。String dir = ;String cmd = ;DataOutputStream dos;/ 数据输出流String direcFile = ;File rootDirectory;String shareFile;DataInputStream dis; / 数据输入流ArrayList fileArrayList = new ArrayList();/ 文件列表private String shareFiledirectory; /共享文件目录字符串Server方法public server(String shareFiledirectory) this.shareFiledirectory = shareFiledirectory;/ 建立套接字try ss = new ServerSocket(8888);/ 创建一个服务器端Socket,即ss,指定绑定的端口,并监听此端口 catch (IOException e1) e1.printStackTrace();try while (true) boolean flag = true;while (flag) Socket s = ss.accept();/ 阻塞式,等待客户端请求连接,即接受客户端请求System.out.println(已有客户端连接);dis = new DataInputStream(new BufferedInputStream(s.getInputStream();/ BufferedInputStream,利用缓冲区来提高读效率,/ 从此套接字读取字节的输入流/ BufferedInputStream(InputStream in) 参数in指定需要被装饰的输入流cmd = dis.readUTF();/ 从dis输入流读取若干字节,把它转换为采用UTF-8字符编码的字符串,并将其放在cmd String变量里/ UTF-8对ASCII字符采用一个字节形式的编码,对非ASCII字符则采用两个或两个以上字节形式的编码dis.close();/ 关闭输入流s.close();/ 关闭socketSystem.out.println(输出读入的字符串: + cmd);/ 接受后放在cmd里用于判断:if (cmd.equals(get)/调用get方法get();else if (cmd.equals(put)/调用put方法put();else if (cmd.equals(cd)/调用cd方法cd();else if (cmd.equals(dir)/调用dir方法dir(); catch (IOException e) 主方法public static void main(String args) System.out.println(等待连接);new server(C:UsersThinkpadDesktop共享文件);/ 此为shareFiledirectory,共享文件目录3.2.2 显示目录(1)算法描述服务器端1.调用initFileArrayList()方法,将目录下所有文件放在一个数组列表里面fileArrayList2.从此套接字读取字节的输出流,并利用处理流进行封装3.将数组列表中内容放入direcFile,再放入缓冲区中并输出(2)代码实现服务器端public void dir() throws IOException rootDirectory = new File(shareFiledirectory);/ shareFiledirectory表示共享文件的路径fileArrayList.clear();initFileArrayList();for (int i = 0; i fileArrayList.size(); i+) / System.out.println(fileArrayList.get(i).getAbsolutePath();direcFile = direcFile + fileArrayList.get(i).getAbsolutePath() + n;try Socket s = ss.accept();dos = new DataOutputStream(new BufferedOutputStream(s.getOutputStream();/输出文件列表byte buf = direcFile.getBytes();dos.write(buf);dos.flush();dos.close();s.close(); catch (IOException e) public void initFileArrayList() / 将目录下所有文件放在一个数组列表里面fileArrayListif (rootDirectory.isDirectory() / 遍历目录下面的文件和子目录File fileList = rootDirectory.listFiles();System.out.println(文件个数: + fileList.length);for (int i = 0; i fileList.length; i+) / 如果是文件,添加到文件列表中if (fileListi.isFile() fileArrayList.add(new File(fileListi.getAbsolutePath();System.out.println(fileListi.getAbsolutePath();System.out.println(添加了 + fileListi.getName();/ 否则递归遍历子目录else if (fileListi.isDirectory() System.out.println(文件);fileListi.mkdir();/rootDirectory = fileListi;initFileArrayList();客户端public void dir(String serName) System.out.println(以下是目录:);try s = new Socket(serName, 8888);/ 创建一个客户端Socket,指定绑定的端口,并监听此端口dis = new DataInputStream(new BufferedInputStream(s.getInputStream();/ 定义数据输入流int BUFSIZE = 8912;byte buf = new byteBUFSIZE;int data = 0;while (data != -1) / true) if (dis != null) data = dis.read(buf);/ 遇到输入流的末尾,返回-1String str = new String(buf);System.out.println(str); / 读两次才执行跳出 catch (IOException e) System.out.println(错了); finally try dis.close(); catch (IOException e) e.printStackTrace();try s.close(); catch (IOException e) e.printStackTrace();public void catalogue() System.out.println(-);System.out.println(1.dir显示目录);System.out.println(2.get下载);System.out.println(3.put上传);System.out.println(3.cd改变目录);System.out.println(-);(3)结果截图3.2.3 改变目录(1)算法描述服务器端1.创建套接字s,并接收客户端请求,即接收新的路径名2.从此套接字读取字节的输入流,并利用处理流进行封装3.将输入流的内容赋值给shareFiledirectory变量4.调用dir方法(2) 代码实现服务器端public void cd() Socket s = null;try / System.out.println(请输出新的路径:);s = ss.accept();/ 读取要到达的改变的目录:dis = new DataInputStream(new BufferedInputStream(s.getInputStream();shareFiledirectory = dis.readUTF();System.out.println(shareFiledirectory);dir(); catch (IOException e) finally 客户端public void cd(String serName) try Socket s = new Socket(serName, 8888);/ 连接System.out.println(请输出新的路径:);br = new BufferedReader(new InputStreamReader(System.in);/String changedDir = br.readLine();/ 发送命令DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(s.getOutputStream();/ byte buf = changedDir.getBytes();dos.writeUTF(changedDir);dos.flush();dos.close();s.close();dir(serName); catch (IOException e) (3) 结果截图3.2.4上传(1) 算法描述服务器端1.创建套接字s,并接收客户端请求,即接收文件名2.从此套接字读取字节的输入流,并利用处理流进行封装3.获取文件名称 保存上传 文件的路径名4.在本地路径建一个数据输出流5.从输入流将数据读到缓冲区中,并通过上面输出流写入文件6.关闭文件输出流,关闭输入流,关闭套接字(2) 代码实现服务器端public void put() /上传/ System.out.println(上传完成!);/ System.out.println(请输入要上传的文件路径和文件名称:);Socket s = null;try s = ss.accept();/接收客户端请求,即接收文件名/ 下载文件dis = new DataInputStream(new BufferedInputStream(s.getInputStream();/ 从客户端接收存放上传文件路径的输出流int bufferSize = 8192;/ 缓冲区byte buf = new bytebufferSize;int passedlen = 0;long len = 0;String savePath = C:UsersThinkpadDesktop上传;/ 获取文件名称 保存上传 文件的路径名savePath = savePath + File.separator + dis.readUTF();/ 在本地路径建一个数据流DataOutputStream fileOut = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(savePath);/ 获取文件长度len = dis.readLong(); / 从输入流 中读取8个字节System.out.println(文件的长度为: + len + KB);System.out.println(开始接收文件!);/ 获取文件while (true) int read = 0;if (dis != null) read = dis.read(buf); / 从输入流将数据读到缓冲区中,并将返回结果赋给readpassedlen += read;if (read = -1) break;System.out.println(文件接收了 + (passedlen * 100 / len) + %);fileOut.write(buf, 0, read);System.out.println(接收完成,文件存为 + savePath);fileOut.close();dis.close();s.close(); catch (IOException e) 客户端public void put(String serName) System.out.println(请输入要上传的文件路径和文件名称:);Socket s = null;try s = new Socket(serName, 8888);/ 从标准输入流输入要传输的文件在本地的的路径:br = new BufferedReader(new InputStreamReader(System.in);String upFile = br.readLine();/ 将该要上传的文件放到upFile/ 传输文件:dos = new DataOutputStream(new BufferedOutputStream(s.getOutputStream();/ 建立一个输出流对象File file = new File(upFile);/ 定义File对象upFiledos.writeUTF(file.getName();/ file.getname () 返回由此抽象路径名表示的文件或目录的名称。/ 写到输出流dos.flush();/ 清空输出流dos.writeLong(file.length();/ 向输出流写入一个long类型的数据dos.flush();dis = new DataInputStream(new BufferedInputStream(new FileInputStream(upFile);int BUFSIZE = 8192;byte buf = new byteBUFSIZE;while (true) int read = 0;if (dis != null) read = dis.read(buf); else System.out.println(no file founded!);break;if (read = -1) break;dos.write(buf, 0, read);dos.flush(); catch (IOException e) finally try dis.close();dos.close();s.close(); catch (IOException e) e.printStackTrace();(3) 结果截图3.2.5下载(1)算法描述服务器端1.创建套接字s,并接收客户端请求,即接收对方的要求的文件名2.从此套接字读取字节的输入流,并利用处理流进行封装,并读取作为filepath3.在下载目录下创建该文件名的文件4.从此套接字读取字节的输出流,利用处理流进行封装,并输出文件名字及文件长度5.将文件封装到输入流中进行输入6.客户端接收7.关闭输入流、输出流、套接字(2)代码实现服务器端public void get() System.out.println(下载文件!);Socket s = null;/ 连接为空try s = ss.accept();/ 接受客户请求,即接受对方的要求的文件名/在创建 BufferedInputStream 时,会创建一个内部缓冲区数组。dis = new DataInputStream(new BufferedInputStream(s.getInputStream();/ 从此套接字读取字节的输入流,并利用处理流进行封装String filePath = dis.readUTF();/filePath,要下载文件路径System.out.println(filePath);/ 传输文件:dos = new DataOutputStream(new BufferedOutputStream(s.getOutputStream();/s.getInputStream() 返回此套接字的输入流。/s.getOutputStream()返回此套接字的输出流/BufferedInputStream,创建新的缓冲输出流File file = new File(filePath);/在下载目录下创建该文件名的文件dos.writeUTF(file.getName();/以utf-8格式输出文件名字dos.flush(); /清空此数据输出流dos.writeLong(file.length();/以8bite格式输出文件长度dos.flush();/读取文件路径dis = new DataInputStream(new BufferedInputStream(new FileInputStream(filePath);/ 将文件封装到输入流中int BUFSIZE = 8192;byte buf = new byteBUFSIZE;/每次发送的块大小while (true) int read = 0;if (dis != null) read = dis.read(buf);/从此输入流中将byte.length个字节的数据读入一个byte数组中/返回读入缓冲区的总字节数,若已经到达流末尾,则返回-1 else /如果文件路径为空System.out.println(no file founded!);break;if (read = -1) break;dos.write(buf, 0, read);/将数组buf内写入输出流dosdos.flush(); catch (IOException e) finally try dos.close();dis.close();s.close(); catch (IOException e) 客户端public void get(String serName) / 下载/ System.out.println(get+54512);System.out.println(请输入要下载的文件路径和文件名称:);try Socket s = new Socket(serName, 8888);/ 创建一个服务器端Socket,指定绑定的端口,并监听此端口br = new BufferedReader(new InputStreamReader(System.in);/ 建立连接:从标准输入中获得要下载的文件的路径放在brString downFile = br.readLine();/ 读取一个文本行。通过下列字符之一即可认为某行已

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论