下载工具的需求
提供方便调用的接口。我们希望既能通过命令行调用,也可以方便的在其他程序代码如 Java 代码中调用此下载功能。
良好的异常处理,确保下载的完整性。异常经常发生在网络通讯,磁盘 I/O 上,导致下载过程终止。我们希望遇到这种异常后,下载进程只是挂起而不是退出,异常消除后能自动恢复下载。
提供良好可扩展性。待测软件可以通过诸如 FTP, SAMBA,HTTP 等多种协议发布,当新的协议被采用后,我们希望能以最快的时间来增加对协议的支持。
FTP 和 CIFS 协议简要描述
FTP
FTP(File Transfer Protocol) 的目标是提高文件的共享性,提供非直接使用远程计算机,使存储介质对用户透明和可靠高效地传送数据。 FTP 协议基于 client/server 模式,并且不同于使用单个连接的协议,FTP 使用双向的多连接,一个控制连接 (control connection) 和多个数据连接 (data connection) 。有主动和被动两种连接模式。
主动模式下,客户端随机打开一个大于 1024 的端口向服务器的命令端口 P 发起连接,同时开放 P+1 端口监听,并向服务器发出 PORT 命令,由服务器主动向客户端发起数据连接。
被动模式下,客户端随机打开一个大于 1024 的端口向服务器的命令端口 P 发起连接,同时开放 P+1 端口。然后向服务器发出 PASV 命令,服务器收到命令后,会开放一个大于 1024 的端口 N 进行监听,然后向客户端发出 PORT N 命令。客户端收到命令后,会通过 P+1 号端口连接服务器的端口 N,然后在两个端口之间进行数据传输。
被动模式下,客户端所有的端口将允许动态入站连接,这种模式通常应用在客户端处于防火墙之后的情况,因为防火墙通常配置为不允许外界访问防火墙之后主机。
CIFS
CIFS 协议的全称为 (Common Internet File System) 。 CIFS 的前身为 SMB 系统 (Server Message Block), 这个系统基于 NetBIOS 设定了一套文件共享协议,是 Microsoft 使用 NetBIOS 实现的一个网络文件 / 打印服务系统。随着 Internet 的流行,Microsoft 将原有缺乏技术文档的 SMB 协议进行整理,重新命名为 CIFS,并将它与 NetBIOS 相脱离,最终成为 Internet 上的一个标准协议。 CIFS/SMB 服务器能对外提供文件或打印服务,每个共享资源均有一个共享名。 SMB 协议一般使用广播的方式,但如果每次都使用广播的方式了解当前的网络资源,需要消耗大量的网络资源和浪费较长的查找时间。
Apache commons net 包和 JCIFS 包介绍
Apache commons net
Apache commons net 最开始是由 ORO 公司开发的一个叫 NetComponents 的商业 JAVA 包,于 1998 年贡献给 Apache 软件基金会,它目前支持多达 13 种网络协议,FTP/FTPS,NNTP ,SMTP ,POP3 ,Telnet ,TFTP ,Finger ,Whois ,rexec/rcmd/rlogin ,Time (rdate) and Daytime ,Echo ,Discard ,NTP/SNTP 。当前的最新版本为 2.0 。对于本文讨论的 FTP 协议,Apache commons net 通过与 FTP 站点建立的 Socket 连接,进行 FTP 命令和数据通信。它提供的主要 API 如下:
connect(String hostname, int port) |
与制定地址和端口的 FTP 站点建立 Socket 连接 |
login(String username, String password) |
使用制定用户名和密码登入 FTP 站点 |
logout() |
通过发送 QUIT 命令,登出 FTP 站点 |
disconnect() |
关闭和 FTP 站点的连接,并重置所有连接参数为初始值 |
sendNoOp() |
发送 NOOP 命令至 FTP 站点,与 noop() 类似。防止连接超时,也可以根据返回值检查连接的状态。 |
setBufferSize(int bufSize) |
设置内部缓冲区大小 |
setFileType(int fileType) |
设置文件传输的方式 |
enterLocalPassiveMode() |
在建立数据连接之前发送 PASV 命令至 FTP 站点,将数据连接模式设置为被动模式。 |
enterLocalActiveMode() |
在建立数据连接之前将数据连接模式设置为主动模式。 |
changeWorkingDirectory(String pathname) |
改变当前 FTP 会话的工作目录 |
listFiles() |
发送 LIST 命令至 FTP 站点,使用系统默认的机制列出当前工作目录的文件信息 |
makeDirectory(String pathname) |
在当前工作目录下新建子目录 |
retrieveFile(String remote, OutputStream local) |
取得 FTP 站点上的指定文件并写入指定的字节流中 |
storeFile(String remote, InputStream local) |
将指定的输入流写入 FTP 站点上的一个指定文件 |
JCIFS
JCIFS 是一个纯 JAVA 编写的实现 CIFS/SMB 协议的开源项目。它由 samba 组织负责维护开发。 JCIFS 是一个完整的,丰富的,具有可扩展能力且线程安全的客户端库。这一库可以应用于各种 JAVA 虚拟机访问遵循 CIFS/SMB 网络传输协议的网络资源,包括 Windows 下的共享资源和 Linux & Unix 下的 SAMBA 资源。
JCIFS 的开发方法类似 java 的文件操作功能,它的资源 url 定位:smb://{user}:{password}@{hostname}/{path},smb 为协议名,user 和 password 分别为共享文件机子的登陆名和密码,@ 之后是要访问的资源的主机名或 IP 地址。 path 是资源的共享文件夹名称和共享资源名。例如smb://administrator:password@9.125.242.49/buildFolder/responseFile.txt。
讨论使用 API 构建多线程和可靠的下载
一个简单的应用 apache commons net ftp 包的例子
清单 1:一个简单的应用 apache commons net ftp 包的例子
ftp = new FTPClient();
ftp.addProtocolCommandListener(new PrintCommandListener(
new PrintWriter(System.out)));
try
{
int reply;
ftp.connect(server);
System.out.println("Connected to " + server + ".");
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply))
{
ftp.disconnect();
System.err.println("FTP server refused connection.");
System.exit(1);
}
}
catch (IOException e)
{
if (ftp.isConnected())
{
try
{
ftp.disconnect();
}
catch (IOException f)
{
// do nothing
}
}
System.err.println("Could not connect to server.");
e.printStackTrace();
System.exit(1);
}
try
{
if (!ftp.login(username, password))
{
ftp.logout();
error = true;
break __main;
}
System.out.println("Remote system is " + ftp.getSystemName());
if (binaryTransfer)
ftp.setFileType(FTP.BINARY_FILE_TYPE);
// Use passive mode as default because most of us are
// behind firewalls these days.
ftp.enterLocalPassiveMode();
if (storeFile)
{
InputStream input;
input = new FileInputStream(local);
ftp.storeFile(remote, input);
input.close();
}
else
{
OutputStream output;
output = new FileOutputStream(local);
ftp.retrieveFile(remote, output);
output.close();
}
ftp.logout();
}
catch (FTPConnectionClosedException e)
{
error = true;
System.err.println("Server closed connection.");
e.printStackTrace();
}
catch (IOException e)
{
error = true;
e.printStackTrace();
}
finally
{
if (ftp.isConnected())
{
try
{
ftp.disconnect();
}
catch (IOException f)
{
// do nothing
}
}
}
一个简单的应用 jcifs 包的例子
清单 2:一个简单的应用 jcifs 包的例子
try {
NtlmPasswordAuthentication auth = new NtlmPasswordAuthentication(
"hostname;username:password");
SmbFile smbFile = new SmbFile("smb://hostname/share/test.tar",auth);
int length = smbFile.getContentLength();
byte buffer[] = new byte[length];
SmbFileInputStream in = new SmbFileInputStream(smbFile);
while ((in.read(buffer)) != -1) {
System.out.write(buffer);
System.out.println(buffer.length);
}
in.close();
} catch (Exception e) {
e.printStackTrace();
}
多线程下载
对于 FTP 来说,可以使用多个 FTPClient 实例同时连接到同一个 FTP 站点,也可以在线程之间共享一个 FTPClient 实例,但 FTPClient 不是线程安全的,这时需要额外处理 FTPClient 实例在临界资源上的同步访问。
首先定义并初始化一个任务队列,每个线程从这个队列中取得下载任务。
该任务队列采用线程安全类 LinkedBlockingQueue,它位于 JDK 的 java.util.concurrent 包中。是一个基于已链接节点的、范围任意的 blocking queue,是 Java Collections Framework 的成员。
每个线程从任务队列中取任务时,调用 LinkedBlockingQueue 的 poll 方法,添加任务时调用 put 方法。
清单 3:建立下载任务队列
LinkedBlockingQueue LBQueue = new LinkedBlockingQueue();
LBQueue.put(new buildFileInfo(...));
FTPClient ftpConnection = new FTPClient();
ftpConnection.setBufferSize(4096);
buildFileInfo fileInfo;
while ((fileInfo =LBQueue.poll()) != null) {
//TODO: ftpConnection.retrieveFile()
}
如何确保可靠的下载
可靠下载的第一个条件是准确的文件个数。以 FTP 协议为例,得到所有待下载文件的一个方法是使用栈的结构:
清单 4:确保可靠下载 1
Stack<String> dirStack = new Stack<String>();
FTPFile[] ftpFiles = null;
dirStack.push(latestBuildPath);
while (!dirStack.empty()) {
String currentDir = dirStack.pop();
...
try {
...
ftpFiles = ((FTPClient) connection).listFiles();
} catch (Exception e) {
...
}
for (FTPFile ftpFile : ftpFiles) {
if (ftpFile.isDirectory()) {
dirStack.push(currentDir + ftpFile.getName());
} else {
try {
lbQueue.put(...);//add download task
} catch (InterruptedException e) {
...
}
}
}//~for
}//~while
可靠下载的另一个条件是每一个文件都能完整下载。下面的代码片断描述了这个过程:
清单 5:确保可靠下载 2
while ((fileInfo = BC_FTP.lbQueue.poll()) != null) {
try {
if (!fileInfo.getWorkingObject().equals(workingDirectory)) {
workingDirectory = (String)fileInfo.getWorkingObject();
if(ftpConnection.changeWorkingDirectory(workingDirectory)){//1
throw new changeWDException("error when changing WD");
}
}
File localFolder = new File(fileInfo.getCurrentLocalDir());
localFolder.mkdirs();//2
File localFile = new File(fileInfo.getCurrentLocalDir()
+ fileInfo.getFileName());//3
if (localFile.exists()
&& localFile.length() == fileInfo.getFileSize()) {
return;
}
FileOutputStream fos = new FileOutputStream(localFile);
if (!ftpConnection.retrieveFile(fileInfo.getFileName(), fos)) {
throw new retrieveFileException("error when retrieve file");//4
}
fos.close();
BC_FTP.hasExceptionFlag = false;
} catch (Exception e) {
try {
BC_FTP.lbQueue.put(fileInfo);
} catch (InterruptedException e1) {
}
BC_FTP.hasExceptionFlag = true;
if (!BC_FTP.ftpIsAlive(ftpConnection)) {
ftpConnection = BC_FTP.getFTPConnection();
}
}
}
每个线程在下载一个单独的文件时,可能会遇到以下异常:向 FTP 服务器发送 CWD 命令失败,在本地文件系统读写文件失败,将 FTP 服务器的文件流写入本地文件失败。由于在这些异常抛出前,线程已经从任务队列中取得并删除了该任务,而一旦发生以上异常,将导致文件下载失败,所以,如果捕获到异常,则应当把当前任务重新送回任务队列。
构建可扩展的框架
以上只涉及了 FTP 和 SMB-CIFS 两种协议的软件发布方式,如果发布方式采用新的协议,我们如何让这个下载工具只做少量修改就能集成新的协议支持呢?我们可以将所有协议的下载过程抽象为一个工厂角色 (BC_Factory),每个具体的协议为具体的工厂角色 (BC_FTP)Factory, BC_SMB_Factory),增加对一个新的协议的支持只需要增加一个具体的协议工厂即可,不需要修改原来的代码。类图描述如下:
图 1. 下载组件 UML 类图
图片看不清楚?请点击这里查看原图(大图)。
总结
以上描述的使用 JAVA 开源包编写的文件下载组件涉及到 FTP, CIFS/SMB 两种协议,实际的测试项目中可能还会涉及到 HTTP 协议,由于采用的可扩展的结构,能很方便的实现对其它协议的支持。在我们的实际应用中,还涉及到下载状态提示(邮件,windows 信使),测试结果上传等,在此不再叙述。希望本文能给您的自动化测试工作提供帮助。
分享到:
相关推荐
Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端...
Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端...
Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端...
Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端...
Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端...
Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端...
### Java开源框架详解 #### Spring Framework —— Java开源J2EE框架 **Spring** 是一个功能强大且全面的框架,旨在解决J2EE开发中遇到的常见问题。它通过提供一致的方法来管理业务对象,强调面向接口编程而非具体...
Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端...
Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端...
Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端...
Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端...
Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端...
Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端...
Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端...
Java 3DMenu 界面源码 5个目标文件 内容索引:Java源码,窗体界面,3DMenu Java 3DMenu 界面源码,有人说用到游戏中不错,其实平时我信编写Java应用程序时候也能用到吧,不一定非要局限于游戏吧,RES、SRC资源都有,都...
### Java开源项目Hibernate包作用详解 #### 概述 Hibernate是一个强大的对象关系映射(Object-Relational Mapping,简称ORM)框架,它极大地简化了Java应用程序与数据库交互的过程。通过Hibernate,开发者能够更加...
Java 开源手机短信开发包源码是一个用于在 Java 应用程序中实现短信发送功能的软件组件。这个开发包通常包含一系列的类、接口和方法,使得开发者能够方便地集成短信服务,支持发送验证码、通知以及其他类型的消息到...
JavaWeb通用组件是开发者在构建Web应用程序时经常会用到的一类工具,它们能够极大地提高开发效率,简化代码,提供友好的用户体验。以下是一些常见的JavaWeb组件及其详细说明: 1. **Calendar控件**: Calendar控件...
它是已经编译好的Java类和资源的集合,可以直接引入到其他Java项目中作为依赖使用。开发者可以通过导入这个JAR文件,轻松地在自己的项目中调用工具包提供的各种组件和功能。 "doc" 文件可能是文档资料,通常包含API...
Java开源工具在软件开发中扮演着至关重要的角色,它们提供了丰富的功能,帮助开发者高效地完成各种任务。以下是对这些工具的详细说明: 1. **SmartUpload**:这是一款强大的Java文件上传组件,它使得文件上传操作变...