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; } } } }