设为首页 收藏本站
查看: 937|回复: 0

[经验分享] android ftp客户端

[复制链接]

尚未签到

发表于 2016-6-9 04:48:52 | 显示全部楼层 |阅读模式
1.
  建立FTPClient对象,连接服务器
  ftp.connect("169.254.xxx.xxx", 21);
  
  


public String[] connect(String host, int port)
throws IllegalStateException, IOException,
FTPIllegalReplyException, FTPException {
synchronized (this.lock) {
if (this.connected) {
throw new IllegalStateException("Client already connected to "
+ host + " on port " + port);
}
Socket connection = null;
try {
connection = this.connector.connectForCommunicationChannel(
host, port);
if (this.security == 1) {
connection = ssl(connection, host, port);
}
this.communication = new FTPCommunicationChannel(connection,
"UTF-8");
for (Iterator i = this.communicationListeners.iterator(); i
.hasNext();) {
this.communication
.addCommunicationListener((FTPCommunicationListener) i
.next());
}
FTPReply wm = this.communication.readFTPReply();
if (!wm.isSuccessCode()) {
throw new FTPException(wm);
}
this.connected = true;
// this.authenticated = false;
// this.parser = null;
this.host = host;
this.port = port;
this.username = null;
this.password = null;
this.utf8Supported = false;
this.restSupported = false;
this.mlsdSupported = false;
this.modezSupported = false;
// this.dataChannelEncrypted = false;
// Returns the welcome message.
return wm.getMessages();
} catch (IOException e) {
// D'oh!
throw e;
} finally {
// If connection has failed...
if (!connected) {
if (connection != null) {
// Close the connection, 'cause it should be open.
try {
connection.close();
} catch (Throwable t) {
;
}
}
}
}
}
}
  
  在连接服务器过程中,
  connection = this.connector.connectForCommunicationChannel( host, port);

  创建了控制通道的socket
  this.communication
= new FTPCommunicationChannel(connection,
"UTF-8");
  然后管理socket创建控制通道的管理类,监听信息入口的接收与发送,建立socket完成后通过FTPReply wm =
this
.communication.readFTPReply(); 获取服务器的信息,判断是否成功。
  

2.


public FTPCommunicationChannel(Socket connection, String charsetName)
throws IOException {
this.connection = connection;
this.charsetName = charsetName;
InputStream inStream = connection.getInputStream();
OutputStream outStream = connection.getOutputStream();
// Wrap the streams into reader and writer objects.
reader = new NVTASCIIReader(inStream, charsetName);
writer = new NVTASCIIWriter(outStream, charsetName);
}
  


private String read() throws IOException {
// Read the line from the server.
String line = reader.readLine();
if (line == null) {
throw new IOException("FTPConnection closed");
}
// Call received() method on every communication listener
// registered.
for (Iterator iter = communicationListeners.iterator(); iter.hasNext();) {
FTPCommunicationListener l = (FTPCommunicationListener) iter.next();
l.received(line);
}
// Return the line read.
return line;
}
  


public void sendFTPCommand(String command) throws IOException {
writer.writeLine(command);
for (Iterator iter = communicationListeners.iterator(); iter.hasNext();) {
FTPCommunicationListener l = (FTPCommunicationListener) iter.next();
l.sent(command);
}
}
  
  监听socket信息接收与发送的管理类,该类创建了两个继承Reader和Writer的类来接收与发送信息——NVTASCIIReader与NVTASCIIWriter,read() 读取方法,sendFTPCommand(String command) 发送信息,每次发送与接收都会触发监听事件

3.
  NVTASCIIReader 类读取信息方法:


public String readLine() throws IOException {
StringBuffer buffer = new StringBuffer();
int previous = -1;
int current = -1;
while (true) {
int i = this.reader.read();
if (i == -1) {
if (buffer.length() == 0) {
return null;
}
return buffer.toString();
}
previous = current;
current = i;
if (/* previous == '\r' && */current == '\n') {
// End of line.
return buffer.toString();
} else if (previous == '\r' && current == 0) {
// Literal new line.
buffer.append(SYSTEM_LINE_SEPARATOR);
} else if (current != 0 && current != '\r') {
buffer.append((char) current);
}
}
}
  该方法每次读取一行一旦遇到\n就返回。相反NVTASCIIReader 类发送信息也是如此:


