概述
FTP 是File Transfer Protocol(文件傳輸協(xié)議)的英文簡稱,用于Internet上文件的雙向傳輸婉弹。FTP的主要作用就是讓客戶端連接上一個(gè)遠(yuǎn)程計(jì)算機(jī)(這些計(jì)算機(jī)上運(yùn)行著FTP服務(wù)器程序)從而察看遠(yuǎn)程計(jì)算機(jī)上的文件辙谜,然后把文件從遠(yuǎn)程計(jì)算機(jī)上拷到本地計(jì)算機(jī)浸剩,或把本地的文件上傳到遠(yuǎn)程計(jì)算機(jī)。
FTP是僅基于TCP的服務(wù)工腋,不支持UDP展鸡。FTP協(xié)議使用2個(gè)連接屿衅,一個(gè)數(shù)據(jù)連接和一個(gè)控制連接(用來傳輸客戶端向FTP服務(wù)器發(fā)送的命令、傳輸FTP服務(wù)器向客戶端命令的響應(yīng))莹弊。通常來說控制連接的端口號是21涤久,數(shù)據(jù)連接的端口號是20,但由于FTP協(xié)議工作方式的不同忍弛,數(shù)據(jù)連接的端口號并不總是20响迂,這也是FTP的主動(dòng)與被動(dòng)模式的最大不同之處。
FTP協(xié)議的兩種工作模式
1. 主動(dòng)模式(Port模式)
1> 客戶端發(fā)起從一個(gè)任意的非特權(quán)端口N(N大于等于1024)連接到FTP服務(wù)器的控制端口21细疚,從而建立控制連接栓拜。
2> 客戶端開始監(jiān)聽客戶端的端口N+1,并發(fā)送FTP命令“port N+1”到FTP服務(wù)器惠昔。
3> 服務(wù)器從自己的數(shù)據(jù)端口20連接到客戶端指定的數(shù)據(jù)端口N+1幕与,從而建立數(shù)據(jù)連接。
2. 被動(dòng)模式(Pasv模式)
1> 客戶端發(fā)起從一個(gè)任意的非特權(quán)端口N(N大于等于1024)連接到FTP服務(wù)器的控制端口21镇防,從而建立控制連接啦鸣。
2> 與主動(dòng)模式不同,客戶端不會發(fā)送PORT命令要求服務(wù)器來回連它的數(shù)據(jù)端口来氧,而是發(fā)送PASV命令诫给。
3> 服務(wù)器接收到會PASV命令后會開啟一個(gè)任意的非特權(quán)端口P(大于等于1024)香拉,并發(fā)送PORT P命令給客戶端。
4> 客戶端接收到PORT P命令后會發(fā)起從非特權(quán)端口N+1連接到服務(wù)器的端口P中狂,從而建立數(shù)據(jù)連接凫碌。
注意:
1> 由于控制連接和數(shù)據(jù)連接都由客戶端發(fā)起,可以解決從FTP服務(wù)器到客戶端的數(shù)據(jù)端口的入方連接被防火墻過濾掉的問題胃榕。
FTP協(xié)議命令與響應(yīng)信息
在FTP服務(wù)的執(zhí)行過程中盛险,F(xiàn)TP客戶端與FTP服務(wù)器之間需要傳輸控制信息,這些信息用于完成某個(gè)具體的FTP操作勋又,它們可以分為兩種類型:FTP命令與FTP響應(yīng)信息苦掘。其中,F(xiàn)TP命令是FTP客戶端向FTP服務(wù)器發(fā)送的操作請求楔壤,F(xiàn)TP響應(yīng)信息是FTP服務(wù)器根據(jù)操作結(jié)果向FTP客戶端返回的響應(yīng)信息鹤啡。FTP協(xié)議詳細(xì)規(guī)定了每種協(xié)議命令的順序--首先需要順序發(fā)送USER與PASS命令,最后需要發(fā)送QUIT命令蹲嚣,其他命令的順序沒有特殊要求递瑰。
1. FTP協(xié)議命令
FTP 每個(gè)命令都有 3 到 4 個(gè)字母組成,命令后面跟參數(shù)隙畜,用空格分開抖部。每個(gè)命令都以 "\r\n"結(jié)束。FTP命令的標(biāo)準(zhǔn)格式為:
命令名 <參數(shù)>
常用的FTP命令如下表所示:
命令 | 描述 |
---|---|
USER <username> | 參數(shù)是標(biāo)記用戶的Telnet串禾蚕。用戶標(biāo)記是訪問服務(wù)器必須的您朽,此命令通常是控制連接后第一個(gè)發(fā)出的命令狂丝,有些主機(jī)還會要求口令和帳戶换淆。服務(wù)器可以在任何時(shí)間接收新的USER命令以改變訪問控制和(或)帳戶信息。這可以重新開始登錄過程几颜,所以傳輸參數(shù)不變倍试,在進(jìn)行中的文件傳輸在過去的訪問控制參數(shù)下完成。 |
PASS <password> | 參數(shù)是標(biāo)記用戶口令的Telnet串蛋哭。此命令緊跟USER命令县习,在某些站點(diǎn)它是完成訪問控制不可缺少的一步。因此口令是個(gè)重要的東西谆趾,因此不能顯示出來躁愿,服務(wù)器方?jīng)]有辦法隱藏口令,所以這一任務(wù)得由用戶FTP進(jìn)程完成沪蓬。 |
ACCT <account> | 參數(shù)是標(biāo)記用戶帳戶的Telnet串彤钟。此命令不需要與USER相關(guān),一些站點(diǎn)可能需要帳戶用于登錄跷叉,另一些可以限制帳戶的權(quán)限逸雹,在后一種情況下营搅,此命令可在任何時(shí)候發(fā)送。應(yīng)答的不同可以區(qū)別不同的情況:當(dāng)?shù)卿浶枰獛粜畔r(shí)梆砸,對PASS命令的響應(yīng)是332转质。另外,如果不需要帳戶信息帖世,對PASS的響應(yīng)是230休蟹,如果需要帳戶信息在以后需要,服務(wù)器會返回332或532狮暑,這要看它是保存此命令還是拒絕此命令了鸡挠。 |
CWD <dir path> | 此命令使用戶可以在不同的目錄或數(shù)據(jù)集下工作而不用改變它的登錄或帳戶信息。傳輸參數(shù)也不變搬男。參數(shù)一般是目錄名或與系統(tǒng)相關(guān)的文件集合拣展。 |
CDUP | 該命令要求系統(tǒng)回到上一級目錄 |
SMNT <pathname> | 此命令使用戶在不改變登錄或帳戶信息的情況下加載另一個(gè)文件系統(tǒng)數(shù)據(jù)結(jié)構(gòu)。傳輸參數(shù)也不變缔逛。參數(shù)是文件目錄或與系統(tǒng)相關(guān)的文件集合备埃。 |
REIN | 此命令終止USER,將所有I/O和帳戶信息寫入褐奴,但不許進(jìn)行中的數(shù)據(jù)傳輸完成按脚。重置所有參數(shù),控制連接打開敦冬,可以再次開始USER命令辅搬。 |
OUIT | 此命令終止USER,如果沒有數(shù)據(jù)傳輸脖旱,服務(wù)器關(guān)閉控制連接堪遂;如果有數(shù)據(jù)傳輸,在得到傳輸響應(yīng)后服務(wù)器關(guān)閉控制連接萌庆。如果用戶進(jìn)程正在向不同的USER傳輸數(shù)據(jù)溶褪,不希望對每個(gè)USER關(guān)閉然后再打開,可以使用REIN践险。對控制連接的意外關(guān)閉猿妈,可以導(dǎo)致服務(wù)器運(yùn)行中止(ABOR)和退出登錄(QUIT)。 |
PORT <address> | 參數(shù)是要使用的數(shù)據(jù)連接端口巍虫,通常情況下對此不需要命令響應(yīng)彭则。如果使用此命令時(shí),要發(fā)送32位的IP地址和16位的TCP端口號占遥。上面的信息以8位為一組俯抖,逗號間隔十進(jìn)制傳輸,如下例: PORT h1,h2,h3,h4,p1,p2 其中h1是IP地址的最高8位筷频。 |
PASV | 此命令要求服務(wù)器DTP在指定的數(shù)據(jù)端口偵聽蚌成,進(jìn)入被動(dòng)接收請求的狀態(tài)前痘,參數(shù)是主機(jī)和端口地址。 |
TYPE <data type> | 該命令定義文件類型以及打印格式 |
STRU <type> | 參數(shù)是一個(gè)Telnet字符代碼指定文件結(jié)構(gòu)担忧。下面是代碼及其意義: F - 文件(非記錄結(jié)構(gòu))芹缔,它是默認(rèn)值 ;R - 記錄結(jié)構(gòu)瓶盛; P - 頁結(jié)構(gòu) |
MODE <mode> | 參數(shù)是一個(gè)Telnet字符代碼指定傳輸模式最欠。下面是代碼及其意義: S - 流(默認(rèn)值); B - 塊惩猫; C - 壓縮 |
RETR <filename> | 此命令使服務(wù)器DTP傳送指定路徑內(nèi)的文件復(fù)本到服務(wù)器或用戶DTP芝硬。這邊服務(wù)器上文件的狀態(tài)和內(nèi)容不受影響。 |
STOR <filename> | 此命令使服務(wù)器DTP接收數(shù)據(jù)連接上傳送過來的數(shù)據(jù)轧房,并將數(shù)據(jù)保存在服務(wù)器的文件中拌阴。如果文件已存在,原文件將被覆蓋奶镶。如果文件不存在迟赃,則新建文件。 |
STOU <filename> | 此命令和STOR差不多厂镇,此命令要求在此目錄下的文件名是唯一的纤壁,對此命令的響應(yīng)必須包括產(chǎn)生的用戶名。 |
APPE <filename> | 它和STOR的功能差不多捺信,但是如果文件在指定路徑內(nèi)已存在酌媒,則把數(shù)據(jù)附加到原文件尾部,如果不存在則新建文件迄靠。 |
ALLO <bytes> | 此命令用于在一些主機(jī)上為新傳送的文件分配足夠的存儲空間秒咨。參數(shù)是十進(jìn)制的邏輯字節(jié)數(shù)。如果是記錄或頁結(jié)構(gòu)梨水,頁或記錄的最大大小也需要拭荤,這在第二個(gè)參數(shù)內(nèi)以十進(jìn)制指定茵臭。第二個(gè)參數(shù)是可選的疫诽,如果有它,它和第一個(gè)參數(shù)以Telnet字符<SP> R <SP>分隔旦委。此命令在STOR或APPE命令后奇徒,對于不需要分配存儲空間的機(jī)器,它的作用等于NOOP缨硝。 |
REST <offset> | 參數(shù)域代表服務(wù)器要重新開始的那一點(diǎn)摩钙,此命令并不傳送文件,而是略過指定點(diǎn)后的數(shù)據(jù)查辩,此命令后應(yīng)該跟其它要求文件傳輸?shù)腇TP命令胖笛。 |
RNFR <old path> | 這個(gè)命令和我們在其它操作系統(tǒng)中使用的一樣网持,只不過后面要跟"rename to"指定新的文件名。 |
RNTO <new path> | 此命令和上面的命令共同完成對文件的重命名长踊。 |
ABOR | 此命令通知服務(wù)中止以前的FTP命令和與之相關(guān)的數(shù)據(jù)傳送功舀。如果先前的操作已經(jīng)完成,則沒有動(dòng)作身弊,返回226辟汰。如果沒有完成,返回426阱佛,然后再返回226帖汞。關(guān)閉控制連接,數(shù)據(jù)連接不關(guān)閉凑术。 |
DELE <filename> | 此命令刪除指定路徑下的文件翩蘸。用戶進(jìn)程負(fù)責(zé)對刪除的提示。 |
RMD <directory> | 此命令刪除目錄淮逊。 |
MKD <directory> | 此命令在指定路徑下創(chuàng)建新目錄鹿鳖。 |
PWD | 在響應(yīng)時(shí)返回當(dāng)前工作目錄。 |
LIST <name> | 服務(wù)器傳送列表到被動(dòng)DTP壮莹,如果路徑指定一個(gè)目錄或許多文件翅帜,返回指定路徑下的文件列表。如果路徑名指定一個(gè)文件命满,服務(wù)器返回文件的當(dāng)前信息涝滴,參數(shù)為空表示用戶當(dāng)前的工作目錄或默認(rèn)目錄。數(shù)據(jù)傳輸在ASCII或EBCDIC下進(jìn)行胶台,用戶必須確認(rèn)這一點(diǎn)歼疮。因?yàn)槲募畔⒁蛳到y(tǒng)不同而不同,所以不可能被程序自動(dòng)利用诈唬,但是人類用戶卻很需要韩脏。 |
NLST <directory> | 服務(wù)器傳送目錄表名到用戶,路徑名應(yīng)指定目錄或其它系統(tǒng)指定的文件群描述子铸磅;空參數(shù)指當(dāng)前目錄赡矢。服務(wù)器返回文件名數(shù)據(jù)流,以ASCII或EBCDIC形式傳送阅仔,并以<CRLF>或<NL>分隔吹散。這里返回的信息有時(shí)可以供程序進(jìn)行進(jìn)一步處理。 |
SITE <params> | 服務(wù)器用來提供服務(wù)器系統(tǒng)信息八酒,信息因系統(tǒng)不同而不同空民,格式在HELP SITE命令應(yīng)答中給出。 |
SYST | 用于確定服務(wù)器上運(yùn)行的操作系統(tǒng)羞迷。 |
STAT <directory> | 此命令返回控制連接狀態(tài)界轩,它可以在文件傳送過程中發(fā)送画饥,服務(wù)器返回操作進(jìn)行的狀態(tài)。也可以在文件傳送之間發(fā)送浊猾,這時(shí)命令有參數(shù)荒澡,參數(shù)是路徑名,此命令的功能除了數(shù)據(jù)在控制連接上傳送以外和列表命令相似与殃。如果指定部分路徑单山,服務(wù)器以文件名或與說明相關(guān)的屬性返回;如沒有參數(shù)幅疼,服務(wù)器返回服務(wù)器FTP進(jìn)程的狀態(tài)信息米奸,包括傳輸參數(shù)的當(dāng)前值和連接狀態(tài)。 |
HELP <command> | 這條命令我們在平常系統(tǒng)中得到的幫助沒有什么區(qū)別爽篷,響應(yīng)類型是211或214悴晰。建議在使用USER命令前使用此命令。 |
NOOP | 此命令不產(chǎn)生什么實(shí)際動(dòng)作逐工,它僅使服務(wù)器返回OK铡溪。 |
2. FTP協(xié)議響應(yīng)信息
FTP響應(yīng)信息由兩部分組成:響應(yīng)碼與描述信息(中間以空格隔開)。其中泪喊,響應(yīng)碼是由3位數(shù)字組成的字符串棕硫,它是對響應(yīng)信息的數(shù)字標(biāo)識,例如200表示用戶登錄成功袒啼;描述信息是對響應(yīng)碼的文字描述哈扮,例如200的描述信息是"Command okay."。FTP響應(yīng)的標(biāo)準(zhǔn)格式為:
響應(yīng)碼 描述信息
常見的FTP響應(yīng)如下表所示:
響 應(yīng) 碼 | 含 義 |
---|---|
110 | 重新啟動(dòng)標(biāo)記應(yīng)答 |
120 | 服務(wù)器準(zhǔn)備就緒的時(shí)間(分鐘數(shù)) |
125 | 打開數(shù)據(jù)連接蚓再,開始傳輸 |
150 | 文件狀態(tài)良好滑肉,打開數(shù)據(jù)連接 |
200 | 命令成功 |
202 | 命令未執(zhí)行 |
211 | 系統(tǒng)狀態(tài) |
212 | 目錄狀態(tài) |
213 | 文件狀態(tài) |
214 | 幫助信息 |
215 | 系統(tǒng)類型 |
220 | 服務(wù)就緒 |
221 | 服務(wù)關(guān)閉控制連接,可以退出登錄 |
225 | 打開數(shù)據(jù)連接 |
226 | 關(guān)閉數(shù)據(jù)連接摘仅,請求的文件操作成功 |
227 | 進(jìn)入被動(dòng)模式(IP地址靶庙、ID端口) |
230 | 登錄因特網(wǎng) |
250 | 請求的文件操作完成 |
257 | 路徑名建立 |
331 | 用戶名正確,需要密碼 |
332 | 登錄時(shí)需要賬戶信息 |
350 | 請求的文件操作需要進(jìn)一步命令 |
421 | 不能提供服務(wù)娃属,關(guān)閉控制連接 |
425 | 無法打開數(shù)據(jù)連接 |
426 | 關(guān)閉連接六荒,中止傳輸 |
450 | 請求的文件操作未執(zhí)行 |
451 | 遇到本地錯(cuò)誤 |
452 | 磁盤空間不足 |
500 | 格式錯(cuò)誤,無效命令 |
501 | 參數(shù)語法錯(cuò)誤 |
502 | 命令未執(zhí)行 |
503 | 命令順序錯(cuò)誤 |
504 | 此參數(shù)下的命令功能未執(zhí)行 |
530 | 未登錄網(wǎng)絡(luò) |
532 | 存儲文件需要賬戶信息 |
550 | 未執(zhí)行請求的操作 |
551 | 不知道的頁類型 |
552 | 超過存儲分配 |
553 | 文件名不合法 |
在Android上的應(yīng)用(實(shí)現(xiàn)一個(gè)FTP服務(wù)器)
- 首先通過啟動(dòng)一個(gè)Service來實(shí)現(xiàn)在后臺守護(hù)ServerScoket模擬的FTP服務(wù)器線程(下面稱為服務(wù)器線程)膳犹,由于Service是在主線程中執(zhí)行的恬吕,所以守護(hù)服務(wù)器線程的工作是在新建的線程中(下面稱為守護(hù)線程)完成的签则,代碼如下所示:
@Override
public void run() {
Log.d(TAG, "Server thread running");
if (isConnectedToLocalNetwork() == false) {
Log.w(TAG, "run: There is no local network, bailing out");
stopSelf();
sendBroadcast(new Intent(ACTION_FAILEDTOSTART));
return;
}
// Initialization of wifi, set up the socket
try {
setupListener();
} catch (IOException e) {
Log.w(TAG, "run: Unable to open port, bailing out.");
stopSelf();
sendBroadcast(new Intent(ACTION_FAILEDTOSTART));
return;
}
// @TODO: when using ethernet, is it needed to take wifi lock?
WifiUtil.takeWifiLock(getApplicationContext(), wifiLock);
PowerUtil.takeWakeLock(getApplicationContext(), wakeLock);
// A socket is open now, so the FTP server is started, notify rest of world
Log.i(TAG, "Ftp Server up and running, broadcasting ACTION_STARTED");
sendBroadcast(new Intent(ACTION_STARTED));
while (!shouldExit) {
if (wifiListener != null) {
if (!wifiListener.isAlive()) {
Log.d(TAG, "Joining crashed wifiListener thread");
try {
wifiListener.join();
} catch (InterruptedException e) {
}
wifiListener = null;
}
}
if (wifiListener == null) {
// Either our wifi listener hasn't been created yet, or has crashed,
// so spawn it
wifiListener = new TcpListener(serverSocket, this);
wifiListener.start();
}
try {
// TODO: think about using ServerSocket, and just closing
// the main socket to send an exit signal
Thread.sleep(WAKE_INTERVAL_MS);
} catch (InterruptedException e) {
Log.d(TAG, "Thread interrupted");
}
}
terminateAllSessions();
if (wifiListener != null) {
wifiListener.quit();
wifiListener = null;
}
shouldExit = false; // we handled the exit flag, so reset it to acknowledge
Log.d(TAG, "Exiting cleanly, returning from run()");
stopSelf();
sendBroadcast(new Intent(ACTION_STOPPED));
}
// This opens a listening socket on all interfaces.
void setupListener() throws IOException {
serverSocket = new ServerSocket();
serverSocket.setReuseAddress(true);
serverSocket.bind(new InetSocketAddress(FtpServerSettings.getPortNumber()));
}
上面setupListener方法就是用來為serverScoket綁定端口用的须床,由于FtpServerSettings.getPortNumber()得到的返回值就是2121,因此serverScoket綁定了2121端口渐裂;由于shouldExit的值是false豺旬,因此守護(hù)線程會每間隔WAKE_INTERVAL_MS時(shí)長就會檢查wifiListener線程是否存活钠惩,如果不存活就會重新建立wifiListener線程,從而完成了守護(hù)wifiListener線程的工作族阅。
wifiListener線程就是serverScoket監(jiān)聽2121端口的工作線程篓跛,即上面提到的ServerScoket模擬的FTP服務(wù)器線程。
- wifiListener對應(yīng)的TcpListener類(堅(jiān)持Thread類)中實(shí)現(xiàn)監(jiān)聽2121端口的過程如下:
@Override
public void run() {
try {
while (true) {
Socket clientSocket = listenSocket.accept();
Log.i(TAG, "New connection, spawned thread");
SessionThread newSession = new SessionThread(clientSocket,
new LocalDataSocket());
newSession.start();
ftpServerService.registerSessionThread(newSession);
}
} catch (Exception e) {
Log.d(TAG, "Exception in TcpListener");
}
}
可以看到是通過調(diào)用serverSocket的accept方法實(shí)現(xiàn)的坦刀,當(dāng)執(zhí)行accept方法后愧沟,F(xiàn)TP服務(wù)器線程就會進(jìn)入等待的狀態(tài),只要有客戶端發(fā)起連接FTP服務(wù)器線程鲤遥,F(xiàn)TP服務(wù)器線程才會繼續(xù)向下執(zhí)行沐寺,現(xiàn)在在windows操作系統(tǒng)的“計(jì)算機(jī)”上的地址欄上輸入上輸入ftp://192.168.10.52:2121/,然后回車盖奈,連接FTP服務(wù)器線程成功后的效果圖如下所示:
然后你就可以像在本地操作文件夾一樣操作android中的文件目錄混坞。
在mac系統(tǒng)中通過finder或者通過瀏覽器的地址欄中輸入ftp://192.168.10.52:2121/也可以連接FTP服務(wù)器線程,效果圖如下:
在mac系統(tǒng)中通過finder或者通過瀏覽器連接雖然是成功的钢坦,但是只能瀏覽和下載文件究孕,修改和上傳是不行的,所以如果想在mac上修改和上傳爹凹,就需要下載一個(gè)ftp客戶端厨诸。
注意:
我現(xiàn)在實(shí)現(xiàn)的是在同一個(gè)局域網(wǎng)上才可以,上面的IP地址192.168.10.52就是手機(jī)的wifi的IP禾酱。
- 當(dāng)?shù)?步中發(fā)起連接(就是上面FTP協(xié)議中提到的控制連接)后泳猬,就會創(chuàng)建SessionThread類(繼承自Thread類)的實(shí)例sessionThread,就相當(dāng)為這個(gè)控制連接創(chuàng)建一個(gè)會話線程宇植,該會話線程用來專門處理客戶端向ServerScoket模擬的FTP服務(wù)器發(fā)送的命令并且對客戶端的命令做出響應(yīng)得封。代碼如下所示:
@Override
public void run() {
Log.i(TAG, "SessionThread started");
if (sendWelcomeBanner) {
writeString("220 SwiFTP " + MyApplication.getVersion() + " ready\r\n");
}
// Main loop: read an incoming line and process it
try {
BufferedReader in = new BufferedReader(new InputStreamReader(
cmdSocket.getInputStream()), 8192); // use 8k buffer
while (true) {
String line;
line = in.readLine(); // will accept \r\n or \n for terminator
if (line != null) {
FTPServerService.writeMonitor(true, line);
Log.d(TAG, "Received line from client: " + line);
FtpCmd.dispatchCommand(this, line);
} else {
Log.i(TAG, "readLine gave null, quitting");
break;
}
}
} catch (IOException e) {
Log.i(TAG, "Connection was dropped");
}
closeSocket();
}
可以看到,會話線程會先解析出FTP客戶端請求的命令指郁,然后通過FtpCmd.dispatchCommand方法處理命令忙上,處理命令的代碼如下:
protected static CmdMap[] cmdClasses = { new CmdMap("SYST", CmdSYST.class),
new CmdMap("USER", CmdUSER.class), new CmdMap("PASS", CmdPASS.class),
new CmdMap("TYPE", CmdTYPE.class), new CmdMap("CWD", CmdCWD.class),
new CmdMap("PWD", CmdPWD.class), new CmdMap("LIST", CmdLIST.class),
new CmdMap("PASV", CmdPASV.class), new CmdMap("RETR", CmdRETR.class),
new CmdMap("NLST", CmdNLST.class), new CmdMap("NOOP", CmdNOOP.class),
new CmdMap("STOR", CmdSTOR.class), new CmdMap("DELE", CmdDELE.class),
new CmdMap("RNFR", CmdRNFR.class), new CmdMap("RNTO", CmdRNTO.class),
new CmdMap("RMD", CmdRMD.class), new CmdMap("MKD", CmdMKD.class),
new CmdMap("OPTS", CmdOPTS.class), new CmdMap("PORT", CmdPORT.class),
new CmdMap("QUIT", CmdQUIT.class), new CmdMap("FEAT", CmdFEAT.class),
new CmdMap("SIZE", CmdSIZE.class), new CmdMap("CDUP", CmdCDUP.class),
new CmdMap("APPE", CmdAPPE.class), new CmdMap("XCUP", CmdCDUP.class), // synonym
new CmdMap("XPWD", CmdPWD.class), // synonym
new CmdMap("XMKD", CmdMKD.class), // synonym
new CmdMap("XRMD", CmdRMD.class), // synonym
new CmdMap("MDTM", CmdMDTM.class), //
new CmdMap("MFMT", CmdMFMT.class), //
new CmdMap("REST", CmdREST.class), //
new CmdMap("SITE", CmdSITE.class), //
};
protected static void dispatchCommand(SessionThread session, String inputString) {
String[] strings = inputString.split(" ");
String unrecognizedCmdMsg = "502 Command not recognized\r\n";
if (strings == null) {
// There was some egregious sort of parsing error
String errString = "502 Command parse error\r\n";
Log.d(TAG, errString);
session.writeString(errString);
return;
}
if (strings.length < 1) {
Log.d(TAG, "No strings parsed");
session.writeString(unrecognizedCmdMsg);
return;
}
String verb = strings[0];
if (verb.length() < 1) {
Log.i(TAG, "Invalid command verb");
session.writeString(unrecognizedCmdMsg);
return;
}
FtpCmd cmdInstance = null;
verb = verb.trim();
verb = verb.toUpperCase();
for (int i = 0; i < cmdClasses.length; i++) {
if (cmdClasses[i].getName().equals(verb)) {
// We found the correct command. We retrieve the corresponding
// Class object, get the Constructor object for that Class, and
// and use that Constructor to instantiate the correct FtpCmd
// subclass. Yes, I'm serious.
Constructor<? extends FtpCmd> constructor;
try {
constructor = cmdClasses[i].getCommand().getConstructor(
new Class[] { SessionThread.class, String.class });
} catch (NoSuchMethodException e) {
Log.e(TAG, "FtpCmd subclass lacks expected " + "constructor ");
return;
}
try {
cmdInstance = constructor.newInstance(new Object[] { session,
inputString });
} catch (Exception e) {
Log.e(TAG, "Instance creation error on FtpCmd");
return;
}
}
}
if (cmdInstance == null) {
// If we couldn't find a matching command,
Log.d(TAG, "Ignoring unrecognized FTP verb: " + verb);
session.writeString(unrecognizedCmdMsg);
return;
}
if (session.isUserLoggedIn()) {
cmdInstance.run();
} else if (session.isAnonymouslyLoggedIn() == true) {
boolean validCmd = false;
for (Class<?> cl : allowedCmdsWhileAnonymous) {
if (cmdInstance.getClass().equals(cl)) {
validCmd = true;
break;
}
}
if (validCmd == true) {
cmdInstance.run();
} else {
session.writeString("530 Guest user is not allowed to use that command\r\n");
}
} else if (cmdInstance.getClass().equals(CmdUSER.class)
|| cmdInstance.getClass().equals(CmdPASS.class)
|| cmdInstance.getClass().equals(CmdQUIT.class)) {
cmdInstance.run();
} else {
session.writeString("530 Login first with USER and PASS, or QUIT\r\n");
}
}
上面的代碼很簡單,首先是對格式不正確的命令做出響應(yīng)(響應(yīng)碼事502闲坎,表示命令未執(zhí)行)疫粥;接著根據(jù)命令名稱找到對應(yīng)的處理這個(gè)命令的類(例如CmdUSER.class就是用來處理USER命令),然后通過反射創(chuàng)建創(chuàng)建該類的實(shí)例腰懂,然后執(zhí)行實(shí)例的run方法梗逮,繼而完成命令的處理,處理完成后绣溜,一般都會返回給FTP客戶端響應(yīng)信息慷彤,具體可以參考上面 常見的FTP響應(yīng)表。
上面的源碼是參考SwiFTP開源軟件,源碼地址:
FTP Server (swiftp)
有興趣的同學(xué)可以自己研究一下底哗。