FTP命令字和返回码

为了从FTP服务器下载文件,需要要实现一个简单的FTP客户端。

FTP(文件传输协议) 是 TCP/IP 协议组中的应用层协议。

FTP协议使用字符串格式命令字,每条命令都是一行字符串,以“\r\n”结尾。

客户端发送格式是:命令+空格+参数+"\r\n"的格式

服务器返回格式是以:状态码+空格+提示字符串+"\r\n"的格式,代码只要解析状态码就可以了。

读写文件需要登陆服务器,特殊用户名:anonymous,表示匿名。注意大小写敏感。

 

从FTP服务器下载文件的基本流程如下:

1. 建立TCP连接,该协议默认使用21端口,当然可以指定其它端口,取决于服务器的配置。

2. 连接成功之后,服务器会发送一行欢迎文字,例如:220 welcome.
    其中左边的数字220表示就绪状态,220后面有一个空格,空格后面是提示文字。
    在解析命令应答的时候,只需要获取前面的数字即可。

3. 收到欢迎信息后,就要开始登陆了,先用USER命令发送用户名,服务器返回331状态。
    然后再用PASS命令发送登陆密码,服务器返回530表示密码错误,返回230表示密码正确。
    发送:USER anonymous
    接收:331 Anonymous login ok, send your complete email address as your password
    发送:PASS anonymous
    接收:230 Anonymous access granted, restrictions apply

4. 登陆成功之后,再发送一条TYPE I命令,进入二进制模式,这样获取文件数据的时候,就会以二进制字节流发送。
    避免以ASCII码格式发送文件数据。

5. 获取文件长度
   发送:SIZE /path/filename
   失败:550 /path/filename: No such file or directory
   成功:213 [filesize]
   返回[filesize]是十进制数字,表示该文件在大小,字节为单位

6. 下载文件
   下载文件前,先发送PASV命令,进入被动模式,这样FTP服务器就会开放一个新的端口,用于接收文件数据。
   客户端成功连接到这个数据端口后,再发送RETR命令请求下载文件,这时文件数据就会从新的端口发送过来,文件传输完毕,服务器自动关闭数据端口。
   发送:PASV
   接收:227 Entering Passive Mode (145,24,145,107,207,235).
   后面括号内的5个数字,分别表示IP地址和端口号,端口号分为高8位和低8位,高8位在前
   这里的例子表示IP地址为145.24.145.107,端口号为53227(计算公式:207 * 256 + 235)。

   发送:RETR /path/filename
   接收:150 Opening BINARY mode data connection for /path/filename (54 bytes)
   >>>从数据端口接收文件数据
   接收:226 Transfer complete
 

7. 上传文件

  上传文件与下载文件类似,也是发送PASV命令,获取到数据端口,然后发送STOR命令请求上传文件。
  不同的是上传文件是往这个数据端口写文件数据,写完数据后,客户端主动断开与数据端口的TCP连接,服务器会返回一条上传成功的状态。
  发送:PASV
  接收:227 Entering Passive Mode (145,24,145,107,207,235).
  发送:STOR /path/filename
  接收:150 Opening BINARY mode data connection for /path/filename
   >>>往数据端口写数据
  接收:226 Transfer complete

=============================

FTP常见的状态码如下:

110重新启动标记答复。
120服务已就绪,在nnn分钟后开始。
125数据连接已打开,正在开始传输。
150文件状态正常,准备打开数据连接。
2xx-肯定的完成答复一项操作已经成功完成。客户端可以执行新命令。
200命令确定。
202未执行命令,站点上的命令过多。
211系统状态,或系统帮助答复。
212目录状态。
213文件状态。
214帮助消息。
215NAME系统类型,其中,NAME是AssignedNumbers文档中所列的正式系统名称。
220服务就绪,可以执行新用户的请求。
221服务关闭控制连接。如果适当,请注销。
225数据连接打开,没有进行中的传输。
226关闭数据连接。请求的文件操作已成功(例如,传输文件或放弃文件)。
227进入被动模式(h1,h2,h3,h4,p1,p2)。
230用户已登录,继续进行。
250请求的文件操作正确,已完成。
257已创建“PATHNAME”。
3xx-肯定的中间答复该命令已成功,但服务器需要更多来自客户端的信息以完成对请求的处理。
331用户名正确,需要密码。
332需要登录帐户。
350请求的文件操作正在等待进一步的信息。
4xx-瞬态否定的完成答复该命令不成功,但错误是暂时的。如果客户端重试命令,可能会执行成功。
421服务不可用,正在关闭控制连接。如果服务确定它必须关闭,将向任何命令发送这一应答。
425无法打开数据连接。 426Connectionclosed;transferaborted.
450未执行请求的文件操作。文件不可用(例如,文件繁忙)。
451请求的操作异常终止:正在处理本地错误。
452未执行请求的操作。系统存储空间不够。
5xx-永久性否定的完成答复该命令不成功,错误是永久性的。如果客户端重试命令,将再次出现同样的错误。
500语法错误,命令无法识别。这可能包括诸如命令行太长之类的错误。
501在参数中有语法错误。
502未执行命令。
503错误的命令序列。
504未执行该参数的命令。
530未登录。
532存储文件需要帐户。
550未执行请求的操作。文件不可用(例如,未找到文件,没有访问权限)。
551请求的操作异常终止:未知的页面类型。
552请求的文件操作异常终止:超出存储分配(对于当前目录或数据集)。
553未执行请求的操作。不允许的文件名。