public void writeLine(String str) throws IOException {
StringBuffer buffer = new StringBuffer();
boolean atLeastOne = false;
StringTokenizer st = new StringTokenizer(str, LINE_SEPARATOR);
int count = st.countTokens();
for (int i = 0; i < count; i++) {
String line = st.nextToken();
if (line.length() > 0) {
if (atLeastOne) {
buffer.append('\r');
buffer.append('\000');
}
buffer.append(line);
atLeastOne = true;
}
}
if (buffer.length() > 0) {
String statement = buffer.toString();
this.writer.write(statement);
this.writer.write("\r\n");
this.writer.flush();
}
}
  该方法主要根据换行符分离字符串,然后加上回车符,直到要发送的文字结束才加上换行符。
  

4.
  FTPReply wm = this.communication.readFTPReply();读取并分析服务器返回的数据,返回一个包括服务器的返回码和信息的FTPReply 类。代码比较无聊,就不贴出来了,可以去查看项目源码。

5.
  接下来是登陆服务器。该方法一步一步验证用户名、密码最后是


public void login(String username, String password, String account)
throws IllegalStateException, IOException,
FTPIllegalReplyException, FTPException {
synchronized (this.lock) {
this.authenticated = false;
this.communication.sendFTPCommand("USER " + username);
FTPReply r = this.communication.readFTPReply();
boolean passwordRequired;
boolean accountRequired;
switch (r.getCode()) {
case 230:
passwordRequired = false;
accountRequired = false;
break;
case 331:
passwordRequired = true;
accountRequired = false;
break;
case 332:
passwordRequired = false;
accountRequired = true;
default:
throw new FTPException(r);
}
if (passwordRequired) {
if (password == null) {
throw new FTPException(331);
}
this.communication.sendFTPCommand("PASS " + password);
r = this.communication.readFTPReply();
switch (r.getCode()) {
case 230:
accountRequired = false;
break;
case 332:
accountRequired = true;
break;
default:
throw new FTPException(r);
}
}
if (accountRequired) {
if (account == null) {
throw new FTPException(332);
}
this.communication.sendFTPCommand("ACCT " + account);
r = this.communication.readFTPReply();
switch (r.getCode()) {
case 230:
break;
default:
throw new FTPException(r);
}
}
this.authenticated = true;
this.username = username;
this.password = password;
}
postLoginOperations();
startAutoNoopTimer();
}
  登陆成功后,运行postLoginOperations() 和startAutoNoopTimer()方法,前一个是获取服务器支持哪些功能,后一个是启动循环等待计时,每段时间都去请求服务器的承认。
  

6.
  FTPFile[] list = ftp.list();这里是重点,主要是获取服务器当前目录的文件。该方法去除了很多判断的枝末^0^,如果想要完整地看它是如何处理的就要去研究查看源码,也不难就是变量多了点。这里使用被动方式,这个方式在文章开头理论就有说明。很简单,先在控制通道(比如A通道)发送一个PASV这个协议(说:hey,man 我想建立socket来传输数据,给个端口我),服务器返回一个随机端口告诉客户端,客户端分析出这个端口,然后与服务器建立一个新的socket。


