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[i]);            if (m.find()) {                int start = m.start();                int end = m.end();                addressAndPort = messages[i].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^看得这么辛苦,最后给个该流程的思维导图,导图没有什么规范,就是按照我觉得比较容易理解的方式画出来。(图片好像过大了……)

总结:

android ftp客户端_第1张图片

红色线发送数据请求,蓝色线获取数据分析,主的线索就是这么简单。剩余的就是socket类建立的设计,信息协议类的设计。

个人观点:可能是协议的不同,我看的这个ftp源码跟smack源码比较发现还是smack源码项目设计得比较好,有很多地方可以扩展自定义消息,当然整个设计也是复杂多一点。

更多相关文章

  1. Android获取SIM卡信息--TelephonyManager
  2. Android使用SDK方法详解
  3. Android 发送普通的post请求的方法
  4. Android中View绘制流程以及invalidate()等相关方法分析
  5. Android 获取Gmail邮箱地址方法
  6. Android:读取本地相册与相机获取图片上传到服务器(用字符串的形式
  7. ubuntu的android studio调试小米手机的方法
  8. Android Studio生成APK文件名带上版本号等信息

随机推荐

  1. Unpack/repack ext4 Android(安卓)system
  2. android bounceScrollView
  3. Android属性动画
  4. ch029 Android(安卓)service aidl
  5. Android自定义对话框的使用
  6. Android(安卓)Property System
  7. Android(安卓)DOM解析XML
  8. Android(安卓)Version
  9. android 3.0编译环境需要的所有组件
  10. Android(安卓)TabHost风格