=============================================================
用C语言实现了一个简单的FTP模块,支持上传和下载文件。
————————————————

  1. #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include "socket.h"
    #include "log.h"
    #include "ftp.h"
     
    static int  m_socket_cmd;
    static int  m_socket_data;
    static char m_send_buffer[1024];
    static char m_recv_buffer[1024];
     
    //命令端口,发送命令
    static int ftp_send_command(char *cmd)
    {
    	int ret;
    	LOG_INFO("send command: %s\r\n", cmd);
    	ret = socket_send(m_socket_cmd, cmd, (int)strlen(cmd));
    	if(ret < 0)
    	{
    		LOG_INFO("failed to send command: %s",cmd);
    		return 0;
    	}
    	return 1;
    }
     
    //命令端口,接收应答
    static int ftp_recv_respond(char *resp, int len)
    {
    	int ret;
    	int off;
    	len -= 1;
    	for(off=0; off<len; off+=ret)
    	{
    		ret = socket_recv(m_socket_cmd, &resp[off], 1);
    		if(ret < 0)
    		{
    			LOG_INFO("recv respond error(ret=%d)!\r\n", ret);
    			return 0;
    		}
    		if(resp[off] == '\n')
    		{
    			break;
    		}
    	}
    	resp[off+1] = 0;
    	LOG_INFO("respond:%s", resp);
    	return atoi(resp);
    }
     
    //设置FTP服务器为被动模式,并解析数据端口
    static int ftp_enter_pasv(char *ipaddr, int *port)
    {
    	int ret;
    	char *find;
    	int a,b,c,d;
    	int pa,pb;
    	ret = ftp_send_command("PASV\r\n");
    	if(ret != 1)
    	{
    		return 0;
    	}
    	ret = ftp_recv_respond(m_recv_buffer, 1024);
    	if(ret != 227)
    	{
    		return 0;
    	}
    	find = strrchr(m_recv_buffer, '(');
    	sscanf(find, "(%d,%d,%d,%d,%d,%d)", &a, &b, &c, &d, &pa, &pb);
    	sprintf(ipaddr, "%d.%d.%d.%d", a, b, c, d);
    	*port = pa * 256 + pb;
    	return 1;
    }
     
    //上传文件
    int  ftp_upload(char *name, void *buf, int len)
    {
    	int  ret;
    	char ipaddr[32];
    	int  port;
    	
    	//查询数据地址
    	ret=ftp_enter_pasv(ipaddr, &port);
    	if(ret != 1)
    	{
    		return 0;
    	}
    	ret=socket_connect(m_socket_data, ipaddr, port);
    	if(ret != 1)
    	{
    		return 0;
    	}
    	//准备上传
    	sprintf(m_send_buffer, "STOR %s\r\n", name);
    	ret = ftp_send_command(m_send_buffer);
    	if(ret != 1)
    	{
    		return 0;
    	}
    	ret = ftp_recv_respond(m_recv_buffer, 1024);
    	if(ret != 150)
    	{
    		socket_close(m_socket_data);
    		return 0;
    	}
    	
    	//开始上传
    	ret = socket_send(m_socket_data, buf, len);
    	if(ret != len)
    	{	
    		LOG_INFO("send data error!\r\n");
    		socket_close(m_socket_data);
    		return 0;
    	}
    	socket_close(m_socket_data);
     
    	//上传完成,等待回应
    	ret = ftp_recv_respond(m_recv_buffer, 1024);
    	return (ret==226);
    }
     
    //下载文件
    int  ftp_download(char *name, void *buf, int len)
    {
    	int   i;
    	int   ret;
    	char  ipaddr[32];
    	int   port;
        
    	//查询数据地址
    	ret = ftp_enter_pasv(ipaddr, &port);
    	if(ret != 1)
    	{
    		return 0;
    	}
    	//连接数据端口
    	ret = socket_connect(m_socket_data, ipaddr, port);
    	if(ret != 1)
    	{
    		LOG_INFO("failed to connect data port\r\n");
    		return 0;
    	}
     
    	//准备下载
    	sprintf(m_send_buffer, "RETR %s\r\n", name);
    	ret = ftp_send_command(m_send_buffer);
    	if(ret != 1)
    	{
    		return 0;
    	}
    	ret = ftp_recv_respond(m_recv_buffer, 1024);
    	if(ret != 150)
    	{
    		socket_close(m_socket_data);
    		return 0;
    	}
    	
    	//开始下载,读取完数据后,服务器会自动关闭连接
    	for(i=0; i<len; i+=ret)
    	{
    		ret = socket_recv(m_socket_data, ((char *)buf) + i, len);
    		LOG_INFO("download %d/%d.\r\n", i + ret, len);
    		if(ret < 0)
    		{
    			LOG_INFO("download was interrupted.\r\n");
    			break;
    		}
    	}
    	//下载完成
    	LOG_INFO("download %d/%d bytes complete.\r\n", i, len);
    	socket_close(m_socket_data);
    	ret = ftp_recv_respond(m_recv_buffer, 1024);
    	return (ret==226);
    }
     
    //返回文件大小
    int  ftp_filesize(char *name)
    {
    	int ret;
    	int size;
    	sprintf(m_send_buffer,"SIZE %s\r\n",name);
    	ret = ftp_send_command(m_send_buffer);
    	if(ret != 1)
    	{
    		return 0;
    	}
    	ret = ftp_recv_respond(m_recv_buffer, 1024);
    	if(ret != 213)
    	{
    		return 0;
    	}
    	size = atoi(m_recv_buffer + 4);
    	return size;
    }
     
    //登陆服务器
    int ftp_login(char *addr, int port, char *username, char *password)
    {
    	int ret;
    	LOG_INFO("connect...\r\n");
    	ret = socket_connect(m_socket_cmd, addr, port);
    	if(ret != 1)
    	{
    		LOG_INFO("connect server failed!\r\n");
    		return 0;
    	}
    	LOG_INFO("connect ok.\r\n");
        //等待欢迎信息
    	ret = ftp_recv_respond(m_recv_buffer, 1024);
    	if(ret != 220)
    	{
    		LOG_INFO("bad server, ret=%d!\r\n", ret);
    		socket_close(m_socket_cmd);
    		return 0;
    	}
    	
    	LOG_INFO("login...\r\n");
        //发送USER
    	sprintf(m_send_buffer, "USER %s\r\n", username);
    	ret = ftp_send_command(m_send_buffer);
    	if(ret != 1)
    	{
    		socket_close(m_socket_cmd);
    		return 0;
    	}
    	ret = ftp_recv_respond(m_recv_buffer, 1024);
    	if(ret != 331)
    	{
    		socket_close(m_socket_cmd);
    		return 0;
    	}
    	
        //发送PASS
    	sprintf(m_send_buffer, "PASS %s\r\n", password);
    	ret = ftp_send_command(m_send_buffer);
    	if(ret != 1)
    	{
    		socket_close(m_socket_cmd);
    		return 0;
    	}
    	ret = ftp_recv_respond(m_recv_buffer, 1024);
    	if(ret != 230)
    	{
    		socket_close(m_socket_cmd);
    		return 0;
    	}
    	LOG_INFO("login success.\r\n");
    	
        //设置为二进制模式
    	ret = ftp_send_command("TYPE I\r\n");
    	if(ret != 1)
    	{
    		socket_close(m_socket_cmd);
    		return 0;
    	}
    	ret = ftp_recv_respond(m_recv_buffer, 1024);
    	if(ret != 200)
    	{
    		socket_close(m_socket_cmd);
    		return 0;
    	}
    	return 1;
    }
     
    void ftp_quit(void)
    {
    	ftp_send_command("QUIT\r\n");
    	socket_close(m_socket_cmd);
    }
     
    void ftp_init(void)
    {
    	m_socket_cmd = socket_create();
    	m_socket_data= socket_create();
    }
    

     

 

