`

使用apache common-net包装实现tftp server

    博客分类:
  • JAVA
 
阅读更多

1、POM文件定义:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>multi-project-parent</artifactId>
        <groupId>multi-project-parent</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>tftp</artifactId>

    <dependencies>
        <dependency>
            <groupId>commons-net</groupId>
            <artifactId>commons-net</artifactId>
            <version>3.3</version>
        </dependency>
    </dependencies>
</project>

==========================================================================

2、Server实现:

package com.wolf.tftp.server;

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.HashSet;
import java.util.Iterator;

import com.wolf.tftp.common.Constants;
import com.wolf.tftp.common.Constants;
import org.apache.commons.net.io.FromNetASCIIOutputStream;
import org.apache.commons.net.io.ToNetASCIIInputStream;
import org.apache.commons.net.tftp.*;

/**
 * A fully multi-threaded tftp server. Can handle multiple clients at the same time. Implements RFC
 * 1350 and wrapping block numbers for large file support.
 * <p/>
 * To launch, just create an instance of the class. An IOException will be thrown if the server
 * fails to start for reasons such as port in use, port denied, etc.
 * <p/>
 * To stop, use the shutdown method.
 * <p/>
 * To check to see if the server is still running (or if it stopped because of an error), call the
 * isRunning() method.
 * <p/>
 * By default, events are not logged to stdout/stderr. This can be changed with the
 * setLog and setLogError methods.
 * <p/>
 * <p/>
 * Example usage is below:
 * <p/>
 * <code>
 * public static void main(String[] args) throws Exception
 * {
 * if (args.length != 1)
 * {
 * System.out
 * .println("You must provide 1 argument - the base path for the server to serve from.");
 * System.exit(1);
 * }
 * <p/>
 * TFTPServer ts = new TFTPServer(new File(args[0]), new File(args[0]), GET_AND_PUT);
 * ts.setSocketTimeout(2000);
 * <p/>
 * System.out.println("TFTP Server running.  Press enter to stop.");
 * new InputStreamReader(System.in).read();
 * <p/>
 * ts.shutdown();
 * System.out.println("Server shut down.");
 * System.exit(0);
 * }
 * <p/>
 * </code>
 *
 * @author <A HREF="mailto:daniel.armbrust.list@gmail.com">Dan Armbrust</A>
 * @since 2.0
 */
public class TFTPServer implements Runnable {


    public static enum ServerMode {GET_ONLY, PUT_ONLY, GET_AND_PUT;}

    private HashSet<TFTPTransfer> transfers_ = new HashSet<TFTPTransfer>();
    private volatile boolean shutdownServer = true;
    private volatile boolean runningServer = false;
    private TFTP serverTftp_;
    private File serverReadDirectory_;
    private File serverWriteDirectory_;
    private int port_;
    private Exception serverException = null;
    private ServerMode mode_;

    /* /dev/null output stream (default) */
    private static final PrintStream nullStream = new PrintStream(
            new OutputStream() {
                @Override
                public void write(int b) {
                }

                @Override
                public void write(byte[] b) throws IOException {
                }
            }
    );

    // don't have access to a logger api, so we will log to these streams, which
    // by default are set to a no-op logger
    private PrintStream log_;
    private PrintStream logError_;

    private int maxTimeoutRetries_ = 3;
    private int socketTimeout_;
    private Thread serverThread;


    public TFTPServer(File serverReadDirectory, File serverWriteDirectory, ServerMode mode, PrintStream log, PrintStream error)
            throws IOException {
        this(serverReadDirectory, serverWriteDirectory, Constants.DEFAULT_TFTP_PORT, mode, log, error);
    }

    /**
     * Start a TFTP Server on the default port (69). Gets and Puts occur in the specified
     * directories.
     * <p/>
     * The server will start in another thread, allowing this constructor to return immediately.
     * <p/>
     * If a get or a put comes in with a relative path that tries to get outside of the
     * serverDirectory, then the get or put will be denied.
     * <p/>
     * GET_ONLY mode only allows gets, PUT_ONLY mode only allows puts, and GET_AND_PUT allows both.
     * Modes are defined as int constants in this class.
     *
     * @param serverReadDirectory  directory for GET requests
     * @param serverWriteDirectory directory for PUT requests
     * @param mode                 A value as specified above.
     * @throws IOException if the server directory is invalid or does not exist.
     */
    public TFTPServer(File serverReadDirectory, File serverWriteDirectory, ServerMode mode)
            throws IOException {
        this(serverReadDirectory, serverWriteDirectory, Constants.DEFAULT_TFTP_PORT, mode, null, null);
    }

    /**
     * Start a TFTP Server on the specified port. Gets and Puts occur in the specified directory.
     * <p/>
     * The server will start in another thread, allowing this constructor to return immediately.
     * <p/>
     * If a get or a put comes in with a relative path that tries to get outside of the
     * serverDirectory, then the get or put will be denied.
     * <p/>
     * GET_ONLY mode only allows gets, PUT_ONLY mode only allows puts, and GET_AND_PUT allows both.
     * Modes are defined as int constants in this class.
     *
     * @param serverReadDirectory  directory for GET requests
     * @param serverWriteDirectory directory for PUT requests
     * @param mode                 A value as specified above.
     * @param log                  Stream to write log message to. If not provided, uses System.out
     * @param errorLog             Stream to write error messages to. If not provided, uses System.err.
     * @throws IOException if the server directory is invalid or does not exist.
     */
    public TFTPServer(File serverReadDirectory, File serverWriteDirectory, int port, ServerMode mode,
                      PrintStream log, PrintStream errorLog) throws IOException {
        port_ = port;
        mode_ = mode;
        log_ = (log == null ? nullStream : log);
        logError_ = (errorLog == null ? nullStream : errorLog);

        log_.println("Starting TFTP Server on port " + port_ + ".  Read directory: "
                + serverReadDirectory + " Write directory: " + serverWriteDirectory
                + " Server Mode is " + mode_);

        serverReadDirectory_ = serverReadDirectory.getCanonicalFile();
        if (!serverReadDirectory_.exists() || !serverReadDirectory.isDirectory()) {
            throw new IOException("The server read directory " + serverReadDirectory_
                    + " does not exist");
        }

        serverWriteDirectory_ = serverWriteDirectory.getCanonicalFile();
        if (!serverWriteDirectory_.exists() || !serverWriteDirectory.isDirectory()) {
            throw new IOException("The server write directory " + serverWriteDirectory_
                    + " does not exist");
        }
        //launch(serverReadDirectory, serverWriteDirectory);
    }

    /**
     * Set the max number of retries in response to a timeout. Default 3. Min 0.
     *
     * @param retries
     */
    public void setMaxTimeoutRetries(int retries) {
        if (retries < 0) {
            throw new RuntimeException("Invalid Value");
        }
        maxTimeoutRetries_ = retries;
    }

    /**
     * Get the current value for maxTimeoutRetries
     */
    public int getMaxTimeoutRetries() {
        return maxTimeoutRetries_;
    }

    /**
     * Set the socket timeout in milliseconds used in transfers. Defaults to the value here:
     * http://commons.apache.org/net/apidocs/org/apache/commons/net/tftp/TFTP.html#DEFAULT_TIMEOUT
     * (5000 at the time I write this) Min value of 10.
     */
    public void setSocketTimeout(int timeout) {
        if (timeout < 10) {
            throw new RuntimeException("Invalid Value");
        }
        socketTimeout_ = timeout;
    }

    /**
     * The current socket timeout used during transfers in milliseconds.
     */
    public int getSocketTimeout() {
        return socketTimeout_;
    }

    /*
     * start the server, throw an error if it can't start.
     */
    public void launch() {
        shutdownServer = false;
        runningServer = true;

        serverTftp_ = new TFTP();
        // This is the value used in response to each client.
        socketTimeout_ = serverTftp_.getDefaultTimeout();

        // we want the server thread to listen forever.
        serverTftp_.setDefaultTimeout(0);

        try {
            serverTftp_.open(port_);
            System.out.println("TFTP Server is listen on "+port_);
        } catch (SocketException e) {
            shutdownServer = true;
            runningServer = false;
            e.printStackTrace();
        }

        serverThread = new Thread(this);
        serverThread.setDaemon(true);
        serverThread.start();
    }

    @Override
    protected void finalize() throws Throwable {
        shutdown();
    }

    /**
     * check if the server thread is still running.
     *
     * @return true if running, false if stopped.
     * @throws Exception throws the exception that stopped the server if the server is stopped from
     *                   an exception.
     */
    public boolean isRunning() throws Exception {
        if (serverException != null) {
            throw serverException;
        }
        return runningServer;
    }

    public void run() {
        try {
            while (!shutdownServer) {
                TFTPPacket tftpPacket;

                tftpPacket = serverTftp_.receive();

                TFTPTransfer tt = new TFTPTransfer(tftpPacket);
                synchronized (transfers_) {
                    transfers_.add(tt);
                }

                Thread thread = new Thread(tt);
                thread.setDaemon(true);
                thread.start();
            }
        } catch (Exception e) {
            if (!shutdownServer) {
                serverException = e;
                logError_.println("Unexpected Error in TFTP Server - Server shut down! + " + e);
            }
        } finally {
            shutdownServer = true; // set this to true, so the launching thread can check to see if it started.
            runningServer = false;
            if (serverTftp_ != null && serverTftp_.isOpen()) {
                serverTftp_.close();
            }
        }
    }

    /**
     * Stop the tftp server (and any currently running transfers) and release all opened network
     * resources.
     */
    public void shutdown() {
        shutdownServer = true;
        runningServer = false;
        synchronized (transfers_) {
            Iterator<TFTPTransfer> it = transfers_.iterator();
            while (it.hasNext()) {
                it.next().shutdown();
            }
        }

        try {
            serverTftp_.close();
        } catch (RuntimeException e) {
            // noop
        }

        try {
            serverThread.join();
        } catch (InterruptedException e) {
            // we've done the best we could, return
        }
        log_.flush();
        logError_.flush();
        log_.close();
        logError_.close();
    }

    /*
     * An instance of an ongoing transfer.
     */
    private class TFTPTransfer implements Runnable {
        private TFTPPacket tftpPacket_;

        private boolean shutdownTransfer = false;

        TFTP transferTftp_ = null;

        public TFTPTransfer(TFTPPacket tftpPacket) {
            tftpPacket_ = tftpPacket;
        }

        public void shutdown() {
            shutdownTransfer = true;
            try {
                transferTftp_.close();
            } catch (RuntimeException e) {
                // noop
            }
        }

        public void run() {
            try {
                transferTftp_ = new TFTP();

                transferTftp_.beginBufferedOps();
                transferTftp_.setDefaultTimeout(socketTimeout_);

                transferTftp_.open();

                if (tftpPacket_ instanceof TFTPReadRequestPacket) {
                    handleRead(((TFTPReadRequestPacket) tftpPacket_));
                } else if (tftpPacket_ instanceof TFTPWriteRequestPacket) {
                    handleWrite((TFTPWriteRequestPacket) tftpPacket_);
                } else {
                    log_.println("Unsupported TFTP request (" + tftpPacket_ + ") - ignored.");
                }
            } catch (Exception e) {
                e.printStackTrace();
                if (!shutdownTransfer) {
                    logError_
                            .println("Unexpected Error in during TFTP file transfer.  Transfer aborted. "
                                    + e);
                }
            } finally {
                try {
                    if (transferTftp_ != null && transferTftp_.isOpen()) {
                        transferTftp_.endBufferedOps();
                        transferTftp_.close();
                    }
                } catch (Exception e) {
                    // noop
                }
                synchronized (transfers_) {
                    transfers_.remove(this);
                }
            }
        }

        /*
         * Handle a tftp read request.
         */
        private void handleRead(TFTPReadRequestPacket trrp) throws IOException, TFTPPacketException {
            InputStream is = null;
            try {
                if (mode_ == ServerMode.PUT_ONLY) {
                    transferTftp_.bufferedSend(new TFTPErrorPacket(trrp.getAddress(), trrp
                            .getPort(), TFTPErrorPacket.ILLEGAL_OPERATION,
                            "Read not allowed by server."));
                    return;
                }

                try {
                    is = new BufferedInputStream(new FileInputStream(buildSafeFile(
                            serverReadDirectory_, trrp.getFilename(), false)));
                } catch (FileNotFoundException e) {
                    transferTftp_.bufferedSend(new TFTPErrorPacket(trrp.getAddress(), trrp
                            .getPort(), TFTPErrorPacket.FILE_NOT_FOUND, e.getMessage()));
                    return;
                } catch (Exception e) {
                    transferTftp_.bufferedSend(new TFTPErrorPacket(trrp.getAddress(), trrp
                            .getPort(), TFTPErrorPacket.UNDEFINED, e.getMessage()));
                    return;
                }

                if (trrp.getMode() == TFTP.NETASCII_MODE) {
                    is = new ToNetASCIIInputStream(is);
                }

                byte[] temp = new byte[TFTPDataPacket.MAX_DATA_LENGTH];

                TFTPPacket answer;

                int block = 1;
                boolean sendNext = true;

                int readLength = TFTPDataPacket.MAX_DATA_LENGTH;

                TFTPDataPacket lastSentData = null;

                // We are reading a file, so when we read less than the
                // requested bytes, we know that we are at the end of the file.
                while (readLength == TFTPDataPacket.MAX_DATA_LENGTH && !shutdownTransfer) {
                    if (sendNext) {
                        readLength = is.read(temp);
                        if (readLength == -1) {
                            readLength = 0;
                        }

                        lastSentData = new TFTPDataPacket(trrp.getAddress(), trrp.getPort(), block,
                                temp, 0, readLength);
                        transferTftp_.bufferedSend(lastSentData);
                    }

                    answer = null;

                    int timeoutCount = 0;

                    while (!shutdownTransfer
                            && (answer == null || !answer.getAddress().equals(trrp.getAddress()) || answer
                            .getPort() != trrp.getPort())) {
                        // listen for an answer.
                        if (answer != null) {
                            // The answer that we got didn't come from the
                            // expected source, fire back an error, and continue
                            // listening.
                            log_.println("TFTP Server ignoring message from unexpected source.");
                            transferTftp_.bufferedSend(new TFTPErrorPacket(answer.getAddress(),
                                    answer.getPort(), TFTPErrorPacket.UNKNOWN_TID,
                                    "Unexpected Host or Port"));
                        }
                        try {
                            answer = transferTftp_.bufferedReceive();
                        } catch (SocketTimeoutException e) {
                            if (timeoutCount >= maxTimeoutRetries_) {
                                throw e;
                            }
                            // didn't get an ack for this data. need to resend
                            // it.
                            timeoutCount++;
                            transferTftp_.bufferedSend(lastSentData);
                            continue;
                        }
                    }

                    if (answer == null || !(answer instanceof TFTPAckPacket)) {
                        if (!shutdownTransfer) {
                            logError_
                                    .println("Unexpected response from tftp client during transfer ("
                                            + answer + ").  Transfer aborted.");
                        }
                        break;
                    } else {
                        // once we get here, we know we have an answer packet
                        // from the correct host.
                        TFTPAckPacket ack = (TFTPAckPacket) answer;
                        if (ack.getBlockNumber() != block) {
                            /*
                             * The origional tftp spec would have called on us to resend the
                             * previous data here, however, that causes the SAS Syndrome.
                             * http://www.faqs.org/rfcs/rfc1123.html section 4.2.3.1 The modified
                             * spec says that we ignore a duplicate ack. If the packet was really
                             * lost, we will time out on receive, and resend the previous data at
                             * that point.
                             */
                            sendNext = false;
                        } else {
                            // send the next block
                            block++;
                            if (block > 65535) {
                                // wrap the block number
                                block = 0;
                            }
                            sendNext = true;
                        }
                    }
                }
            } finally {
                try {
                    if (is != null) {
                        is.close();
                    }
                } catch (IOException e) {
                    // noop
                }
            }
        }

        /*
         * handle a tftp write request.
         */
        private void handleWrite(TFTPWriteRequestPacket twrp) throws IOException,
                TFTPPacketException {
            OutputStream bos = null;
            try {
                if (mode_ == ServerMode.GET_ONLY) {
                    transferTftp_.bufferedSend(new TFTPErrorPacket(twrp.getAddress(), twrp
                            .getPort(), TFTPErrorPacket.ILLEGAL_OPERATION,
                            "Write not allowed by server."));
                    return;
                }

                int lastBlock = 0;
                String fileName = twrp.getFilename();

                try {
                    File temp = buildSafeFile(serverWriteDirectory_, fileName, true);
                    //if file exists, overwrite
                    /*if (temp.exists()) {
                        transferTftp_.bufferedSend(new TFTPErrorPacket(twrp.getAddress(), twrp
                                .getPort(), TFTPErrorPacket.FILE_EXISTS, "File already exists"));
                        return;
                    }*/
                    bos = new BufferedOutputStream(new FileOutputStream(temp,false));

                    if (twrp.getMode() == TFTP.NETASCII_MODE) {
                        bos = new FromNetASCIIOutputStream(bos);
                    }
                } catch (Exception e) {
                    transferTftp_.bufferedSend(new TFTPErrorPacket(twrp.getAddress(), twrp
                            .getPort(), TFTPErrorPacket.UNDEFINED, e.getMessage()));
                    return;
                }

                TFTPAckPacket lastSentAck = new TFTPAckPacket(twrp.getAddress(), twrp.getPort(), 0);
                transferTftp_.bufferedSend(lastSentAck);

                while (true) {
                    // get the response - ensure it is from the right place.
                    TFTPPacket dataPacket = null;

                    int timeoutCount = 0;

                    while (!shutdownTransfer
                            && (dataPacket == null
                            || !dataPacket.getAddress().equals(twrp.getAddress()) || dataPacket
                            .getPort() != twrp.getPort())) {
                        // listen for an answer.
                        if (dataPacket != null) {
                            // The data that we got didn't come from the
                            // expected source, fire back an error, and continue
                            // listening.
                            log_.println("TFTP Server ignoring message from unexpected source.");
                            transferTftp_.bufferedSend(new TFTPErrorPacket(dataPacket.getAddress(),
                                    dataPacket.getPort(), TFTPErrorPacket.UNKNOWN_TID,
                                    "Unexpected Host or Port"));
                        }

                        try {
                            dataPacket = transferTftp_.bufferedReceive();
                        } catch (SocketTimeoutException e) {
                            if (timeoutCount >= maxTimeoutRetries_) {
                                throw e;
                            }
                            // It didn't get our ack. Resend it.
                            transferTftp_.bufferedSend(lastSentAck);
                            timeoutCount++;
                            continue;
                        }
                    }

                    if (dataPacket != null && dataPacket instanceof TFTPWriteRequestPacket) {
                        // it must have missed our initial ack. Send another.
                        lastSentAck = new TFTPAckPacket(twrp.getAddress(), twrp.getPort(), 0);
                        transferTftp_.bufferedSend(lastSentAck);
                    } else if (dataPacket == null || !(dataPacket instanceof TFTPDataPacket)) {
                        if (!shutdownTransfer) {
                            logError_
                                    .println("Unexpected response from tftp client during transfer ("
                                            + dataPacket + ").  Transfer aborted.");
                        }
                        break;
                    } else {
                        int block = ((TFTPDataPacket) dataPacket).getBlockNumber();
                        byte[] data = ((TFTPDataPacket) dataPacket).getData();
                        int dataLength = ((TFTPDataPacket) dataPacket).getDataLength();
                        int dataOffset = ((TFTPDataPacket) dataPacket).getDataOffset();

                        if (block > lastBlock || (lastBlock == 65535 && block == 0)) {
                            // it might resend a data block if it missed our ack
                            // - don't rewrite the block.
                            bos.write(data, dataOffset, dataLength);
                            lastBlock = block;
                        }

                        lastSentAck = new TFTPAckPacket(twrp.getAddress(), twrp.getPort(), block);
                        transferTftp_.bufferedSend(lastSentAck);
                        if (dataLength < TFTPDataPacket.MAX_DATA_LENGTH) {
                            // end of stream signal - The tranfer is complete.
                            bos.close();

                            // But my ack may be lost - so listen to see if I
                            // need to resend the ack.
                            for (int i = 0; i < maxTimeoutRetries_; i++) {
                                try {
                                    dataPacket = transferTftp_.bufferedReceive();
                                } catch (SocketTimeoutException e) {
                                    // this is the expected route - the client
                                    // shouldn't be sending any more packets.
                                    break;
                                }

                                if (dataPacket != null
                                        && (!dataPacket.getAddress().equals(twrp.getAddress()) || dataPacket
                                        .getPort() != twrp.getPort())) {
                                    // make sure it was from the right client...
                                    transferTftp_
                                            .bufferedSend(new TFTPErrorPacket(dataPacket
                                                    .getAddress(), dataPacket.getPort(),
                                                    TFTPErrorPacket.UNKNOWN_TID,
                                                    "Unexpected Host or Port"));
                                } else {
                                    // This means they sent us the last
                                    // datapacket again, must have missed our
                                    // ack. resend it.
                                    transferTftp_.bufferedSend(lastSentAck);
                                }
                            }

                            // all done.
                            break;
                        }
                    }
                }
            } finally {
                if (bos != null) {
                    bos.close();
                }
            }
        }

        /*
         * Utility method to make sure that paths provided by tftp clients do not get outside of the
         * serverRoot directory.
         */
        private File buildSafeFile(File serverDirectory, String fileName, boolean createSubDirs)
                throws IOException {
            File temp = new File(serverDirectory, fileName);
            temp = temp.getCanonicalFile();

            if (!isSubdirectoryOf(serverDirectory, temp)) {
                throw new IOException("Cannot access files outside of tftp server root.");
            }

            // ensure directory exists (if requested)
            if (createSubDirs) {
                createDirectory(temp.getParentFile());
            }

            return temp;
        }

        /*
         * recursively create subdirectories
         */
        private void createDirectory(File file) throws IOException {
            File parent = file.getParentFile();
            if (parent == null) {
                throw new IOException("Unexpected error creating requested directory");
            }
            if (!parent.exists()) {
                // recurse...
                createDirectory(parent);
            }

            if (parent.isDirectory()) {
                if (file.isDirectory()) {
                    return;
                }
                boolean result = file.mkdir();
                if (!result) {
                    throw new IOException("Couldn't create requested directory");
                }
            } else {
                throw new IOException(
                        "Invalid directory path - file in the way of requested folder");
            }
        }

        /*
         * recursively check to see if one directory is a parent of another.
         */
        private boolean isSubdirectoryOf(File parent, File child) {
            File childsParent = child.getParentFile();
            if (childsParent == null) {
                return false;
            }
            if (childsParent.equals(parent)) {
                return true;
            } else {
                return isSubdirectoryOf(parent, childsParent);
            }
        }
    }

    /**
     * Set the stream object to log debug / informational messages. By default, this is a no-op
     *
     * @param log
     */
    public void setLog(PrintStream log) {
        this.log_ = log;
    }

    /**
     * Set the stream object to log error messsages. By default, this is a no-op
     *
     * @param logError
     */
    public void setLogError(PrintStream logError) {
        this.logError_ = logError;
    }
}

==========================================================================

3、包装server实现

package com.wolf.tftp.server;

import java.io.*;

/**
 * Created with IntelliJ IDEA.
 * User: jiyanbin
 * Date: 13-8-6
 * Time: 下午12:43
 * To change this template use File | Settings | File Templates.
 */
public class TFTPServerConsole {
    private TFTPServer server;
    private String serverReadDir;
    private String serverWriteDir;
    private int port;
    private int timeout = 30000;
    private TFTPServer.ServerMode serverMode;
    //single instance
    private volatile static TFTPServerConsole console;

    private TFTPServerConsole() throws Exception {
        //set default value;
        serverReadDir = SERVER_READ_DIR;
        serverWriteDir = SERVER_WRITE_DIR;
        port = DEFAULT_TFTP_PORT;
        serverMode = TFTPServer.ServerMode.GET_AND_PUT;

        server = new TFTPServer(new File(serverReadDir), new File(serverWriteDir), port, serverMode,
                null, null);
        server.setSocketTimeout(timeout);
    }

    public static TFTPServerConsole getInstance() throws Exception {
        if (console == null) {
            synchronized (Object.class) {
                if (console == null) {
                    console = new TFTPServerConsole();
                }
            }
        }
        return console;
    }

    public void startServer() {
        try {
            if (server != null && !server.isRunning()) {
                server.launch();
            } else {
                System.out.println("TFTP server has been running already!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void stopServer() {
        try {
            if (server != null && server.isRunning()) {
                server.shutdown();
                System.out.println("TFTP Server shutdown.");
            } else {
                System.out.println("TFTP server is not running");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void setServerReadDir(String serverReadDir) {
        this.serverReadDir = serverReadDir;
    }

    public void setServerWriteDir(String serverWriteDir) {
        this.serverWriteDir = serverWriteDir;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public void setServerMode(TFTPServer.ServerMode serverMode) {
        this.serverMode = serverMode;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }
}

==========================================================================

4、客户端工作器

package com.wolf.tftp.client;

import org.apache.commons.net.tftp.TFTP;
import org.apache.commons.net.tftp.TFTPClient;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.SocketException;
import java.net.UnknownHostException;

/**
 * Created with IntelliJ IDEA.
 * User: jiyanbin
 * Date: 13-8-7
 * Time: 上午11:02
 * To change this template use File | Settings | File Templates.
 */
public class TFTPClientWorker {
    private final String host;
    private final int port;
    private final boolean isReceive;
    private final int transferMode;
    private final String localFilename;
    private final String remoteFilename;

    /**
     * TFTPClientTask Builder
     */
    public static class TFTPClientWorkerBuilder {
        private String host = "localhost";
        private int port = DEFAULT_TFTP_PORT;
        private boolean isReceive;
        private int mode = TFTP.BINARY_MODE;// or TFTP.ASCII_MODE
        private String localFilename;
        private String remoteFilename;

        public TFTPClientWorkerBuilder host(String host) {
            this.host = host;
            return this;
        }

        public TFTPClientWorkerBuilder port(int port) {
            this.port = port;
            return this;
        }

        public TFTPClientWorkerBuilder isReceive(boolean isReceiver) {
            this.isReceive = isReceiver;
            return this;
        }

        public TFTPClientWorkerBuilder mode(int mode) {
            this.mode = mode;
            return this;
        }

        public TFTPClientWorkerBuilder localFilename(String localFilename) {
            this.localFilename = localFilename;
            return this;
        }

        public TFTPClientWorkerBuilder remoteFilename(String remoteFilename) {
            this.remoteFilename = remoteFilename;
            return this;
        }

        public TFTPClientWorker builder() {
            return new TFTPClientWorker(this);
        }
    }

    private TFTPClientWorker(TFTPClientWorkerBuilder builder) {
        host = builder.host;
        port = builder.port;
        isReceive = builder.isReceive;
        transferMode = builder.mode;
        localFilename = builder.localFilename;
        remoteFilename = builder.remoteFilename;
    }

    public String getHost() {
        return host;
    }

    public boolean isReceive() {
        return isReceive;
    }

    public int getTransferMode() {
        return transferMode;
    }

    public String getLocalFilename() {
        return localFilename;
    }

    public String getRemoteFilename() {
        return remoteFilename;
    }

    /**
     *
     * @return the File path in TFTP server
     * @throws SocketException
     */
    public String doJob() throws SocketException{
        TFTPClient tftp = new TFTPClient();
        // We want to timeout if a response takes longer than 60 seconds
        tftp.setDefaultTimeout(60000);
        // Open local socket
        try {
            tftp.open();
        } catch (SocketException e) {
            System.err.println("Error: could not open local UDP socket.");
            System.err.println(e.getMessage());
            throw e;
        }
        if (isReceive) {
            received(tftp);
            return SERVER_WRITE_DIR.endsWith(File.separator)?
                    SERVER_WRITE_DIR + remoteFilename:
                    SERVER_WRITE_DIR + File.separator + remoteFilename;
        } else {
            send(tftp);
            return SERVER_READ_DIR.endsWith(File.separator)?
                    SERVER_READ_DIR + remoteFilename:
                    SERVER_READ_DIR + File.separator + remoteFilename;
        }
    }

    private void received(TFTPClient tftp) {
        FileOutputStream output = null;
        File file = new File(localFilename);
        // Try to open local file for writing
        try {
            // If file exists,  overwrite it.
            output = new FileOutputStream(file,false);
        } catch (IOException e) {
            tftp.close();
            System.err.println("Error: could not open local file for writing.");
            System.err.println(e.getMessage());
            return;
        }
        // Try to receive remote file via TFTP
        try {
            tftp.receiveFile(remoteFilename, transferMode, output, host,port);
        } catch (UnknownHostException e) {
            System.err.println("Error: could not resolve hostname.");
            System.err.println(e.getMessage());
            return;
        } catch (IOException e) {
            System.err.println(
                    "Error: I/O exception occurred while receiving file.");
            System.err.println(e.getMessage());
            return;
        } finally {
            // Close local socket and output file
            tftp.close();
            try {
                if (output != null) {
                    output.close();
                }
                //closed = true;
            } catch (IOException e) {
                //closed = false;
                System.err.println("Error: error closing file.");
                System.err.println(e.getMessage());
            }
        }
    }

    private void send(TFTPClient tftp) {
        // We're sending a file
        FileInputStream input = null;
        // Try to open local file for reading
        try {
            input = new FileInputStream(localFilename);
        } catch (IOException e) {
            tftp.close();
            System.err.println("Error: could not open local file for reading.");
            System.err.println(e.getMessage());
            return;
        }
        // Try to send local file via TFTP
        try {
            tftp.sendFile(remoteFilename, transferMode, input, host, port);
        } catch (UnknownHostException e) {
            System.err.println("Error: could not resolve hostname.");
            System.err.println(e.getMessage());
            return;
        } catch (IOException e) {
            System.err.println(
                    "Error: I/O exception occurred while sending file.");
            System.err.println(e.getMessage());
            return;
        } finally {
            // Close local socket and input file
            tftp.close();
            try {
                if (input != null) {
                    input.close();
                }
                //closed = true;
            } catch (IOException e) {
                //closed = false;
                System.err.println("Error: error closing file.");
                System.err.println(e.getMessage());
            }
        }
    }
}

==========================================================================

5、公共常量类

package com.wolf.tftp.common;

/**
 * Created with IntelliJ IDEA.
 * User: jiyanbin
 * Date: 13-8-7
 * Time: 上午11:00
 * To change this template use File | Settings | File Templates.
 */
public abstract class Constants {
    public static final int DEFAULT_TFTP_PORT = 69;
    public static final String SERVER_READ_DIR;
    public static final String SERVER_WRITE_DIR;

    static {
        SERVER_READ_DIR = System.getProperty("tftp.server.read_dir",System.getProperty("java.io.tmpdir"));
        SERVER_WRITE_DIR = System.getProperty("tftp.server.write_dir",System.getProperty("java.io.tmpdir"));
    }
}

==========================================================================

6、测试,可以测试并发时的性能

启动tftp server

package com.wolf.tftp.test;

import com.wolf.tftp.server.TFTPServer;
import com.wolf.tftp.server.TFTPServerConsole;

import java.io.*;

/**
 * Created with IntelliJ IDEA.
 * User: jiyanbin
 * Date: 13-8-6
 * Time: 下午12:43
 * To change this template use File | Settings | File Templates.
 */
public class TFTPServerConsoleTest {

    private final static String WRITE_PATH = "E:\\tftp";
    private final static String READ_PATH = "E:\\tftp";

    private final static String LOG_FILE="e:\\log\\tftp.log";
    private final static String LOG_ERROR_FILE="e:\\log\\tftp.err";

    public static void main(String[] args) throws Exception {
        TFTPServerConsole console = TFTPServerConsole.getInstance();
        console.startServer();
        System.out.println("TFTP Server running.  Press enter to stop.");
        new InputStreamReader(System.in).read();
        console.stopServer();
        System.exit(0);
    }


    public void startServer() throws Exception {
        {
            PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream(LOG_FILE)));
            PrintStream err = new PrintStream(new BufferedOutputStream(new FileOutputStream(LOG_ERROR_FILE)));
            TFTPServer ts = new TFTPServer(new File(READ_PATH), new File(WRITE_PATH),6900, TFTPServer.ServerMode.GET_AND_PUT,
                    out,err );
            ts.setSocketTimeout(2000);
            System.out.println("TFTP Server running.  Press enter to stop.");
            new InputStreamReader(System.in).read();
            ts.shutdown();
            System.out.println("Server shut down.");
            System.exit(0);
        }
    }
}

 

客户端调用

package com.wolf.tftp.test;

import org.apache.commons.net.tftp.TFTP;

import java.util.Random;

/**
 * Created with IntelliJ IDEA.
 * User: jiyanbin
 * Date: 13-8-6
 * Time: 下午2:18
 * To change this template use File | Settings | File Templates.
 */
public class TFTPClientConsoleTest {
    static final String USAGE =
            "Usage: tftp [options] hostname localfile remotefile\n\n" +
                    "hostname   - The name of the remote host\n" +
                    "localfile  - The name of the local file to send or the name to use for\n" +
                    "\tthe received file\n" +
                    "remotefile - The name of the remote file to receive or the name for\n" +
                    "\tthe remote server to use to name the local file being sent.\n\n" +
                    "options: (The default is to assume -r -b)\n" +
                    "\t-s Send a local file\n" +
                    "\t-r Receive a remote file\n" +
                    "\t-a Use ASCII transfer mode\n" +
                    "\t-b Use binary transfer mode\n";


    private final static int CLIENT_COUNT = 200;
    private final static int DURATION = 300;
    private final static String TASK_PREFIX = "TFTP-client-";

    public static void main(String[] args) {
        int argc = 0;
        String arg = "";
        boolean receiveFile = false;
        int transferMode = TFTP.ASCII_MODE;
        String hostname, localFilename = "", remoteFilename = "";
        // Parse options
        for (argc = 0; argc < args.length; argc++) {
            arg = args[argc];
            if (arg.startsWith("-")) {
                if (arg.equals("-r")) {
                    receiveFile = true;
                } else if (arg.equals("-s")) {
                    receiveFile = false;
                } else if (arg.equals("-a")) {
                    transferMode = TFTP.ASCII_MODE;
                } else if (arg.equals("-b")) {
                    transferMode = TFTP.BINARY_MODE;
                } else {
                    System.err.println("Error: unrecognized option.");
                    System.err.print(USAGE);
                    System.exit(1);
                }
            } else {
                break;
            }
        }

        // Make sure there are enough arguments
        if (args.length - argc < 2 || args.length - argc > 3) {
            System.err.println("Error: invalid number of arguments.");
            System.err.print(USAGE);
            System.exit(1);
        }

        // Get host and file arguments
        hostname = args[argc];
        if (args.length - argc == 2) {
            localFilename = remoteFilename = args[argc + 1];
        }
        if (args.length - argc == 3) {
            localFilename = args[argc + 1];
            remoteFilename = args[argc + 2];
        }


        TaskSchedule schedule = new TaskSchedule(DURATION, CLIENT_COUNT);
        for (int i = 0; i < CLIENT_COUNT; i++) {
            String taskId = TASK_PREFIX + i;
            Random random = new Random();
            //取1-500之间的正整数
            int temp = random.nextInt(SOURCE_FILE_COUNT);
            localFilename = SOURCE_FILE_DIR + PREFIX + temp;
            //remoteFilename = taskId + PREFIX + System.currentTimeMillis();
            remoteFilename = PREFIX + temp;

            TFTPClientTask task = new TFTPClientTask.TFTPClientTaskBuilder(taskId)
                    .host(hostname)
                    .isReceive(receiveFile)
                    .localFilename(localFilename)
                    .remoteFilename(remoteFilename)
                    .mode(transferMode)
                    .barrier(schedule.cyclicBarrier)
                    .builder();
            schedule.schedule(task);
        }
    }

    private final static String SOURCE_FILE_DIR = "D:\\work\\documents\\tftp-test-file\\";
    private final static String PREFIX = "CiscoConfig-";
    //upload file count + 1
    private final static int SOURCE_FILE_COUNT = 501;
}

 

package com.wolf.tftp.test;

import java.util.concurrent.CyclicBarrier;

/**
 * Created with IntelliJ IDEA.
 * User: jiyanbin
 * Date: 13-8-7
 * Time: 上午10:39
 * To change this template use File | Settings | File Templates.
 */
public abstract class AbstractTask implements Runnable {
    String taskId;
    long successTimes;
    long failTimes;
    static long totalTimes;
    static CyclicBarrier cyclicBarrier;

    public String getTaskId() {
        return taskId;
    }

    public void setTaskId(String taskId) {
        this.taskId = taskId;
    }

    public long getSuccessTimes() {
        return successTimes;
    }

    public void setSuccessTimes(long successTimes) {
        this.successTimes = successTimes;
    }

    public long getFailTimes() {
        return failTimes;
    }

    public void setFailTimes(long failTimes) {
        this.failTimes = failTimes;
    }

    public long getTotalTimes() {
        return totalTimes;
    }

    public void setTotalTimes(long totalTimes) {
        this.totalTimes = totalTimes;
    }

    public CyclicBarrier getCyclicBarrier() {
        return cyclicBarrier;
    }

    public void setCyclicBarrier(CyclicBarrier cyclicBarrier) {
        this.cyclicBarrier = cyclicBarrier;
    }
}

 

package com.wolf.tftp.test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created with IntelliJ IDEA.
 * User: jiyanbin
 * Date: 13-8-7
 * Time: 上午9:18
 * To change this template use File | Settings | File Templates.
 */
public class TaskSchedule<T extends AbstractTask> {
    CyclicBarrier cyclicBarrier;
    long begin = System.currentTimeMillis();
    List<T> tasks = new ArrayList<T>();
    ExecutorService executor = Executors.newCachedThreadPool();

    public TaskSchedule(final long duration, int taskCount) {
        System.out.println("TFTP client task is running...");
        System.out.println("Total task : " + taskCount);
        System.out.println("Time of duration :" + duration + "s");
        cyclicBarrier = new CyclicBarrier(taskCount, new Runnable() {
            @Override
            public void run() {
                long times = 0L;
                long successTimes = 0L;
                while (System.currentTimeMillis() - begin > duration * 1000) {
                    for (AbstractTask task : tasks) {
                        times = task.getTotalTimes();
                        successTimes += task.getSuccessTimes();
                        System.out.println("==============================================================");
                        //System.out.println(task.getTaskId() + " execute total times is: " + task.getTotalTimes());
                        System.out.println(task.getTaskId() + " execute success times is: " + task.getSuccessTimes());
                        System.out.println(task.getTaskId() + " execute fail times is: " + task.getFailTimes());
                        //System.out.println(task.getTaskId() + " execute rate is: " + task.getTotalTimes() / duration + "times/s");
                        System.out.println("==============================================================");
                    }
                    System.out.println("All of " + tasks.size() + " tasks execute TFTP transfer file task total count is : " + times);
                    System.out.println("Execute success rate is: " + successTimes / duration + " times/s");
                    executor.shutdown();
                    return;
                }
                System.out.println("Task is execute...");
            }
        });
    }

    public void schedule(T task) {
        tasks.add(task);
        executor.execute(task);
    }
}

package com.wolf.tftp.test;

import com.wolf.tftp.client.TFTPClientWorker;
import com.wolf.tftp.common.Constants;
import org.apache.commons.net.tftp.TFTP;

import java.net.SocketException;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * Created with IntelliJ IDEA.
 * User: jiyanbin
 * Date: 13-8-6
 * Time: 下午1:27
 * To change this template use File | Settings | File Templates.
 */
public class TFTPClientTask extends AbstractTask {
    final TFTPClientWorker worker;

    /**
     * TFTPClientTask Builder
     */
    public static class TFTPClientTaskBuilder {
        private final String taskId;
        private String host = "localhost";
        private int port = Constants.DEFAULT_TFTP_PORT;
        private boolean isReceive;
        private int mode = TFTP.BINARY_MODE;// or TFTP.ASCII_MODE
        private String localFilename;
        private String remoteFilename;
        private CyclicBarrier barrier;

        public TFTPClientTaskBuilder(String taskId) {
            this.taskId = taskId;
        }

        public TFTPClientTaskBuilder host(String host) {
            this.host = host;
            return this;
        }

        public TFTPClientTaskBuilder port(int port) {
            this.port = port;
            return this;
        }

        public TFTPClientTaskBuilder isReceive(boolean isReceiver) {
            this.isReceive = isReceiver;
            return this;
        }

        public TFTPClientTaskBuilder mode(int mode) {
            this.mode = mode;
            return this;
        }

        public TFTPClientTaskBuilder localFilename(String localFilename) {
            this.localFilename = localFilename;
            return this;
        }

        public TFTPClientTaskBuilder remoteFilename(String remoteFilename) {
            this.remoteFilename = remoteFilename;
            return this;
        }

        public TFTPClientTaskBuilder barrier(CyclicBarrier barrier) {
            this.barrier = barrier;
            return this;
        }

        public TFTPClientTask builder() {
            return new TFTPClientTask(this);
        }
    }

    private TFTPClientTask(TFTPClientTaskBuilder builder) {
        TFTPClientWorker.TFTPClientWorkerBuilder jobBuilder = new TFTPClientWorker.TFTPClientWorkerBuilder();
        jobBuilder.host(builder.host)
                .port(builder.port)
                .isReceive(builder.isReceive)
                .mode(builder.mode)
                .localFilename(builder.localFilename)
                .remoteFilename(builder.remoteFilename);
        worker = jobBuilder.builder();
        taskId = builder.taskId;
        cyclicBarrier = builder.barrier;
    }

    public TFTPClientWorker getWorker() {
        return worker;
    }

    public void run() {
        try {
            while (!Thread.interrupted()) {
                //System.out.println("Task :" + taskId + "is executing...");
                totalTimes++;
                String filePath = worker.doJob();
                //System.out.println("Remote file path is : " + filePath);
                successTimes++;
                cyclicBarrier.await();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            failTimes++;
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
            failTimes++;
        } catch (SocketException e) {
            System.err.println("Error: could not open local UDP socket.");
            System.err.println(e.getMessage());
            failTimes++;
        }
    }
   
}

 

 

分享到:
评论
1 楼 di1984HIT 2016-02-10  
学习了~ 

相关推荐

    Apache Common-net Ftp客户端实例

    在本文中,我们将深入探讨如何使用Apache Commons Net库中的FTP客户端类来实现FTP文件传输操作。首先,我们需要理解FTP的基本概念。 FTP是一种应用层协议,用于在互联网上进行文件传输。它允许用户从远程服务器上传...

    apache-atlas-2.1.0-server.tar.gz 不含hbase和solr

    在Apache Atlas 2.1.0版本中,服务器组件的压缩包`apache-atlas-2.1.0-server.tar.gz`提供了一个核心的服务框架,用于构建和维护数据治理解决方案,但值得注意的是,这个版本不含HBase和Solr这两个关键组件。...

    apache-atlas-2.1.0-server.tar.gz

    在"apache-atlas-2.1.0-server.tar.gz"压缩包中,包含了Apache Atlas 2.1.0服务器的所有组件和配置文件。解压后,通常会包含以下部分: 1. **bin** 目录:存放启动和停止Apache Atlas服务器的脚本。 2. **conf** ...

    apache-atlas-2.0.0-server.tar.gz

    这个压缩包 "apache-atlas-2.0.0-server.tar.gz" 包含了 Atlas 的 2.0.0 版本服务器端的所有组件,可以用于在生产环境中部署。用户如果需要内嵌HBase和Solr的功能,可以通过联系提供者来获取。 Apache Atlas 在数据...

    apache-common-compress.rar

    用于压缩/解压缩的java开发工具包,基本上主流格式全包含,其中apache-common-compress内有5个jar包,两个是test的不用管,剩余三个,一个是源码包,一个是开发包另一个是javadoc。解压除RAR外的所有格式。 apache-...

    apache-atlas-2.2.0-server.tar.gz

    这个压缩包“apache-atlas-2.2.0-server.tar.gz”包含了Apache Atlas服务器的所有组件,用于搭建一个完整的内嵌式安装环境。 Apache Atlas的核心功能包括: 1. **元数据管理**:Apache Atlas提供了强大的元数据...

    WLSPlugin12.2.1.4.0-Apache2.2-Apache2.4-Linux_x86_64-12.2.1.4.0.zip

    这样可以利用Apache的高并发处理能力,同时利用WebLogic Server的高级Java EE功能。 **Apache HTTP Server 2.2和2.4**: Apache HTTP Server是世界上最流行的开源Web服务器软件,其2.2和2.4版本代表了两个主要的...

    apacheds-kerberos-codec-2.0.0-M15-API文档-中文版.zip

    标签:server、codec、apache、apacheds、kerberos、directory、jar包、java、API文档、中文版; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,文档中的代码和...

    Tftp Server

    基于Apache Common-net 的 TFTP Server, 通过了测试,支持大文件,java版的TFTP Server 不好找,网上存有一个Tftp4java 0.8 版的,存在bug,只支持32M以下的文件,主要原因是开发者对FTTP传输的处理规则不清楚,所以...

    apache commons-net-3.5.jar

    使用apache commons-net包实现文件ftp上传

    apache-jmeter-5.6.3

    apache-jmeter-5.6.3.zip apache-jmeter-5.6.3.tgz apache-jmeter-5.6.3_src.zip apache-jmeter-5.6.3_src.tgz

    apache-tomcat-10.0.8.zip

    Apache Tomcat 软件是Jakarta Servlet、 Jakarta Server Pages、 Jakarta Expression Language、 Jakarta WebSocket、 Jakarta Annotations和 Jakarta Authentication 规范的开源实现 。 压缩包内容: apache-...

    hadoop-yarn-server-common-2.6.5-API文档-中文版.zip

    Maven坐标:org.apache.hadoop:hadoop-yarn-server-common:2.6.5; 标签:server、apache、common、hadoop、yarn、jar包、java、中文文档; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可...

    apache-atlas-1.1.0编译成品

    apache-atlas-1.1.0-server.tar.gz apache-atlas-1.1.0-falcon-hook.tar.gz apache-atlas-1.1.0-sources.tar.gz apache-atlas-1.1.0-hbase-hook.tar.gz apache-atlas-1.1.0-sqoop-hook.tar.gz apache-atlas-1.1.0-...

    flink-table-common-1.12.7-API文档-中文版.zip

    Maven坐标:org.apache.flink:flink-table-common:1.12.7; 标签:apache、flink、table、common、中文文档、jar包、java; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 ...

    rh-java-common-apache-commons-net-3.2-8.11.el7.noarch.rpm

    官方离线安装包,测试可用。使用rpm -ivh [rpm完整包名] 进行安装

    hadoop-yarn-server-common-2.5.1-API文档-中文版.zip

    Maven坐标:org.apache.hadoop:hadoop-yarn-server-common:2.5.1; 标签:apache、hadoop、yarn、server、common、中文文档、jar包、java; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可...

    parquet-common-1.10.0-API文档-中文版.zip

    Maven坐标:org.apache.parquet:parquet-common:1.10.0; 标签:apache、common、parquet、jar包、java、API文档、中文版; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 ...

Global site tag (gtag.js) - Google Analytics