一、背景
公司的一个产品需要提供上传功能,http不能满足上传速度需求,于是改用FTP上传。
二、安装与配置
1.安装命令:
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是没用的,只有用挂载磁盘的超级管理员赋权限才可以。