命令和返回码
C-->S:命令
S-->C:返回码
每一个Ftp发送之后,Ftp服务器都会返回一个字符串,其中包括一个返回代码和一串说明信息。这个返回码主要是用于判断命令是否被成功执行了。除此之 外,还有一个非常重要的命令的返回。当发送PASV之后,返回“227 Entering Passive Mode (127,0,0,1,4,18)”。这意味着在服务器上有一个端口被开放,他将为我们后面接着的数据传输作好准备,但是我们如何知道该端口号呢,就在 (127,0,0,1,4,18)中,前面四位指服务器的地址,关键是最后两位,将最后第二位乘256再加上最后一位的值就是我们的端口号,也就是 4*256+18。取得端口号之后我们就可以用socket连接到这里。这为我们后面的工作作好准备了,因为我们的取得列表,上传,下载文件都要依靠它来 实现。

一个非典型的ftp交互实例:
Response: 220 Gene6 FTP Server v3.10.0 (Build 2) ready...
Request: USER anonymous
Response: 331 Password required for anonymous.
Request: PASS notexist.com
Response: 230 User anonymous logged in.
Request: CWD board
Response: 250 CWD command successful. "/board" is current directory.
Request: TYPE I
Response: 200 Type set to I.
Request: SIZE 4saac062.zip
Response: 213 248288
Request: RETR 4saac062.zip
Response: 150 Data connection accepted from x.x.x.x:2841; transfer starting for /board/4saac062.zip (248288 bytes)


FTP命令:
灰色的命令一般很少使用,所以往往在具体实现中不被支持,所以可能返回的信息是“500 'xx': command not understood”。

命令

描述

ABOR

中断数据连接程序

ACCT <account>

系统特权帐号

ALLO <bytes>

为服务器上的文件存储器分配字节

APPE <filename>

添加文件到服务器同名文件

CDUP <dir path>

改变服务器上的父目录

CWD <dir path>

改变服务器上的工作目录

DELE <filename>

删除服务器上的指定文件

HELP <command>

返回指定命令信息

LIST <name>

如果是文件名列出文件信息,如果是目录则列出文件列表

MODE <mode>

传输模式(S=流模式,B=块模式,C=压缩模式)

MKD <directory>

在服务器上建立指定目录

NLST <directory>

列出指定目录内容

NOOP

无动作,除了来自服务器上的承认

PASS <password>

系统登录密码

PASV

请求服务器等待数据连接

PORT <address>

IP 地址和两字节的端口 ID

PWD

显示当前工作目录

QUIT

从 FTP 服务器上退出登录

