写的小程序里要集成TFTP Server的功能,搜索了下,整理如下:
=================================================== TFTP(Trivial File Transfer Protocol,简单文件传输协议)是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务。端口号为69。
Ftp基于tcp协议,会先建立连接在传输,有完善的容错机制。 tftp基于udp协议,数据是直接发送的,对方能否收到完全不知,是不可靠的传送。
此协议设计的时候是进行小文件传输的。因此它不具备通常的FTP的许多功能,它只能从文件服务器上获得或写入文件,不能列出目录,不进行认证,它传输8位数据。传输中有两种模式:netascii,这是8位的ASCII码形式,另一种是octet,这是8位源数据类型;
通常用于更新网络设备的flash image。 ======================================================= 下面是系统带的TFTP命令的用法。 E:\>tftp
Transfers files to and from a remote computer running the TFTP service.
TFTP [-i] host [GET | PUT] source [destination]
-i Specifies binary image transfer mode (also called octet). In binary image mode the file is moved literally, byte by byte. Use this mode when transferring binary files. host Specifies the local or remote host. GET Transfers the file destination on the remote host to the file source on the local host. PUT Transfers the file source on the local host to the file destination on the remote host. source Specifies the file to transfer. destination Specifies where to transfer the file.
E:\>tftp -i 127.0.0.1 put yyyyy.zip Transfer successful: 9071188 bytes in 1 second, 9071188 bytes/s
E:\>tftp -i 127.0.0.1 get xxxx.bin Transfer successful: 2884008 bytes in 1 second, 2884008 bytes/s
就一行命令,直接就执行了,倒是很省事啊。 这里,E盘就是当前路径,GET文件会放到这里,PUT文件也是在这里找。 ======================================================= Java中开源的话,找了找:
apache ftpserver http://mina.apache.org/ftpserver/ 只支持FTP功能,不支持TFTP。
Apache Commons Net http://commons.apache.org/net/ 对一大堆协议进行了封装,便于使用,它是支持TFTP的。
FTP/FTPS FTP over HTTP (experimental) NNTP SMTP(S) POP3(S) IMAP(S) Telnet TFTP Finger Whois rexec/rcmd/rlogin Time (rdate) and Daytime Echo Discard NTP/SNTP
还有一些使用范例,在它网站的download中的source的zip包中。 --------------- http://www.java2s.com/Code/Java/Network-Protocol/Ftp.htm 有一些使用Apache Commons Net的例子 -------------- 附: Apache Commons Net的范例中的TFTP Server和Client的代码,直接就能用,爽啊。
客户端TFTPExample.java
/* * 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. */
package examples.ftp;
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.SocketException; import java.net.UnknownHostException; import org.apache.commons.net.tftp.TFTP; import org.apache.commons.net.tftp.TFTPClient;
/*** * This is an example of a simple Java tftp client. * Notice how all of the code is really just argument processing and * error handling. * <p> * Usage: tftp [options] hostname localfile remotefile * hostname - The name of the remote host * localfile - The name of the local file to send or the name to use for * the received file * remotefile - The name of the remote file to receive or the name for * the remote server to use to name the local file being sent. * options: (The default is to assume -r -b) * -s Send a local file * -r Receive a remote file * -a Use ASCII transfer mode * -b Use binary transfer mode * <p> ***/ public final class TFTPExample { 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";
public final static void main(String[] args) { boolean receiveFile = true, closed; int transferMode = TFTP.BINARY_MODE, argc; String arg, hostname, localFilename, remoteFilename; TFTPClient tftp;
// 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 != 3) { System.err.println("Error: invalid number of arguments."); System.err.print(USAGE); System.exit(1); }
// Get host and file arguments hostname = args[argc]; localFilename = args[argc + 1]; remoteFilename = args[argc + 2];
// Create our TFTP instance to handle the file transfer. 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()); System.exit(1); }
// We haven't closed the local file yet. closed = false;
// If we're receiving a file, receive, otherwise send. if (receiveFile) { FileOutputStream output = null; File file;
file = new File(localFilename);
// If file exists, don't overwrite it. if (file.exists()) { System.err.println("Error: " + localFilename + " already exists."); System.exit(1); }
// Try to open local file for writing try { output = new FileOutputStream(file); } catch (IOException e) { tftp.close(); System.err.println("Error: could not open local file for writing."); System.err.println(e.getMessage()); System.exit(1); }
// Try to receive remote file via TFTP try { tftp.receiveFile(remoteFilename, transferMode, output, hostname); } catch (UnknownHostException e) { System.err.println("Error: could not resolve hostname."); System.err.println(e.getMessage()); System.exit(1); } catch (IOException e) { System.err.println( "Error: I/O exception occurred while receiving file."); System.err.println(e.getMessage()); System.exit(1); } 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()); } }
if (!closed) { System.exit(1); }
} else { // 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()); System.exit(1); }
// Try to send local file via TFTP try { tftp.sendFile(remoteFilename, transferMode, input, hostname); } catch (UnknownHostException e) { System.err.println("Error: could not resolve hostname."); System.err.println(e.getMessage()); System.exit(1); } catch (IOException e) { System.err.println( "Error: I/O exception occurred while sending file."); System.err.println(e.getMessage()); System.exit(1); } 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()); } }
if (!closed) { System.exit(1); }
}
}
}
服务端TFTPServer.java
/* * 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. */
package org.apache.commons.net.tftp;
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.SocketTimeoutException; import java.util.HashSet; import java.util.Iterator;
import org.apache.commons.net.io.FromNetASCIIOutputStream; import org.apache.commons.net.io.ToNetASCIIInputStream;
/** * 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. * * 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. * * To stop, use the shutdown method. * * To check to see if the server is still running (or if it stopped because of an error), call the * isRunning() method. * * By default, events are not logged to stdout/stderr. This can be changed with the * setLog and setLogError methods. * * <p> * Example usage is below: * * <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); * } * * TFTPServer ts = new TFTPServer(new File(args[0]), new File(args[0]), GET_AND_PUT); * 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); * } * * </code> * * * @author <A HREF="mailto:daniel.armbrust.list@gmail.com">Dan Armbrust</A> * @since 2.0 */
public class TFTPServer implements Runnable { private static final int DEFAULT_TFTP_PORT = 69; public static enum ServerMode { GET_ONLY, PUT_ONLY, GET_AND_PUT; }
private HashSet<TFTPTransfer> transfers_ = new HashSet<TFTPTransfer>(); private volatile boolean shutdownServer = 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;
/** * Start a TFTP Server on the default port (69). Gets and Puts occur in the specified * directories. * * The server will start in another thread, allowing this constructor to return immediately. * * 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. * * 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, DEFAULT_TFTP_PORT, mode, null, null); }
/** * Start a TFTP Server on the specified port. Gets and Puts occur in the specified directory. * * The server will start in another thread, allowing this constructor to return immediately. * * 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. * * 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); 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. */ private void launch(File serverReadDirectory, File serverWriteDirectory) throws IOException { 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"); }
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);
serverTftp_.open(port_);
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 (shutdownServer && serverException != null) { throw serverException; } return !shutdownServer; }
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. 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;
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 } }
/* * 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) { 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 (temp.exists()) { transferTftp_.bufferedSend(new TFTPErrorPacket(twrp.getAddress(), twrp .getPort(), TFTPErrorPacket.FILE_EXISTS, "File already exists")); return; } bos = new BufferedOutputStream(new FileOutputStream(temp));
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; } }
跑了跑,发现server端路径不能设置为某个盘的根目录, 否则上传文件时,会说Unexpected error creating requested directory 就是代码里这段: File parent = file.getParentFile(); if (parent == null) { throw new IOException("Unexpected error creating requested directory"); } 它取了下parent,根目录的时候,parent是null,所以报错了。 所以,别设置为根目录,随便下层目录就没问题了。
|