网络文件传输设计报告
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
合肥学院
计算机科学与技术系
课程设计报告
2011~2012学年第一学期
课程Java语言程序设计
课程设计名称网络文件传输系统
专业班级08网络工程(1)班
姓名王阳光
指导教师金莹、郭昌建
2011年9月
根据用户需求,本系统可以分为两个子系统:1、局域网内简单文字的聊天子系统,它是在局域网内选择相应的用户,以便对之发送文字信息;2、局域网内文件、文件夹传输系统,它是在源主机端先把需要传输的文件、文件夹转换成相应的数据流进行传输,到了目的主机端再由之进行还原。
本系统的文字聊天系统实现的功能类似于QQ用户之间的聊天。
在设计系统时,应根据简洁、流畅的特点安排布局,在编制程序时应充分考虑到网络传输的稳定、快捷,真正做到“简洁、高效、流畅、安全”的使用环境。
用户使用的过程为:先是用户登录启动本系统,系统自动检索局域网内的其它用户,并添加到自己的用户列表中,供用户选择。
当用户需要连接到某个用户时,在用户列表中选中该用户,即可发送消息或者文件、文件夹。
接收方可以选择接收或者拒绝接受相应传输的文件或者文件夹。
当用户退出本系统前,系统将通知局域网内的其它用户,其它用户在收到用户下线通知后,将会自动删除用户列表中该用户的信息。
二、设计
1.设计思想
1)局域网用户列表的建立。
用户主机启动本系统时,使用UDP协议向236.136.25.7这个多播地址发送多播数据包,默认端口是3608。
多播数据包内容包含用户名、主机名、IP 地址、登录名、优先级的用户信息;已启动本系统的用户通过3608端口收到此多播数据包后,就会在自己的用户列表中添加这个用户的用户名、IP地址等信息,同时向对方IP发送本机用户的个人信息;从而双方都能建立起用户列表;
2)刷新用户列表时发送的多播数据包和启动的一样,只是在发送数据包前,将本机的用户列表清空,重新建立新的用户列表。
3)传送聊天信息时同样使用UDP协议。
由于UDP协议是无连接协议,传输速度快,但是没有确认机制,是不可靠的协议,需要自己定义返回信息的标志来判断对方是否收到信息。
4)用户离线时发送一个离线多播数据包到236.136.25.7,收到此多播包的用户,根据包中的IP地址删除对方的用户列表信息。
5)传送文件和文件夹是使用TCP协议,端口3608。
TCP是一种面向连接的服务,在文件传输时会先行建立连接,通过链接提供双向、有序且无重复的数据流服务、以及流量控制、差错检测和纠错等服务,从而确保文件传输的正确性。
2.功能设计
在C/S体系结构下,设计局域网文件传送的网络传输软件。
功能包括局域网文件传送和局域网简单文字聊天,可以同时对多个用户发送文件,以及传送文件功能不分目录与文件,
1) C/S(Client/Server)结构,即客户机和服务器结构。
它是软件系统体系结构,通过它可以充分利用两端硬件环境的优势,将任务合理的分配到Client端和Server端来实现,降低了系统的通讯开销。
Client和Server常常分别处在相距很远的两台计算机上,Client 程序的任务是将用户的要求提交给Server程序,再将Server程序返回的结果以特定的形式显示给用户;Server程序的任务是接受Client程序提出的服务请求,进行相应的处理,再将结果返回给Client程序。
图2.1 C/S结构
2)Java的网络编程
Java程序通过.Socket和.ServerSocket在两台主机之间建立I/O数据连接流,文件以字节流的形式从一台主机传送到另一台主机。
在TCP/IP参考模型中,应用程序处在应用层,应用层下面分别是传输层、网络互连层、主机-网络层。
传输层的功能是使源主机和目标主机上的进程可以进行对话。
在传输层定义了两种服务质量不同的协议,即TCP(Transmission Control Protocol,传输控制协议)和UDP(User Datagram Protocol,用户数据包协议)。
TCP协议是一种面向连接的、可靠的协议,主要适用于需要保证数据准确性的场合。
UDP协议是一个不可靠的、无连接协议,主要适用于不需要对报文进行排序和流量控制的场合。
.Socket与.ServerSocket建立在TCP协议的基础上,可靠性好,可用来传送文件。
.DatagramSocket与.DatagramPacket是建立在UDP协议的基础上,可用来在局域网中广播本地主机的IP地址。
3)Java的多线程
Java程序通过ng.Thread和ng.Runnable来实现程序的多线程。
Java 虚拟机允许应用程序并发地运行多个执行线程。
服务器端创建多个服务线程,便可以同时接受多个来自客户端的请求,并给予回应。
从而实现同时向多个用户传送文件的功能。
4) Java的异常控制
Java语言中的所有异常都是由Throwable类继承而来,但在下一层分为两个分支:Error和Exception。
Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误。
应用程序不应该抛出这种类型的对象。
我们在进行Java程序设计的时候,只需要关注Exception层次结构。
这个层次结构又分解为两个分支:一个分支是派生于RuntimeException的异常;另一个分支包含其他异常。
由程序错误导致的异常属于RuntimeException;曾经能够正确运行,而由于某些情况导致的异常不属于RuntimeException。
Java的方法可以抛出一个异常,不予解决;也可以catch一个异常,并给予处理的方法。
图2.2 Java中的异常层次结构
4.详细设计
网络文件传输系统的核心部分为服务器端的线程组测监听等待客户端的连接进行通信。
系统启动的时候,接收端线程首先启动,等待客户端的链接,客户端启动后根据IP地址会自动链接到相应的服务器上进行通信。
1)服务器端的阻塞等待状态
图2.3 服务器端的线程等待2)客户端的线程启动连接
图2.4 客户端的线程启动
3)客户端选择要传输的文件的路径
图2.5 路径指定后文件传输4)接受端接受文件成功默认保存在源码文件夹下
图2.6 文件接受端的文件接受成功5)当一端失去链接后进行的异常处理
图2.7 异常处理
6)类图
图2.8 程序类图
三.调试及测试
1、调试过程中遇到的主要问题及解决方法
1)文件的路径问题,当显示要传输的文件时,直接发送一个文件路径即可,通过FILE ()函数可以直接构造出一个文件实例,然后即可对文件进行操作。
2)文件传输结束标志,文件什么时候传输完毕,什么时候退出SOCKET,不再进行数据的收发,我们可以在传输文件前的通信中把文件大小告诉接收方,当接收方写入文件数据到了该大小后传输结束。
3)文件传输过程中出现发送方与接收方单方中断传输时候,如何通知对方结束程序。
接收端暂停接收文件,将发送暂停的消息给发送端,发送端使用join()方法暂停该写线程;若接收端继续接收,将发送继续的消息给发送端,发送端使用interrupt()方法结束join()方法,发送端继续发送;若接收端终止接收,将发送终止的消息给发送端,发送端终止发送。
4)文件内容的压缩与不压缩一定程度上影响了文件传输的逻辑结构,采取不同的压缩方式,在不同的阶段进行压缩,文件传输的逻辑结构应该不同,这样才能保证传输数据的正确性和传输的高效性。
5)在文件传输过程中是否会有丢包的现象呢?采取TCP方式,由于它是可靠的连接,三次握手机制保证了传输数据的正确性。
而采取UDP方式就不行了。
6)通过构造服务器端套接字实现阻塞等待的结果。
然后用选择器和通道链接实现服务器和客户端的链接。
当通道链接好后,文件传输用字节流进行传输。
2、对设计和编码的回顾讨论和分析
本系统应该实现的功能基本已实现,但还是有一点瑕疵没有解决。
例如,程序正常结束时,有时会出现IO异常;有时电脑没连接局域网,启动程序后出现加入多播组错误。
3、程序运行的性能及效率分析
1)传输速度
文件传输速度是指文件的实际内容开始发送到文件内容全部传输完毕所用的时间来除文件的大小得出的值。
这个值由发送数据包的大小决定,这个大小值要保证一次传输数据要尽可能的多的同时发送数据包的速度也要快,也就是说网络占用率与传输速度相互关联。
程序中我们使用TCP进行文件的传输,并规定了每次传送的字节数1024,这样既做到了速率上的大大改进,使传输速率更高,从而也使该网络文件传输系统实现了性能上的提高。
1)网络资源的使用率
网络资源的利用率是指在文件传输过程中,对网络资源的利用情况,SOCKET在传输数据包的时候有一个最大传输字节,可以用SND_BUF、RCV_BUF分别取出发送端与接收端的最大值。
每次发送数据包时候最大利用了SND_BUF、RCV_BUF的大小,就可以使传输效率大大提高,网络利用率也就很高。
网络资源的使用率也影响文件数据的传输速率。
为了保证每次发包与收包的数据大小的一致性,我们保证了接收端的RCV_BUF与发送端的SND_BUF相等。
3)用户使用
首先,刷新功能采用基于UDP协议的局域网多播和启动线程执行,使刷新速度更加快速;其次,用户发送文件后,会反馈文件发送接受情况。
如果发送成功,用户可以考虑下线;如果发送失败,则要提醒发送方失败信息。
4)使用范围
网络文件传输系统是我们在windows系统下开发的一款文件传输系统,能够实现windows系统下用户之间的文件传输。
四、经验和体会
通过本次Java语言课程设计,使我对面向对象的程序设计有了很大程度的掌握,它将重点放在对象与对象的接口上,与传统的面向过程程序设计有着很大的不同。
就我所做的网络文件传输系统而言,主要用到Java的网络编程,网络编程包括UDP和TCP,UDP是不可靠的
可以向多个用户传送文件,使本系统的功能更加完善;文件流、对象流的使用使得程序在读取文件与对象方便提供了很大的便捷;另外,程序中增加了对异常的处理方法,增强了程序的鲁棒性(Robust);还有系统界面的设计也花费了很大的功夫。
虽然在完成本系统的过程中遇到很多问题,经过自己的反复调试,另外在老师的指导和与同学的讨论,很多问题都解决了。
同时在解决问题的过程中,自己对很多知识都有了进一步的了解;也让我知道了自己哪些地方的不足,在以后的学习中会加强该方面的学习。
在此,我想感谢我的指导老师对我的指导与帮助,还有帮助我的同学们。
附录:源程序
/*
*首先定义文件传输的信息,比如块信息,传输命令,缓冲区信息等。
import java.nio.ByteBuffer;
public class FileUtil {
private byte FileInfo = 0x1; ///文件信息上传命令
private byte FileDB = 0x2; ///文件数据传输命令
private int BlockSize = 512; ///规定文件块大小为512
public byte[] getFileInfoPack(String FileName, int FileSize) {
ByteBuffer buf = ByteBuffer.allocate(260); //分配一个新的字节缓冲区 byte[] infopack = new byte[260];
byte[] filename = new byte[255];
System.arraycopy(FileName.getBytes(), 0, filename, 0, FileName.length()); //从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。
buf.clear();
buf.put(FileInfo); //相对put方法(可选操作),将给定的字节写入此缓冲区的当前位置,然后该位置递增。
buf.putInt(FileSize); //用于写入 int 值的相对 put 方法(可选操作),
//将 4 个包含给定 int 值的字节按照当前的字节顺序写入到
此缓冲区的当前位置,然后将该位置增加 4。
buf.put(filename);
buf.flip();
buf.get(infopack);//此方法将此缓冲区的字节传输到给定的目标数组中
pact();//将缓冲区的当前位置和界限之间的字节(如果有)复制到缓冲区的开始处
return infopack;
}
public byte[] getFileDB(int index, int blocksize, byte[] data) { byte[] filedb = new byte[9 + blocksize];
//对文件本身的传输过程定义
ByteBuffer buf = ByteBuffer.allocate(9 + blocksize);
buf.clear();
buf.put(this.FileDB);
buf.putInt(index);
buf.putInt(blocksize);
buf.put(data,0,blocksize);
buf.flip();
buf.get(filedb);
pact();
return filedb;
public int getBlockSize(){
return this.BlockSize;
}
}
/*
*文件接受端线程的过程。
*/
import java.nio.channels.SocketChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.ByteBuffer;
import java.io.RandomAccessFile;
import java.io.FileOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.channels.ClosedChannelException;
public class ReveiceThread extends Thread {
private FileUtil util = new FileUtil();
/*
* 针对面向流的连接套接字的可选择通道。
*/
private SocketChannel r_channel = null; //
private ByteBuffer buf = ByteBuffer.allocate(util.getBlockSize() + 9); //
public ReveiceThread(SocketChannel channel) throws Exception {
this.r_channel = channel;
this.r_channel.configureBlocking(false);
}
public void run() {
try {
Selector selector = Selector.open();
SelectionKey key = r_channel.register(selector,
SelectionKey.OP_READ);
buf.clear();
File file = null;
FileOutputStream fout = null;//文件输出流对象
RandomAccessFile raf = null;//创建临时文件对象
int FileSize = 0; //文件大小
String FileName = ""; //文件名称
int BlockIndex = 0; //文件块索引号
int BlockSize = 0; //文件块大小
int readlen = 0; //读到的数据长度
int buflen = 0; //buf的长度
byte cmd = 0x0; //命令码
int BlockNum = 0; // 文件块数据计数器
int CompSize = 0; //已经完成数
while (key.selector().isOpen()) {
int selkey = selector.select();
if (selkey == 0) {
readlen = this.r_channel.read(buf); //开始读数据 buflen += readlen; //记录缓冲区buf的长度
if (readlen >= 1) { //读到了数据
cmd = buf.get(); //读取一个字节,获得文件头信息,判断发送信息类型(head or body)
switch (cmd) {
case 0x1: { ///文件信息
if (buflen >= 260) {
FileSize = buf.getInt(); //获取文件大小
byte[] filename = new byte[255];
buf.get(filename); //从缓冲区中读取255个字节,获取文件名 FileName = (new String(filename)).trim();
pact(); //删除读取过的数据
buflen -= 260; //缓冲区大小减去已读的260个字节大小
buf.position(buflen); //数据指针指向buf的最后位置
System.out.println("文件名-->:" + FileName + ",文件大小-->:" +
FileSize);
fout = new FileOutputStream(FileName);
file = new File("~" + FileName + ".tmp"); //创建临时文件
raf = new RandomAccessFile(file, "rw"); //向临时文件中
写入数据
System.out.println("文件创建成功,开始写入数据...");
}
else {
buf.position(buflen); ///数据指针指向buf的最后位置
}
}
break;
case 0x2: { //文件主体数据(body)
if (buflen >= 9) {
BlockIndex = buf.getInt(); //获得文件块索引号
BlockSize = buf.getInt(); //获得文件块大小
if (buflen >= (9 + BlockSize)) { //缓冲区大小减去已读数据
byte[] blockdb = new byte[BlockSize];
buf.get(blockdb); //读取文件块数据
pact(); //删除已读数据
buflen -= (9 + BlockSize);
buf.position(buflen); //数据指针指向buf缓冲区最后位置
if (BlockSize < util.getBlockSize()) {
//如果收到的数据块大小小于定义的最大文件块大小
byte[] tmpdb = new byte[util.getBlockSize()]; ///定义临时最大数据块
System.arraycopy(blockdb, 0, tmpdb, 0, BlockSize);//
raf.seek( (util.getBlockSize() + 4) * BlockIndex); //(+4)是用于存放文件大小信息的
//文件块写入临时文件中将相应数据块放到相应的位置中去
raf.writeInt(BlockSize); //向临时数据块中写入文件大小信息
raf.write(tmpdb); //向临时数据块中写入文件块信息
BlockNum++; //文件块计数器累加
}
else {
//收到的数据等于文件块定义的最大值
raf.seek( (util.getBlockSize() + 4) * BlockIndex); //文件块写入临时文件中将相应数据块放到相应的位置中去
raf.writeInt(BlockSize);
raf.write(blockdb);
BlockNum++;//文件块计数器累加
}
System.out.println("写入临时文件完成: ---->" +
(CompSize += BlockSize) + "****");
FileSize -= BlockSize; ///每收到一块,就从总文件大小里减去
if (FileSize == 0) { ///如果减到0了,说明文件块已经收全了,可以关闭文件了。
for (int i = 0; i < BlockNum; i++) {
//根据文件块个数循环将临时文件写入正式文件
raf.seek( (util.getBlockSize() + 4) * i);
BlockSize = raf.readInt();//从临时文件中读取文件块大小
byte[] tmpdb = new byte[BlockSize];
raf.read(tmpdb);//从临时文件中读取文件块数据
fout.write(tmpdb);//向正式文件中写入文件块
}
raf.close();//释放资源
file.delete(); ///删除临时文件
fout.close();
*******");
}
}
else {
buf.position(buflen); ///数据指针指向buf的最后位置
}
}
else {
buf.position(buflen); ///数据指针指向buf的最后位置
}
}
break;
}
}
}
}
}
catch (ClosedChannelException ex) {
System.out.println("发生ClosedChannelException异常,原因 ----> :" + ex.getMessage());
}
catch (IOException ex) {
System.out.println("打开选择器时异常,原因 ----> :" + ex.getMessage());
}
}
}
/*
*文件接受端线程的套接字启动及接受数据。
*/
import .SocketAddress;
import .InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
public class ReveiceFile {
public static void main(String[] args){
try{
System.out.println("***********文件接收端***********");
/*
* 创建套接字地址,其中 IP 地址为通配符地址,端口号为指定值。
*/
SocketAddress saddress = new InetSocketAddress(9999);
/*
* 针对面向流的侦听套接字的可选择通道.
* 通过调用此类的 open 方法创建服务器套接字通道。
* 新创建的服务器套接字通道已打开,但尚未绑定。
*/
ServerSocketChannel ssc = ServerSocketChannel.open();
/*
* 可通过调用相关服务器套接字的某个 bind 方法来绑定服务器套接字通道
* 将 ServerSocket 绑定到特定地址(IP 地址和端口号)。
*/
ssc.socket().bind(saddress);
/*
* 调整此通道的阻塞模式
* 此类定义了处理通道注册、注销和关闭机制的各种方法。
它会维持此通道的当前阻塞模式及其当前的选择键集。
为 false,则此通道将被置于非阻塞模式
* 新创建的可选择通道总是处于阻塞模式。
在结合使用基于选择器的多路复用时,非阻塞模式是最有用的。
* 向选择器注册某个通道前,必须将该通道置于非阻塞模式,并且在注销之前可能无法返回到阻塞模式。
*/
ssc.configureBlocking(false);
/*
* 可通过调用此类的 open 方法创建选择器
*/
Selector selector = Selector.open();
/*
* 此方法返回一个表示该通道已向选择器注册的新 SelectionKey 对象
* 通过某个通道的 register 方法注册该通道时,所带来的副作用是向选择器的键集中添加了一个键。
* 向给定的选择器注册此通道,返回一个选择键。
*/
SelectionKey selkey = ssc.register(selector,
SelectionKey.OP_ACCEPT);
/*
* 告知此选择器是否已打开。
*/
while (selkey.selector().isOpen()) {
/*
* 选择一组键,其相应的通道已为 I/O 操作准备就绪
* 已更新其准备就绪操作集的键的数目,该数目可能为零
*/
int key = selector.select();
if (key == 0) {
/*
* 接受到此通道套接字的连接,不管此通道的阻塞模式如何,此方法返回的套接字通道(如果有)将处于阻塞模式
* 返回用于新连接的套接字通道,或者如果此通道处于非阻塞模式并且没有要接受的可用连接,
*/
(new ReveiceThread(ssc.accept())).start();
System.out.println("文件发送端已连接上...");
}
}
}catch(Exception err){
System.out.println("等待文件发送端连接时异常,原因:" +
err.getMessage());
}
}
/*
*文件发送端文件传输的过程。
*/
import java.nio.channels.SocketChannel; import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class SendThread
extends Thread {
private FileUtil util = new FileUtil();
private SocketChannel s_channel = null;
private int down_count = 1; //记录文件被接收的次数
private InputStream is = null;
private InputStreamReader isr = null;
private BufferedReader br = null;
private ByteBuffer buf = ByteBuffer.allocate(1024);
public SendThread(SocketChannel channel) {
this.s_channel = channel;
}
public void run() {
String ch = "";
do{
try {
System.out.print("****请选择需要发送的文件:"); //选择进行传输的文件
is = System.in; //获得输入流对象
isr = new InputStreamReader(is);
/*
* 从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取
*/
br = new BufferedReader(isr);
String filePath = br.readLine(); //控制台读取目标文件路径,读取一个文本行
if (filePath.equals("exit")) {
break;
}
int index = 0; //文件块索引
int size = 0; //文件块大小
/*
* File 类的实例是不可变的;也就是说,一旦创建,File 对象表示的抽象路径名将永不改变
* 通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。
*/
File file = new File(filePath); //创建文件对象
/*
* 从文件系统中的某个文件中获得输入字节
* 用于读取诸如图像数据之类的原始字节流
*/
FileInputStream filestream = new FileInputStream(file); //包装一个文件输出流
/*
* Byte 类将基本类型 byte 的值包装在一个对象中
*/
byte[] filedb = new byte[util.getBlockSize()]; //定义一个文件块(字节数组)
System.out.println("文件名是: ---->" + filePath);
byte[] fileinfo = util.getFileInfoPack(file.getName(),
(int) file.length());
buf.clear(); //
buf.put(fileinfo); //将文件块添加到ByteBuffer中
buf.flip(); //将极限设置为当前位置,位置设置为0
size = buf.limit(); //获得极限值
while ( (size -= this.s_channel.write(buf)) > 0) {}
pact();
System.out.println("文件发送完毕O(∩_∩)O哈哈~");
while ( (size = filestream.read(filedb)) != -1) {
byte[] filedatabase = util.getFileDB(index, size, filedb); buf.clear(); //
buf.put(filedatabase);
while ( (size -= this.s_channel.write(buf)) > 0) {}
pact(); //
index++;
}
System.out.println("文件传输完成,共有" + (down_count++) + "个客户端(文件接收端)接收了服务端发送的文件!"); filestream.close();
System.out.println("****发送文件发送成功!****");
}
catch (Exception err) {
System.out.println("发送文件时异常,原因: ---->" +
err.getMessage());
}
//////////////////////////提示是否继续操作
////////////////////////////
System.out.println("是否继续传输:(y or n)");
is = System.in;
isr = new InputStreamReader(is);
br = new BufferedReader(isr);
try {
if(!("y".equalsIgnoreCase(ch))){
System.out.println("您选择了退出程序!"); System.exit(1);//程序退出
}
}
catch (IOException ex1) {
System.out.println("读取字符参数时异常,原因:"+ex1.getMessage());
}
}
while ("y".equalsIgnoreCase(ch));
}
}
/*
*文件发送端套接字的链接
*/
import java.nio.channels.SocketChannel;
import .InetSocketAddress;
import .SocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.io.IOException;
public class SendFile {
public static void main(String[] args) throws IOException {
System.out.println("***********文件发送端***********");
SocketAddress address = new InetSocketAddress("127.0.0.1", 9999); //打开套接字通道,并将其连接到远程
SocketChannel sc = SocketChannel.open(address);
sc.configureBlocking(false);
Selector selector = Selector.open(); //打开选择器
SelectionKey key = sc.register(selector, SelectionKey.OP_READ); //注册key,为read方式
new SendThread(sc).start();
while (key.selector().isOpen()) {
int celkey = selector.select();
if (celkey == 0) {
System.out.println("文件接收端已连接上...");
}
} }。