最近用CBrother脚本语言实现了FTP协议,为了方便记忆今天就来用画图的方式记录一下。首先ftp协议是一个文本协议,每一行都要以rn结尾
FTP协议中,客户端发送消息组成为 [命令字 参数],服务端回复为[响应码 描述],其中服务端回应的描述没有具体意义,成功与否主要是判断响应码。
连接服务端第一步,客户端主动发起连接,服务端返回自己的名称
编码约定为了保证两端编码一致不产生乱码,一般第一时间会约定编码格式
登录登录失败
账号验证
登录成功
FTP数据传输,PASV和PORT登录成功后,就可以传输数据了,FTP传输较大数据时,并不是通过登录的这一条线路发送的,如果我们把这一条线路称为控制线路,那么传输大数据时会重新创建一条数据线路,而且数据线路在传输完毕后就会销毁,每次传输大数据都会重新开启一条数据线路。
- PASV模式:服务端监听,客户端主动连接
返回值里,(192,168,1,2,82,171),前面4个是IP,后面两个是端口 (82 << 8)+ 171 = 21163
连接服务器
- PORT模式:客户端监听,服务端主动连接
同样的(192,168,1,3,71,222)其中前4个数字是IP后面两个是端口 (71 << 8) + 222 = 18398
对于数据线路的连接模式一般是由客户端决定的,也可以在尝试了一种模式失败后再使用另一种模式,最大程度的防止防火墙类软件的干扰
需要使用数据线路的命令有如下几个,这几条命令在发送命令前都会提前创建好数据线路:
LIST | 获取文件列表 |
NLST | 获取文件名 |
RETR | 下载文件 |
STOR | 上传文件 |
其他的一些命令,因为没有太大的数据,都是通过控制线路发出的
(以下图中出现的绿色箭头,表示使用PASV或PORT模式创建了数据线路后,再发送数据)
查看文件列表【LIST】在windows系统下默认发送dos风格文件目录,可以通过发送 SITE DIRSTYLE命令行风格切换
切换风格
发送linux风格目录列表
选择工作目录【CWD】CWD用来切换了工作目录,其他的一些需要路径的命令才可基于工作目录做相对路径操作
通过相对路径获取列表
下载文件【RETR】修改文件名【RNFR】【RNTO】修改文件名是先通过RNFR选中文件,再通过RNTO修改。如果RNTO给服务端的是一个路径,那么就相当于挪动文件
工作目录回退【CDUP】CDUP命令相当于命令行中的CD ..,退出到上一级目录作为工作目录
删除文件【DELE】DELE命令在FTP协议约定中是用来删除文件,但是我发现很多FTP客户端并没有遵守这条协议,在删除一个目录的时也会发送这条命令。所以如果你要自己实现服务端,建议这条命令判断是否为目录,如果为目录就删除目录(如果不为空也要删除子项),这样可以兼容更多的FTP客户端
绝对路径在所有可以提交路径的命令中里,如果第一个字符是’/’表示基于工作目录的绝对路径,否则就表示是当前工作目录的相对路径
在所有需提交路径的命令中,如果第一个字符是’/’表示基于绝对路径,否则就表示是当前工作目录的相对路径
上传文件【STOR】删除目录【RMD】RMD命令在FTP协议中约定只能删除空目录,但有很多客户端是不遵守这条约定的。所以如果你要实现FTP客户端在删除目录时应该先删除目录里的子项这样来适配更多的服务端。相反如果你实现的是FTP服务端那么在处理这条命令的时候应该判断目录非空情况下自动删除目录子项,这样来适配更多的客户端
退出登录【QUIT】退出登录
一些常用的FTP命令字CDUP | 设置工作目录上一级为工作目录 |
CWD | 改变服务端上的工作目录 |
DELE | 删除服务端上的指定文件 |
LIST | 列出文件列表 |
MKD | 在服务端上建立指定目录 |
NLST | 列出文件名 |
NOOP | 无动作,心跳保活 |
PASS | 密码 |
PASV | 请求PASV模式数据线路 |
PORT | 请求PORT模式数据线路 |
PWD | 显示当前工作目录 |
QUIT | 从 FTP 服务端上退出登录 |
RMD | 在服务端上删除指定目录 |
RNFR | 对旧路径重命名 |
RNTO | 对新路径重命名 |
SITE | 由服务端提供的站点特殊参数 |
STOR | 储存(复制)文件到服务端上 |
SYST | 返回服务端使用的操作系统 |
TYPE | 数据类型(A=ASCII,I=binary) |
USER | 系统登录的用户名 |
CBrother包中lib目录下有实现的FTP源码,ftp.cb为客户端实现,ftpserver.cb为服务端实现,有兴趣的小伙伴可以看看源码加深一下认识
用CBrother启动一个FTP服务如下就启动了一个ftp服务,可以通过ftp://127.0.0.1直接访问
import lib/ftp
function main(parm)
{
var ftp = new FtpServer();
ftp.addUser("win7","123");
ftp.setRoot("C:UsersaaaDesktopftp");
ftp.startServer(21);
while (1)
{
Sleep(1000);
}
}
用CBrother启动一个FTP客户端
import lib/ftp
function main(params)
{
var ftp = new FtpClient();
ftp.connect("127.0.0.1",21);
if(!ftp.login("win7","123"))
{
print ftp._lastResponseValue;
return;
}
ftp.cwd("dir");
ftp.list("");
ftp.quit();
}