private FTPDataTransferConnectionProvider openPassiveDataTransferChannel()
throws IOException, FTPIllegalReplyException, FTPException {
// Send the PASV command.
communication.sendFTPCommand("PASV");
// Read the reply.
FTPReply r = communication.readFTPReply();
touchAutoNoopTimer();
if (!r.isSuccessCode()) {
throw new FTPException(r);
}
// Use a regexp to extract the remote address and port.
String addressAndPort = null;
String[] messages = r.getMessages();
for (int i = 0; i < messages.length; i++) {
Matcher m = PASV_PATTERN.matcher(messages);
if (m.find()) {
int start = m.start();
int end = m.end();
addressAndPort = messages.substring(start, end);
break;
}
}
if (addressAndPort == null) {
// The remote server has not sent the coordinates for the
// data transfer connection.
throw new FTPIllegalReplyException();
}
// Parse the string extracted from the reply.
StringTokenizer st = new StringTokenizer(addressAndPort, ",");
int b1 = Integer.parseInt(st.nextToken());
int b2 = Integer.parseInt(st.nextToken());
int b3 = Integer.parseInt(st.nextToken());
int b4 = Integer.parseInt(st.nextToken());
int p1 = Integer.parseInt(st.nextToken());
int p2 = Integer.parseInt(st.nextToken());
final InetAddress remoteAddress;
// Ignore address?
// String useSuggestedAddress = System
// .getProperty(FTPKeys.PASSIVE_DT_USE_SUGGESTED_ADDRESS);
String useSuggestedAddress = "IP";
if ("true".equalsIgnoreCase(useSuggestedAddress)
|| "yes".equalsIgnoreCase(useSuggestedAddress)
|| "1".equals(useSuggestedAddress)) {
remoteAddress = InetAddress.getByAddress(new byte[] { (byte) b1,
(byte) b2, (byte) b3, (byte) b4 });
} else {
remoteAddress = InetAddress.getByName(host);
}
final int remotePort = (p1 << 8) | p2;
FTPDataTransferConnectionProvider provider = new FTPDataTransferConnectionProvider() {
public Socket openDataTransferConnection() {
// Establish the connection.
Socket dtConnection = null;
String remoteHost = remoteAddress.getHostAddress();
try {
dtConnection = connector.connectForDataTransferChannel(
remoteHost, remotePort);
} catch (IOException e) {
}
return dtConnection;
}
public void dispose() {
// nothing to do
}
};
return provider;
}
  该方法返回一个新的socket。
  然后就是通过新的socket来接收服务器端返回的file列表,(谨记:所有请求协议都是通过控制通道(A通道)发送的)


FTPDataTransferConnectionProvider provider = openDataTransferChannel();
String command = "LIST";
// Adds the file/directory selector.
if (fileSpec != null && fileSpec.length() > 0) {
command += " " + fileSpec;
}
// Sends the command.
communication.sendFTPCommand(command);
Socket dtConnection;
try {
try {
dtConnection = provider.openDataTransferConnection();
} finally {
r = communication.readFTPReply();
touchAutoNoopTimer();
if (r.getCode() != 150 && r.getCode() != 125) {
throw new FTPException(r);
}
}
} finally {
provider.dispose();
}
// Fetch the list from the data transfer connection.
ArrayList lines = new ArrayList();
NVTASCIIReader dataReader = null;
try {
// Opens the data transfer connection.
dataTransferInputStream = dtConnection.getInputStream();
// MODE Z enabled?
if (modezEnabled) {
dataTransferInputStream = new InflaterInputStream(
dataTransferInputStream);
}
// Let's do it!
dataReader = new NVTASCIIReader(dataTransferInputStream,
"UTF-8");
String line;
while ((line = dataReader.readLine()) != null) {
if (line.length() > 0) {
lines.add(line);
}
}
} catch (IOException e) {
} finally {
if (dataReader != null) {
try {
dataReader.close();
} catch (Throwable t) {
;
}
}
try {
dtConnection.close();
} catch (Throwable t) {
;
}
// Consume the result reply of the transfer.
communication.readFTPReply();
// Set to null the instance-level input stream.
dataTransferInputStream = null;
}
  这样一个文件目录的获取就完成了,举一反三,下载、上传同样道理。
  
  ^0^看得这么辛苦,最后给个该流程的思维导图,导图没有什么规范,就是按照我觉得比较容易理解的方式画出来。(图片好像过大了……)
DSC0000.png
  

总结:
DSC0001.jpg
  红色线发送数据请求,蓝色线获取数据分析,主的线索就是这么简单。剩余的就是socket类建立的设计,信息协议类的设计。
  个人观点:可能是协议的不同,我看的这个ftp源码跟smack源码比较发现还是smack源码项目设计得比较好,有很多地方可以扩展自定义消息,当然整个设计也是复杂多一点。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-227937-1-1.html 上篇帖子: common-net ftp封装 下篇帖子: ftp主动方式和被动方式
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表