C语言实现的简易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模块,支持上传和下载文件。

#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();
}

 


2020.04.25补充:
补充socket.h的源码:
socket.h是网络编程的底层接口,不同的系统平台有不同的实现方法。因此需要自行实现这些函数。

#ifndef __SOCKET_H
#define __SOCKET_H

int  socket_create(void);
void socket_delete(int sock);
int  socket_connect(int sock, const char *addr, int port);
void socket_close(int sock);
int  socket_send(int sock, void *data, int len);
int  socket_recv(int sock, void *data, int len);

#endif

补充应用代码:

//应用层下载文件的调用流程(仅供参考)
void main()
{
    int ret;
    ftp_init();
    ret = ftp_login("192.168.0.1", 21, "admin", "admin");
    int size = ftp_filesize("/dir/filename.bin");
    char *buf = malloc(size);
    ret = ftp_download("/dir/filename.bin", buf, size);
    ftp_quit();
    free(buf);
}

 

  • 32
    点赞
  • 151
    收藏
    觉得还不错? 一键收藏
  • 49
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 49
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值