REIN

重新初始化登录状态连接

REST <offset>

由特定偏移量重启文件传递

RETR <filename>

从服务器上找回(复制)文件

RMD <directory>

在服务器上删除指定目录

RNFR <old path>

对旧路径重命名

RNTO <new path>

对新路径重命名

SITE <params>

由服务器提供的站点特殊参数

SIZE〈FILENAME〉

文件大小,执行成功返回 213

SMNT <pathname>

挂载指定文件结构

STAT <directory>

在当前程序或目录上返回信息

STOR <filename>

储存(复制)文件到服务器上

STOU <filename>

储存文件到服务器名称上

STRU <type>

数据结构(F=文件,R=记录,P=页面)

SYST

返回服务器使用的操作系统

TYPE <data type>

数据类型(A=ASCII,E=EBCDIC,I=binary)




FTP返回码/响应码:

 

响应代码

解释说明

110

新文件指示器上的重启标记

120

服务器准备就绪的时间(分钟数)

125

打开数据连接,开始传输

150

打开连接

200

成功

202

命令没有执行

211

系统状态回复

212

目录状态回复

213

文件状态回复

214

帮助信息回复

215

系统类型回复

220

服务就绪

221

退出网络

225

打开数据连接

226

结束数据连接

227

进入被动模式(IP 地址、ID 端口)

230

登录完成

250

文件行为完成

257

路径名建立

331

要求密码

332

要求帐号

350

文件行为暂停

421

服务关闭

425

无法打开数据连接

426

结束连接

450

文件不可用

451

遇到本地错误

452

磁盘空间不足

500

无效命令

501

错误参数

502

命令没有执行

503

错误指令序列

504

无效命令参数

530

未登录网络

532

存储文件需要帐号

550

文件不可用

551

不知道的页类型

552

超过存储分配

553

文件名不允许

 

=========================================

5. FTP的工作方式

  FTP支持两种模式,一种方式叫做Standard (也就是 PORT方式,主动方式),一种是 Passive (也就是PASV,被动方式)。 Standard模式 FTP的客户端发送 PORT 命令到FTP服务器。Passive模式FTP的客户端发送 PASV命令到 FTP Server。

  下面介绍一个这两种方式的工作原理:

  Port模式FTP 客户端首先和FTP服务器的TCP 21端口建立连接,通过这个通道发送命令,客户端需要接收数据的时候在这个通道上发送PORT命令。 PORT命令包含了客户端用什么端口接收数据。在传送数据的时候,服务器端通过自己的TCP 20端口连接至客户端的指定端口发送数据。 FTP server必须和客户端建立一个新的连接用来传送数据。(可以看到在这种方式下是客户端和服务器建立控制连接,服务器向客户端建立数据连接,其中,客户端的控制连接和数据连接的端口号是大于1024的两个端口号(临时端口),而FTP服务器的数据端口为20,控制端口为21)

  Passive模式在建立控制通道的时候和Standard模式类似,但建立连接后发送的不是Port命令,而是Pasv命令。FTP服务器收到Pasv命令后,随机打开一个临时端口(也叫自由端口,端口号大于1023小于65535)并且通知客户端在这个端口上传送数据的请求,客户端连接FTP服务器此端口,然后FTP服务器将通过这个端口进行数据的传送,这个时候FTP server不再需要建立一个新的和客户端之间的连接。(可以看到这种情况下的连接都是由客户端向服务器发起的,与下面所说的“为了解决服务器发起到客户的连接的问题,人们开发了一种不同的FTP连接方式。这就是所谓的被动方式”相对应,而服务器端的数据端口是临时端口,而不是常规的20)

  很多防火墙在设置的时候都是不允许接受外部发起的连接的,所以许多位于防火墙后或内网的FTP服务器不支持PASV模式,因为客户端无法穿过防火墙打开FTP服务器的高端端口;而许多内网的客户端不能用PORT模式登陆FTP服务器,因为从服务器的TCP 20无法和内部网络的客户端建立一个新的连接,造成无法工作。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值