一、背景
公司的一個產品需要提供上傳功能,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是沒用的,只有用掛載磁盤的超級管理員賦權限才可以。