查看源代码,首先从FtpServerFactory开始,我们创建FTPServer第一步也是从它开始创建server,进行启动。
public class FtpServerFactory {
private DefaultFtpServerContext serverContext;
public FtpServerFactory() {
serverContext = new DefaultFtpServerContext();
}
public FtpServer createServer() {
return new DefaultFtpServer(serverContext);
}
public Map<String, Listener> getListeners() {
return serverContext.getListeners();
}
public Listener getListener(final String name) {
return serverContext.getListener(name);
}
public void addListener(final String name, final Listener listener) {
serverContext.addListener(name, listener);
}
public void setListeners(final Map<String, Listener> listeners) {
serverContext.setListeners(listeners);
}
public Map<String, Ftplet> getFtplets() {
return serverContext.getFtpletContainer().getFtplets();
}
public void setFtplets(final Map<String, Ftplet> ftplets) {
serverContext.setFtpletContainer(new DefaultFtpletContainer(ftplets));
}
public UserManager getUserManager() {
return serverContext.getUserManager();
}
public void setUserManager(final UserManager userManager) {
serverContext.setUserManager(userManager);
}
public FileSystemFactory getFileSystem() {
return serverContext.getFileSystemManager();
}
public void setFileSystem(final FileSystemFactory fileSystem) {
serverContext.setFileSystemManager(fileSystem);
}
public CommandFactory getCommandFactory() {
return serverContext.getCommandFactory();
}
public void setCommandFactory(final CommandFactory commandFactory) {
serverContext.setCommandFactory(commandFactory);
}
public MessageResource getMessageResource() {
return serverContext.getMessageResource();
}
public void setMessageResource(final MessageResource messageResource) {
serverContext.setMessageResource(messageResource);
}
public ConnectionConfig getConnectionConfig() {
return serverContext.getConnectionConfig();
}
public void setConnectionConfig(final ConnectionConfig connectionConfig) {
serverContext.setConnectionConfig(connectionConfig);
}
}
它的属性只有一个DefaultFtpServerContext,其他的都是Get、Set方法,从DefaultFtpServerContext获取,主要有Listener(监听器,对外监听请求)、Ftplet(相当于JAVA的servlet,处理请求)、UserManager(用户管理组件,数据库用户就是这里配置)、FileSystemFactory(文件系统,主目录,切换目录,上传下载文件都是这个实现的)、CommandFactory(FTP命令的实现)、MessageResource(消息内容)、ConnectionConfig(连接配置)。默认这些参数都是DefaultFtpServerContext里配置的。我们再看看DefaultFtpServerContext
public class DefaultFtpServerContext implements FtpServerContext {
private final Logger LOG = LoggerFactory
.getLogger(DefaultFtpServerContext.class);
private MessageResource messageResource = new MessageResourceFactory().createMessageResource();
private UserManager userManager = new PropertiesUserManagerFactory().createUserManager();
private FileSystemFactory fileSystemManager = new NativeFileSystemFactory();
private FtpletContainer ftpletContainer = new DefaultFtpletContainer();
private FtpStatistics statistics = new DefaultFtpStatistics();
private CommandFactory commandFactory = new CommandFactoryFactory().createCommandFactory();
private ConnectionConfig connectionConfig = new ConnectionConfigFactory().createConnectionConfig();
private Map<String, Listener> listeners = new HashMap<String, Listener>();
private static final List<Authority> ADMIN_AUTHORITIES = new ArrayList<Authority>();
private static final List<Authority> ANON_AUTHORITIES = new ArrayList<Authority>();
/**
* The thread pool executor to be used by the server using this context
*/
private ThreadPoolExecutor threadPoolExecutor = null;
static {
ADMIN_AUTHORITIES.add(new WritePermission());
ANON_AUTHORITIES.add(new ConcurrentLoginPermission(20, 2));
ANON_AUTHORITIES.add(new TransferRatePermission(4800, 4800));
}
public DefaultFtpServerContext() {
// create the default listener
listeners.put("default", new ListenerFactory().createListener());
}
public void createDefaultUsers() throws Exception {
UserManager userManager = getUserManager();
// create admin user
String adminName = userManager.getAdminName();
if (!userManager.doesExist(adminName)) {
LOG.info("Creating user : " + adminName);
BaseUser adminUser = new BaseUser();
adminUser.setName(adminName);
adminUser.setPassword(adminName);
adminUser.setEnabled(true);
adminUser.setAuthorities(ADMIN_AUTHORITIES);
adminUser.setHomeDirectory("./res/home");
adminUser.setMaxIdleTime(0);
userManager.save(adminUser);
}
// create anonymous user
if (!userManager.doesExist("anonymous")) {
LOG.info("Creating user : anonymous");
BaseUser anonUser = new BaseUser();
anonUser.setName("anonymous");
anonUser.setPassword("");
anonUser.setAuthorities(ANON_AUTHORITIES);
anonUser.setEnabled(true);
anonUser.setHomeDirectory("./res/home");
anonUser.setMaxIdleTime(300);
userManager.save(anonUser);
}
}
public UserManager getUserManager() {
return userManager;
}
public FileSystemFactory getFileSystemManager() {
return fileSystemManager;
}
public MessageResource getMessageResource() {
return messageResource;
}
public FtpStatistics getFtpStatistics() {
return statistics;
}
public void setFtpStatistics(FtpStatistics statistics) {
this.statistics = statistics;
}
public FtpletContainer getFtpletContainer() {
return ftpletContainer;
}
public CommandFactory getCommandFactory() {
return commandFactory;
}
public Ftplet getFtplet(String name) {
return ftpletContainer.getFtplet(name);
}
public void dispose() {
listeners.clear();
ftpletContainer.getFtplets().clear();
if (threadPoolExecutor != null) {
LOG.debug("Shutting down the thread pool executor");
threadPoolExecutor.shutdown();
try {
threadPoolExecutor.awaitTermination(5000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
} finally {
// TODO: how to handle?
}
}
}
public Listener getListener(String name) {
return listeners.get(name);
}
public void setListener(String name, Listener listener) {
listeners.put(name, listener);
}
public Map<String, Listener> getListeners() {
return listeners;
}
public void setListeners(Map<String, Listener> listeners) {
this.listeners = listeners;
}
public void addListener(String name, Listener listener) {
listeners.put(name, listener);
}
public Listener removeListener(String name) {
return listeners.remove(name);
}
public void setCommandFactory(CommandFactory commandFactory) {
this.commandFactory = commandFactory;
}
public void setFileSystemManager(FileSystemFactory fileSystemManager) {
this.fileSystemManager = fileSystemManager;
}
public void setFtpletContainer(FtpletContainer ftpletContainer) {
this.ftpletContainer = ftpletContainer;
}
public void setMessageResource(MessageResource messageResource) {
this.messageResource = messageResource;
}
public void setUserManager(UserManager userManager) {
this.userManager = userManager;
}
public ConnectionConfig getConnectionConfig() {
return connectionConfig;
}
public void setConnectionConfig(ConnectionConfig connectionConfig) {
this.connectionConfig = connectionConfig;
}
public synchronized ThreadPoolExecutor getThreadPoolExecutor() {
if(threadPoolExecutor == null) {
int maxThreads = connectionConfig.getMaxThreads();
if(maxThreads < 1) {
int maxLogins = connectionConfig.getMaxLogins();
if(maxLogins > 0) {
maxThreads = maxLogins;
}
else {
maxThreads = 16;
}
}
LOG.debug("Intializing shared thread pool executor with max threads of {}", maxThreads);
threadPoolExecutor = new OrderedThreadPoolExecutor(maxThreads);
}
return threadPoolExecutor;
}
}
下面是MessageResource的初始化数据,是服务响应信息。
实现我们自定义的需求就是修改DefaultFtpServerContext中的属性即可,不必重写整个DefaultFtpServerContext类,只替换到我们用到的,节省开发时间,提高系统稳定性。用数据库管理用户就在FtpServerFactory设置从DbUserManagerFactory获取的用户管理系统。用自己的FTPlet就在FtpServerFactory添加我们自己已实现Ftplet接口的类。用自己的监听器就向FtpServerFactory中添加我们自己的Listener,默认监听器在DefaultFtpServerContext是这样配置的,如下代码
private Map<String, Listener> listeners = new HashMap<String, Listener>();
public DefaultFtpServerContext() {
// create the default listener
listeners.put("default", new ListenerFactory().createListener());
}
接下来讲最重要的命令系统和文件系统。上代码
private CommandFactory commandFactory = new CommandFactoryFactory().createCommandFactory();
public CommandFactory createCommandFactory() {
Map<String, Command> mergedCommands = new HashMap<String, Command>();
if(useDefaultCommands) {
mergedCommands.putAll(DEFAULT_COMMAND_MAP);
}
mergedCommands.putAll(commandMap);
return new DefaultCommandFactory(mergedCommands);
}
我们可以看到CommandFactory里的默认命令都是以Map的数据结构维护的,如下代码:
private static final HashMap<String, Command> DEFAULT_COMMAND_MAP = new HashMap<String, Command>();
static {
// first populate the default command list
DEFAULT_COMMAND_MAP.put("ABOR", new ABOR());
DEFAULT_COMMAND_MAP.put("ACCT", new ACCT());
DEFAULT_COMMAND_MAP.put("APPE", new APPE());
DEFAULT_COMMAND_MAP.put("AUTH", new AUTH());
DEFAULT_COMMAND_MAP.put("CDUP", new CDUP());
DEFAULT_COMMAND_MAP.put("CWD", new CWD());
DEFAULT_COMMAND_MAP.put("DELE", new DELE());
DEFAULT_COMMAND_MAP.put("EPRT", new EPRT());
DEFAULT_COMMAND_MAP.put("EPSV", new EPSV());
DEFAULT_COMMAND_MAP.put("FEAT", new FEAT());
DEFAULT_COMMAND_MAP.put("HELP", new HELP());
DEFAULT_COMMAND_MAP.put("LANG", new LANG());
DEFAULT_COMMAND_MAP.put("LIST", new LIST());
DEFAULT_COMMAND_MAP.put("MD5", new MD5());
DEFAULT_COMMAND_MAP.put("MFMT", new MFMT());
DEFAULT_COMMAND_MAP.put("MMD5", new MD5());
DEFAULT_COMMAND_MAP.put("MDTM", new MDTM());
DEFAULT_COMMAND_MAP.put("MLST", new MLST());
DEFAULT_COMMAND_MAP.put("MKD", new MKD());
DEFAULT_COMMAND_MAP.put("MLSD", new MLSD());
DEFAULT_COMMAND_MAP.put("MODE", new MODE());
DEFAULT_COMMAND_MAP.put("NLST", new NLST());
DEFAULT_COMMAND_MAP.put("NOOP", new NOOP());
DEFAULT_COMMAND_MAP.put("OPTS", new OPTS());
DEFAULT_COMMAND_MAP.put("PASS", new PASS());
DEFAULT_COMMAND_MAP.put("PASV", new PASV());
DEFAULT_COMMAND_MAP.put("PBSZ", new PBSZ());
DEFAULT_COMMAND_MAP.put("PORT", new PORT());
DEFAULT_COMMAND_MAP.put("PROT", new PROT());
DEFAULT_COMMAND_MAP.put("PWD", new PWD());
DEFAULT_COMMAND_MAP.put("QUIT", new QUIT());
DEFAULT_COMMAND_MAP.put("REIN", new REIN());
DEFAULT_COMMAND_MAP.put("REST", new REST());
DEFAULT_COMMAND_MAP.put("RETR", new RETR());
DEFAULT_COMMAND_MAP.put("RMD", new RMD());
DEFAULT_COMMAND_MAP.put("RNFR", new RNFR());
DEFAULT_COMMAND_MAP.put("RNTO", new RNTO());
DEFAULT_COMMAND_MAP.put("SITE", new SITE());
DEFAULT_COMMAND_MAP.put("SIZE", new SIZE());
DEFAULT_COMMAND_MAP.put("SITE_DESCUSER", new SITE_DESCUSER());
DEFAULT_COMMAND_MAP.put("SITE_HELP", new SITE_HELP());
DEFAULT_COMMAND_MAP.put("SITE_STAT", new SITE_STAT());
DEFAULT_COMMAND_MAP.put("SITE_WHO", new SITE_WHO());
DEFAULT_COMMAND_MAP.put("SITE_ZONE", new SITE_ZONE());
DEFAULT_COMMAND_MAP.put("STAT", new STAT());
DEFAULT_COMMAND_MAP.put("STOR", new STOR());
DEFAULT_COMMAND_MAP.put("STOU", new STOU());
DEFAULT_COMMAND_MAP.put("STRU", new STRU());
DEFAULT_COMMAND_MAP.put("SYST", new SYST());
DEFAULT_COMMAND_MAP.put("TYPE", new TYPE());
DEFAULT_COMMAND_MAP.put("USER", new USER());
}
当我们要替换或增加自定义命令,利用Map即可实现,替换就put一个同名的命名key,放入自定义的命令实现类。(ps:我开发的有个自定义的文件系统需求,所以重写了PASS命名实现,在登录成功后,创建自己的文件管理系统支持服务)
在DefaultFtpServerContext中可以看到默认的文件系统是本机文件系统,下代码:
private FileSystemFactory fileSystemManager = new NativeFileSystemFactory();
我们来梳理一下该文件系统的组织,首先用户登录成功后,在FtpServer中找到配置的文件管理系统工厂,默认为NativeFileSystemFactory,由它创建一个与用户相关联的
NativeFileSystemView。里面包含用户信息、根目录、当前目录,主要为用户提供切换目录,获取当前目录对象,获取当前目录下指定文件功能。该文件系统中实际使用的一个文件对象是NativeFtpFile,包含fileName、File、User,提供文件的所有操作。上代码:
public class NativeFileSystemFactory implements FileSystemFactory {
//创建FileSystemView对象
public FileSystemView createFileSystemView(User user) throws FtpException {
synchronized (user) {
// create home if does not exist
if (createHome) {
String homeDirStr = user.getHomeDirectory();
File homeDir = new File(homeDirStr);
if (homeDir.isFile()) {
LOG.warn("Not a directory :: " + homeDirStr);
throw new FtpException("Not a directory :: " + homeDirStr);
}
if ((!homeDir.exists()) && (!homeDir.mkdirs())) {
LOG.warn("Cannot create user home :: " + homeDirStr);
throw new FtpException("Cannot create user home :: "
+ homeDirStr);
}
}
FileSystemView fsView = new NativeFileSystemView(user,
caseInsensitive);
return fsView;
}
}
}
public class NativeFileSystemView implements FileSystemView {
//获取根目录
public FtpFile getHomeDirectory() {
return new NativeFtpFile("/", new File(rootDir), user);
}
}
//获取当前目录对象
public FtpFile getWorkingDirectory() {
FtpFile fileObj = null;
if (currDir.equals("/")) {
fileObj = new NativeFtpFile("/", new File(rootDir), user);
} else {
File file = new File(rootDir, currDir.substring(1));
fileObj = new NativeFtpFile(currDir, file, user);
}
return fileObj;
}
//获取目录下文件
public FtpFile getFile(String file) {
// get actual file object
String physicalName = getPhysicalName(rootDir,
currDir, file, caseInsensitive);
File fileObj = new File(physicalName);
// strip the root directory and return
String userFileName = physicalName.substring(rootDir.length() - 1);
return new NativeFtpFile(userFileName, fileObj, user);
}
//切换目录
public boolean changeWorkingDirectory(String dir) {
// not a directory - return false
dir = getPhysicalName(rootDir, currDir, dir,
caseInsensitive);
File dirObj = new File(dir);
if (!dirObj.isDirectory()) {
return false;
}
// strip user root and add last '/' if necessary
dir = dir.substring(rootDir.length() - 1);
if (dir.charAt(dir.length() - 1) != '/') {
dir = dir + '/';
}
currDir = dir;
return true;
}
NativeFtpFile可以当File一起看,基本实现都是File实现的。上述的三个文件系统必备的类,都各自实现了三个接口FileSystemFactory、FileSystemView、FtpFile,所以我们要创建自己的文件系统也要实现这三个接口,实现完成后,可以在FtpServerFactory设置我们自己的文件系统,如果要实现多文件系统相互切换,也可以在PASS命令里实现切换,不同登录的人,不同的端口号登录进入不同的文件系统。
到这里我们就对ApacheFTPServer有了初步的了解和应用,可以满足大部分FTP服务的需求,我们可以实现向web应用一样,接收FTP请求,可以转发,可以有自己的文件系统,用户管理系统,可以实现SSL加密连接数据传输,可以有自己的日志管理。现在我又接到一个BT需求,要实现Linux命名行连接FTPS,编写shell脚本实现自动文件传输任务。但是服务器自带的FTP又不支持,网上都是建议用户客户端,又和脚本冲突,难受!。