一、背景
公司的一个产品需要提供上传功能,http不能满足上传速度需求,于是改用FTP上传。
二、安装与配置
1.安装命令:
yum install vsftpd,安装完成后service vsftpd restart启动vsftp。
2.配置:
1).vsftp配置
/etc/vsftpd/vsftpd.conf ,vsftp的主要配置文件,配置文件的全部我就不贴出来了,这里有详细的说明,我只说几个需要修改的参数:
anonymous_enable=NO,是否允许匿名登录,因为我们有账号权限控制,所以不能允许匿名登录
chroot_local_user=YES
chroot_list_enable=YES
chroot_list_file=/etc/vsftpd/chroot_list 这三个设置后不在chroot_list中的不给其浏览上层目录的权限。
userlist_deny=NO
userlist_file=/etc/vsftpd/user_list 上面两个参数化设置后,效果是只允许user_list中的用户登录
我们知道FTP默认监听的通信端口为21,数据端口为20,但是,网上基本每时每刻都有人在扫这些常用端口,我就有一次在Window上搭建FTP,当时是测试一个功能,没有对配置文件做过多的更改,刚开了一个小时就被软件扫到了,在我网站根目录放了一个jsp文件,我自己调用了下,吓了一身冷汗,服务器目录信息基本都显示出来了,为了防止被这些“黑客”的软件扫到,还是自己设置一个端口吧。
listen_port=8021 通信端口
ftp_data_port=8020 数据端口
pasv_enable=YES 被动模式开启
pasv_addr_resolve=YES 被动模式是否用设置好的的地址返回给客户端,如果是NO,则从链接的套接字中自己获取地址,如果为YES,则设置为下面这个地址
pasv_address=222.185.xxx.xxx 被动模式下返回的地址,安全需要,隐掉后面两个地址段
pasv_min_port=10001 pasv模式下数据端口的下界
pasv_max_port=10010 pasv模式下数据端口的上界
local_max_rate=200000 用户传输速度限制,单位为bytes/second,0表示不限制
service vsftpd restart ,重启生效
2).防火墙配置
在/etc/sysconfig/iptables中添加vsftp用到的端口
-A INPUT -m state --state NEW -m tcp -p tcp --dport 8021 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 8020 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 10000:10010 -j ACCEPT
service iptables restart ,重启生效
3).添加VSFTP用户,且禁止用户用SSH登陆
#adduser -d /home/ftp/bruce -g ftp -s /sbin/nologin bruce 新建vsftp用户,/home/ftp/bruce为此用户主目录(可自己随意设置),-g ftp此用户为ftp组员,/sbin/nologin 禁止此用户登录,bruce->用户名(可自己随意设置)
#passwd bruce 为bruce设置密码
最后用chmod命令给用户主目录赋予权限
chmod 755 /home/ftp/bruce
到此为止,我们的vsftp服务器的搭建完成了一半,下面是硬件的配置和客户端的编写。
三、端口映射和客户端编写
1.端口映射
这个问题至关重要,也是整个服务器搭建过程中,困扰我最多的地方。在第二部配置完成和客户端编写完成后,我尝试着连接并查询目录中文件列表,客户端总是返回Connectiontimeout,从vsftp配置到程序检查了多遍,都是只能连接不能获取数据,后来我用抓包工具抓包,发现了问题所在,下面是程序错误的时候抓到的包:
1.[2013/4/27 星期六 16:04:13:129]
220 (vsFTPd 2.2.2)
2.[2013/4/27 星期六 16:04:13:130]
USER bruce
3.[2013/4/27 星期六 16:04:13:133]
331 Please specify the password.
4.[2013/4/27 星期六 16:04:13:133]
PASS bruce,2013
5.[2013/4/27 星期六 16:04:28:208]
230 Login successful.
6.[2013/4/27 星期六 16:04:28:209]
TYPE I
7.[2013/4/27 星期六 16:04:28:211]
200 Switching to Binary mode.
8.[2013/4/27 星期六 16:04:28:247]
PASV
9.[2013/4/27 星期六 16:04:28:249]
227 Entering Passive Mode (192.168.1.122,39,16).
500 OOPS: vsf_sysutil_recv_peek: no data
500 OOPS: child died
跑到第9步的时候卡住一会,接着报错java.net.ConnectException: Connection timed out: connect,出现上面最后两行500错误。第一反应就是返回的IP错了,因为我映射了外网端口,这里返回客户端的却是一个内网地址,于是加上上面提到过的参数
pasv_addr_resolve=YES 被动模式是否用设置好的的地址返回给客户端,如果是NO,则从链接的套接字中自己获取地址,如果为YES,则设置为下面这个地址
pasv_address=222.185.xxx.xxx 被动模式下返回的地址,安全需要,隐掉后面两个地址段
再跑的时候返回的就对了,
227 Entering Passive Mode (222,185,xxx,xxx,39,24).
注解:括号里39,24表示连接的端口号,算法为:39*256+24=10008,端口落在vsftp.conf配置文件的上界和下界之间,说明端口设置生效了。可是,问题依然存在,看来不是IP地址这么简单的事情。那就只剩下端口号了,回去重新学习了FTP的两种工作方式:
主动FTP: 命令连接:客户端 >1024端口 -> 服务器 21端口(我们这里是8081,外网映射为15321) 数据连接:客户端 >1024端口 <- 服务器 20端口(我们这里是8020,外网映射为15320) 被动FTP(防止服务器主动去连的端口在防火墙后面,连接不上): 命令连接:客户端大于1024的端口 -> 服务器 21端口 (我们这里是8081,外网映射为15321)
数据连接:客户端大于1024的端口 -> 服务器上大于1024的端口(我们限定为10001-10010)
实际情况是,当时在公网对内网端口映射的时候,只映射了8021和8020两个端口,并没有映射vsftp.conf配置文件中的10001-10010,于是跟IT管理部门申请,映射了这10个端口,10001->10010这样严格映射,再执行客户端程序:
[2013/4/27 星期六 16:04:17:114]
220 (vsFTPd 2.2.2)
[2013/4/27 星期六 16:04:17:116]
USER bruce
[2013/4/27 星期六 16:04:17:118]
331 Please specify the password.
[2013/4/27 星期六 16:04:17:119]
PASS bruce,2013
[2013/4/27 星期六 16:04:32:203]
230 Login successful.
[2013/4/27 星期六 16:04:32:203]
TYPE I
[2013/4/27 星期六 16:04:32:206]
200 Switching to Binary mode.
[2013/4/27 星期六 16:04:32:236]
PASV
[2013/4/27 星期六 16:04:32:238]
227 Entering Passive Mode (222,185,xxx,xxx,39,24).
[2013/4/27 星期六 16:04:32:241]
LIST /
[2013/4/27 星期六 16:04:32:243]
150 Here comes the directory listing.
226 Directory send OK.
搞定!!
2.客户端程序
import java.io.IOException; import java.net.SocketException; import org.apache.commons.net.ftp.FTP; import org.apache.commons.net.ftp.FTPClient; import org.apache.commons.net.ftp.FTPClientConfig; import org.apache.commons.net.ftp.FTPFile; import org.apache.commons.net.ftp.FTPReply; public class FTPTools { private FTPClient ftp=new FTPClient(); /** * 连接ftp的方法 * @param hostname 公网IP * @param port 公网端口 * @param username ftp用户名 * @param password ftp密码 * @return */ public boolean connect(String hostname,int port,String username,String password){ try { FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_NT); conf.setServerLanguageCode("zh"); ftp.configure(conf); ftp.setControlEncoding("GBK");//避免中文文件名乱码 ftp.setConnectTimeout(150000); ftp.enterLocalPassiveMode(); ftp.connect(hostname, port); int code=ftp.getReplyCode(); if(FTPReply.isPositiveCompletion(code)){ if(ftp.login(username, password)){ ftp.enterLocalPassiveMode();//切换成pasv被动模式 ftp.setFileType(FTP.BINARY_FILE_TYPE);//必须要,设置为2进制传输 ftp.setDataTimeout(60000); ftp.setSoTimeout(120000); FTPFile[] files = ftp.listFiles("/"); System.out.println(files.length); for(FTPFile file:files){ System.out.println(file.getName()); } return true; } } } catch (SocketException e) { e.printStackTrace(); try { ftp.disconnect(); } catch (IOException e1) { e1.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); try { ftp.disconnect(); } catch (IOException e1) { e1.printStackTrace(); } } try { ftp.disconnect(); } catch (IOException e) { e.printStackTrace(); } return false; } public static void main(String[] args) { new FTPTools().connect("222.185.xxx.xxx", 15321, "bruce", "bruce,2013"); } }
好了,vsftp服务器的搭建就写到这里,客户端代码只放出了简单的连接测试代码,后面随着项目的推进,会把上传、下载、断点续传以及遇到的其它问题解决方法分享给大家,希望能够帮助到刚接触vsftp或者在这上面遇到问题的同学们。
PS:后来发生个小插曲,我把目录指向到服务器挂载的磁盘阵列上面,能下载,但是死活传不上去,权限也赋了。后来知道了,在挂载机器上面给ftp用户chmod 777是没用的,只有用挂载磁盘的超级管理员赋权限才可以。