1、 FTP命令
FTP 協議中規定了一些大家都認識的命令和組成。FTP協議中的命令都由3~4個字母組成,命令與參數之間用空格隔開,每個命令用回車換行結束。
(1)訪問命令
(1)訪問命令有:
USER命令——格式為:USER <username>, 指定登錄的用戶名,以便服務器進行身份驗證。這個命令通常是控制連接后第一個發出的命令
PASS命令——格式為:PASS <password>, 指定用戶密碼,該命令必須跟在登錄用戶名命令之后。
REIN命令——格式為:REIN, 表示重新初始化用戶信息,該命令終止當前USER的傳輸,同時終止正在傳輸的數據,然后重置所有參數,並打開控制連接,以便客戶端再次發生USER命令。
QUIT命令——格式為:QUIT,關閉與服務器的連接
(2)模式設置命令:
PASV命令——格式為:PASV,該命令告訴FTP服務器,讓FTP服務器在指定的數據端口進行監聽,被動接受客戶端的請求。如果未指定任何模式,FTP服務器默認使用PASV模式
PORT命令——格式為:PORT <address>,該命令告訴FTP服務器,客戶端監聽的端口號是address,讓FTP服務器采用主動模式連接客戶端。
TYPE命令——格式為: TYPE <data type>,該命令指定要傳輸的數據類型,有ASCII和BINARY兩種類型。
MODE命令——格式為:MODE <mode>,該命令指定傳輸模式,S表示流,B表示塊,C表示壓縮。
(3)文件管理命令
CWD命令——格式為:CWD <directory>,該命令是用戶可以在不同的目錄或數據集下工作而不用改變登錄信息,directory一般是目錄名或與系統相關的文件集合。
PWD命令——格式為:PWD,該命令返回當前工作目錄。
MKD命令——格式為:MKD <directory>,該命令表示在指定路徑下創建新目錄,directory 表示特定目錄的字符串。
CDUP命令——格式為:CDUP,該命令表示回到上層目錄
RMD命令——格式為:RMD <directory>,刪除指定目錄,directory表示特定目錄的字符串。
LIST命令——格式為:LIST <name>,該命令返回指定路徑下的子目錄及文件列表,name 為路徑。省略路徑時,返回當前路徑下的文件列表。
NLIST命令——格式為:NLIST <directory>,該命令返回指定路徑下的目錄列表,省略路徑時,返回當前目錄。
RNFR命令——格式為:RNFR <old path>,該命令表示重新命名文件,該命令的下一條命令用RNTO指定新的文件名。
RNTO命令——格式為:RNTO <new path>,該命令和RNFR命令共同完成對文件的重命名。
DELE命令——格式為:DELE <filename>,該命令表示刪除指定路徑下的文件
(4)文件傳輸命令:
RETR命令——RETR <filename>,表示下載指定路徑的文件
STOR命令——STOR <filename>,表示上傳一個指定的文件,並將其存儲在指定的位置,如果文件已存在,原文件將被覆蓋,如果文件不存在,則創建新文件。
(5)其他命令
SYST命令——格式為:SYST,該命令返回服務器使用的操作系統。
2、 FTP響應碼
客戶端發送FTP命令后,服務器需要返回FTP響應碼,響應碼即是回答,我們平常聊天中別人問了說了話或者問了問題,另外一方就需要回答,FTP協議中定義以響應碼的形式來作為回答,FTP響應碼由ASCII編碼的3位數字開頭,后面接一行文本提示信息,數字和提示信息中有一個空格,如XXX 接收請求。
每個響應碼同樣以回車換行結束。
FTP響應碼的3位數字每位都有特定的意義,具體見下表:
響應碼 |
表示 |
|
第 1 位 數 字 |
1XX |
表示信息已被服務器正確接收,但尚未被處理 |
2XX |
表示信息已被服務器正確處理完畢 |
|
3XX |
彪西信息已被服務器正在接受,並正在處理中 |
|
4XX |
表示信息處理錯誤(暫時) |
|
5XX |
表示信息處理錯誤(永久) |
|
第 2 位 數 字 |
X0X |
表示語法錯誤 |
X1X |
表示系統狀態與信息 |
|
X2X |
表示與FTP服務器系統連接狀態 |
|
X3X |
表示與用戶認證有關的信息 |
|
X4X |
表示未定義 |
|
X5X |
表示與文件系統有關的信息 |
下表列出了常用的響應碼所代表的意義:
響應碼 |
意義 |
響應碼 |
意義 |
110 |
重新啟動標記應答 |
332 |
登陸是需要賬戶信息 |
120 |
服務在指定時間內准備好 |
350 |
請求的文件操作需要進一步命令 |
125 |
數據連接打開——開始傳輸 |
421 |
服務關閉 |
150 |
文件狀態良好,將要打開數據連接 |
425 |
不能打開數據連接 |
200 |
命令成功 |
426 |
關閉連接,終止傳輸 |
202 |
命令沒有執行 |
450 |
文件不可用 |
211 |
系統狀態回復 |
451 |
中止請求操作:有本地錯誤 |
212 |
目錄狀態回復 |
452 |
磁盤空間不足 |
213 |
文件狀態回復 |
500 |
無效命令 |
214 |
幫助信息回復 |
501 |
語法錯誤 |
215 |
系統類型回復 |
502 |
命令未執行 |
220 |
服務就緒 |
503 |
命令順序錯誤 |
221 |
服務關閉控制連接,可以退出登陸 |
504 |
無效命令參數 |
225 |
數據連接打開,無傳輸正在進行 |
530 |
未登陸 |
226 |
關閉數據連接,請求的文件操作成功 |
532 |
存儲文件需要賬戶信息 |
227 |
進入被動模式 |
550 |
未執行請求操作 |
230 |
用戶已登陸 |
551 |
請求操作終止:頁類型未知 |
250 |
請求的文件操作完成 |
552 |
請求文件操作終止:超過存儲分配 |
257 |
創建路徑名 |
553 |
為執行請求的操作:文件名不合法 |
331 |
用戶名正確,需要口令 |
|
|
3、參考代碼
// 啟動服務器 private void btnFtpServerStartStop_Click(object sender, EventArgs e) { if (myTcpListener == null) { listenThread = new Thread(ListenClientConnect); listenThread.IsBackground = true; listenThread.Start(); lstboxStatus.Enabled = true; lstboxStatus.Items.Clear(); lstboxStatus.Items.Add("啟動Ftp服務..."); btnFtpServerStartStop.Text = "停止"; } else { myTcpListener.Stop(); myTcpListener = null; listenThread.Abort(); lstboxStatus.Items.Add("Ftp服務已停止!"); lstboxStatus.TopIndex = lstboxStatus.Items.Count - 1; btnFtpServerStartStop.Text = "啟動"; } } // 監聽端口,處理客戶端連接 private void ListenClientConnect() { myTcpListener = new TcpListener(IPAddress.Parse(tbxFtpServerIp.Text), int.Parse(tbxFtpServerPort.Text)); // 開始監聽傳入的請求 myTcpListener.Start(); AddInfo("啟動成功!"); AddInfo("Ftp服務運行中...[單機”停止“退出]"); while (true) { try { // 接收連接請求 TcpClient tcpClient = myTcpListener.AcceptTcpClient(); AddInfo(string.Format("客戶端({0})與本機({1})建立Ftp連接", tcpClient.Client.RemoteEndPoint, myTcpListener.LocalEndpoint)); User user = new User(); user.commandSession = new UserSeesion(tcpClient); user.workDir = tbxFtpRoot.Text; Thread t = new Thread(UserProcessing); t.IsBackground = true; t.Start(user); } catch { break; } } } // 處理客戶端用戶請求 private void UserProcessing(object obj) { User user = (User)obj; string sendString = "220 FTP Server v1.0"; RepleyCommandToUser(user, sendString); while (true) { string receiveString = null; try { // 讀取客戶端發來的請求信息 receiveString = user.commandSession.streamReader.ReadLine(); } catch(Exception ex) { if (user.commandSession.tcpClient.Connected == false) { AddInfo(string.Format("客戶端({0}斷開連接!)", user.commandSession.tcpClient.Client.RemoteEndPoint)); } else { AddInfo("接收命令失敗!" + ex.Message); } break; } if (receiveString == null) { AddInfo("接收字符串為null,結束線程!"); break; } AddInfo(string.Format("來自{0}:[{1}]", user.commandSession.tcpClient.Client.RemoteEndPoint, receiveString)); // 分解客戶端發來的控制信息中的命令和參數 string command = receiveString; string param = string.Empty; int index = receiveString.IndexOf(' '); if (index != -1) { command = receiveString.Substring(0, index).ToUpper(); param = receiveString.Substring(command.Length).Trim(); } // 處理不需登錄即可響應的命令(這里只處理QUIT) if (command == "QUIT") { // 關閉TCP連接並釋放與其關聯的所有資源 user.commandSession.Close(); return; } else { switch (user.loginOK) { // 等待用戶輸入用戶名: case 0: CommandUser(user, command, param); break; // 等待用戶輸入密碼 case 1: CommandPassword(user, command, param); break; // 用戶名和密碼驗證正確后登陸 case 2: switch (command) { case "CWD": CommandCWD(user, param); break; case "PWD": CommandPWD(user); break; case "PASV": CommandPASV(user); break; case "PORT": CommandPORT(user, param); break; case "LIST": CommandLIST(user, param); break; case "NLIST": CommandLIST(user, param); break; // 處理下載文件命令 case "RETR": CommandRETR(user, param); break; // 處理上傳文件命令 case "STOR": CommandSTOR(user, param); break; // 處理刪除命令 case "DELE": CommandDELE(user, param); break; // 使用Type命令在ASCII和二進制模式進行變換 case "TYPE": CommandTYPE(user, param); break; default: sendString = "502 command is not implemented."; RepleyCommandToUser(user, sendString); break; } break; } } } }