FTP客户端搭建(linux环境)

本文详细介绍了如何使用C语言实现一个简单的FTP客户端,包括建立连接、登录、切换二进制模式、获取文件大小、下载和上传文件的步骤。还提供了FTP状态码的列表,以及FTP客户端的函数实现,包括ftp_download和ftp_upload接口。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为了从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未执行请求的操作。不允许的文件名。
 

下面的函数实现了ftp_download和ftp_upload两个接口,在需要使用FTP功能的地方调用者两个接口就可以实现FTP通信;

参考程序:

APP_FTP.c

#include "APP_FTP.h"



#define COMMAND_LEN      1024
static char m_send_buffer[1024];
static char m_recv_buffer[1024];
pthread_mutex_t mutex_cmd = PTHREAD_MUTEX_INITIALIZER;/*初始化互斥锁*/


struct ftp_property_t {
    char *download_src;    //下载源URL
    char *download_desc;    //下载目的URL
    char *upload_src;      //上传源URL
    char *upload_desc;      //上传目的URL
    char *ip_addr;      //ftp服务端ip
    int port;       //ftp服务端端口
    char *username;        //username
    char *password;        //password;        
};

static struct ftp_property_t ftp_property;


static int  m_socket_cmd;
static int  m_socket_data;
int socket_create(void)
{

    char buf[512];
    int sockfd;


    //打开套接字,得到套接字描述符
    sockfd = socket(AF_INET, SOCK_STREAM,0);
    if(sockfd<0){
        perror("socket error\n");
        exit(EXIT_FAILURE);
    }

    return sockfd;
}


int  socket_connect(int sock, const char *addr, int port)
{
    struct sockaddr_in server_addr = {0};
    int ret;
    //调用connect链接远端服务器
    server_addr.sin_family = AF_INET;       
    server_addr.sin_port = htons(port);
    inet_pton(AF_INET,addr,&server_addr.sin_addr); //将IP地址从字符串格式转换成网络地址格式
    //链接
    ret = connect(sock,(struct sockaddr *)&server_addr,sizeof(server_addr));
    if(ret < 0){
        perror("connect error");
        //close(sock);
        return ret;

    }

    printf("[FTP]服务器链接成功!\n\n");
}


void socket_close(int sock)
{
    pthread_mutex_lock(&mutex_cmd);
    close(sock);
    pthread_mutex_unlock(&mutex_cmd);
}

int  socket_send(int sock, void *data, int len)
{
    int ret;
    pthread_mutex_lock(&mutex_cmd);
    ret = send(sock,data,len,0);
    pthread_mutex_unlock(&mutex_cmd);
    if(ret<0){
        perror("send error");
    }
    return ret;
}

int  socket_recv(int sock, void *data, int len)
{
    int ret;
    //读取数据
    pthread_mutex_lock(&mutex_cmd);
    ret = recv(sock,data,len,0);
    pthread_mutex_unlock(&mutex_cmd);
    if(ret<0){
        perror("recv error\n");
        close(sock);
    }
    return ret;
}



//命令端口,发送命令
static int ftp_send_command(char *cmd)
{
	int ret;
    char buf[1024];
    memset(buf,'\0',1024);
    memcpy(buf,cmd,strlen(cmd));
	printf("[FTP]send command: %s\r\n", buf);
	ret = socket_send(m_socket_cmd, buf, (int)strlen(buf));
	if(ret < 0)
	{
		printf("[FTP]failed to send command: %s",buf);
		return 0;
	}
	return 1;
}
 
//命令端口,接收应答
static int ftp_recv_respond(char *resp, int len)
{
	int ret;
	int off;
	len -= 1;

    memset(resp,'\0',len);
	for(off=0; off<len; )
	{
        printf("%d ",off);
		ret = socket_recv(m_socket_cmd, &resp[off], 1);
		if(ret < 0)
		{
			printf("[FTP]recv respond error(ret=%d)!\r\n", ret);
			return 0;
		}
        off+=ret;
		if(resp[off-1] == '\n')
		{
			break;
		}
	}
	resp[off+1] = 0;
	printf("[FTP]respond:%s", resp);
	return atoi(resp);
}
 
//设置FTP服务器为被动模式,并解析数据端口
static int ftp_enter_pasv(char *ipaddr, int *port)
{
	int ret;
	char *find;
	int a=0,b=0,c=0,d=0;
	int pa=0,pb=0;
	ret = ftp_send_command("PASV\r\n");
	if(ret != 1)
	{
		return 0;
	}

    
	ret = ftp_recv_respond(m_recv_buffer, COMMAND_LEN);
	if(ret != 227)
	{
		return 0;
	}
    
   //memcpy(m_recv_buffer,"12345(6)",sizeof("12345(6)"));
	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 *src, char *des)
{
	int  ret,i;
	char ipaddr[32];
	int  port;
    char buf[102400] = {0};
	FILE *fp = NULL;

	//查询数据地址
	ret=ftp_enter_pasv(ipaddr, &port);
	if(ret != 1)
	{
		return 0;
	}
	ret=socket_connect(m_socket_data, ipaddr, port);
	if(ret < 0)
	{
		return 0;
	}
	//准备上传
	sprintf(m_send_buffer, "STOR %s\r\n", des);
	ret = ftp_send_command(m_send_buffer);
	if(ret != 1)
	{
		return 0;
	}
	ret = ftp_recv_respond(m_recv_buffer, COMMAND_LEN);
	if(ret != 150)
	{
		socket_close(m_socket_data);
		return 0;
	}
	


    //读取文件
    fp = fopen(src,"ab+");
    if(fp == NULL){
        perror("Open error");
        return 0;
    }

    //获取文件大小
    struct stat statbuf;
    stat(src, &statbuf);
    int len = statbuf.st_size;


	for(i=0; i<len; )
	{
        //开始上传
        memset(buf,'\0', sizeof(buf));
        //ret = fread(buf,1,10240,fp);
        ret = fread(buf,1,sizeof(buf),fp);

        ret = socket_send(m_socket_data, buf, ret);
        if(ret < 0)
        {	
            printf("[FTP]send data error:%d!\r\n",ret);
            socket_close(m_socket_data);
            return 0;
        }
        i+=ret;
        printf("[FTP]upload: %d/%d.\r\n",i,len);


        if(feof(fp)){
            break;
        }
    }
	socket_close(m_socket_data);
    fclose(fp);
	//上传完成,等待回应
	ret = ftp_recv_respond(m_recv_buffer, COMMAND_LEN);
    if(ret == 226){
        printf("[FTP]upload complete,save to%s!\r\n",des);
    }
	return (ret==226);
}
 
//下载文件
int  _ftp_download(char *src,char *des ,int len)
{
	int   i;
	int   ret;
	char  ipaddr[32];
	int   port;
    char buf[102400];
    FILE *fp = NULL;
    
	//查询数据地址
	ret = ftp_enter_pasv(ipaddr, &port);
	if(ret != 1)
	{
		return 0;
	}


	//连接数据端口
	ret = socket_connect(m_socket_data, ipaddr, port);
	if(ret < 0)
	{
		printf("[FTP]failed to connect data port\r\n");
		return 0;
	}
 

	//准备下载
	sprintf(m_send_buffer, "RETR %s\r\n", src);
	ret = ftp_send_command(m_send_buffer);
	if(ret != 1)
	{
		return 0;
	}

	ret = ftp_recv_respond(m_recv_buffer, COMMAND_LEN);
	if(ret != 150)
	{
		socket_close(m_socket_data);
		return 0;
	}

    fp = fopen(des,"ab+");
    if(fp == NULL){
        perror("Open error");
        return 0;
    }
        
    

	//开始下载,读取完数据后,服务器会自动关闭连接
	for(i=0; i<len; i+=ret)
	{
        memset(buf,'\0',sizeof(buf));
		//ret = socket_recv(m_socket_data, ((char *)buf) + i, len);
		ret = socket_recv(m_socket_data, buf, sizeof(buf));
		printf("[FTP]download %d/%d.\r\n", i + ret, len);
		if(ret < 0)
		{
			printf("[FTP]download was interrupted.\r\n");
			break;
		}

        if(fwrite(buf,1,ret,fp) < ret){
            printf("[FTP]fwrite error.\r\n");
            fclose(fp);
            return 0;
        };
	}

	socket_close(m_socket_data);
    fclose(fp);
	ret = ftp_recv_respond(m_recv_buffer, COMMAND_LEN);
    if(ret == 226){
        //下载完成
        printf("[FTP]download %d/%d bytes complete,Save to %s.\r\n", i, len,des);
    }else{
        printf("[FTP]download falied.\r\n");
        return 0;
    }
	return (ret==226);
}
 
//返回文件大小
int  ftp_filesize(char *name)
{
	int ret;
	int size;
    memset(m_send_buffer, '\0', sizeof(m_send_buffer));
	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, COMMAND_LEN);
	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,i;
	printf("[FTP]connect...\r\n");

    //3次链接服务器不成功就取消连接
    for ( i = 0; i < 3; i++)
    {
        ret = socket_connect(m_socket_cmd, addr, port);
        if(ret >= 0){
            break;
        }else if(ret < 0 && i < 3 ){
            sleep(3);
            printf("[FTP]Trying connect again...\r\n");
        }else{
            printf("[FTP]connect server failed!\r\n");
            return 0;
        }
    }
    
 


	printf("[FTP]connect ok.\r\n");
    
    //等待欢迎信息
	ret = ftp_recv_respond(m_recv_buffer, COMMAND_LEN);//1024
	if(ret != 220)
	{
		printf("[FTP]bad server, ret=%d!\r\n", ret);
		socket_close(m_socket_cmd);
		return 0;
	}

	printf("[FTP]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, COMMAND_LEN);
	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, 24);
	if(ret != 230)
	{
		socket_close(m_socket_cmd);
		return 0;
	}
	printf("[FTP]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, COMMAND_LEN);
	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();
}


/**
 * @brief Function:API,从ftp服务器下载文件
 * @param src:FTP服务器URL
 * @param des:本地的保存位置-
 * @param Host:FTP服务器IP地址 *
 * @param Port:FTP服务器端口 
 * @param username:FTP服务器账户
 * @param password:FTP服务器密码
 */
int ftp_download(char *src, char *des,char *Host,int Port,char *username,char *password)
{
    memset(&ftp_property,0,sizeof(ftp_property));
    ftp_property.download_src = src;
    ftp_property.download_desc = des;
    ftp_property.ip_addr = Host;
    ftp_property.username = username;
    ftp_property.password = password;
    ftp_property.port = Port;
    
    printf("[FTP]ftp_download:\r\n[src:%s]\r\n[des:%s]\r\n[IPAddr:%s]\r\n[Port:%u]\r\n[username:%s]\r\n[password:%s]\r\n", \
        ftp_property.download_src,ftp_property.download_desc,ftp_property.ip_addr,ftp_property.port,ftp_property.username,ftp_property.password);


    int ret = 0;
        ftp_init();

        ret = ftp_login(ftp_property.ip_addr, ftp_property.port, ftp_property.username,ftp_property.password);
        if(ret == 0){
            ftp_quit();
            return;
        }
        

        int size = ftp_filesize(ftp_property.download_src);
        if(ret == 0){
            ftp_quit();
            return;
        }
        ret = _ftp_download(ftp_property.download_src,ftp_property.download_desc, size);
        if(ret == 0){
            ftp_quit();
            return;
        }
        ftp_quit();
/*
	if(pthread_create(&Ftp_download_thread, NULL, ftp_download_thread, NULL) != 0){     
		perror("线程\"socket_net_dila\"创建失败!\r\n");
    }
    */
}

/**
 * @brief Function:API,向ftp服务器上传文件
 * @param src:本地URL
 * @param des:服务器保存位置
 * @param Host:FTP服务器IP地址 *
 * @param Port:FTP服务器端口 
 * @param username:FTP服务器账户
 * @param password:FTP服务器密码
 */
int ftp_upload(char *src, char *des,char *Host,int Port,char *username,char *password)
{
    int ret = 0;
    memset(&ftp_property,0,sizeof(ftp_property));
    ftp_property.upload_src = src;
    ftp_property.upload_desc = des;
    ftp_property.ip_addr = Host;
    ftp_property.username = username;
    ftp_property.password = password;
    ftp_property.port = Port;
    
    printf("[FTP]ftp_upload:\r\n[src:%s]\r\n[des:%s]\r\n[IPAddr:%s]\r\n[Port:%u]\r\n[username:%s]\r\n[password:%s]\r\n", \
        ftp_property.upload_src,ftp_property.upload_desc,ftp_property.ip_addr,ftp_property.port,ftp_property.username,ftp_property.password);

    ftp_init();
    ret = ftp_login(ftp_property.ip_addr, ftp_property.port, ftp_property.username,ftp_property.password);
    if(ret == 0)
        return;
    ret = _ftp_upload(ftp_property.upload_src,ftp_property.upload_desc);
    if(ret == 0)
        return;
    ftp_quit();

	//if(pthread_create(&Ftp_upload_thread, NULL, ftp_upload_thread, NULL) != 0){     
	//	perror("线程\"socket_net_dila\"创建失败!\r\n");
	//}
}


void main(void)
{

    return 0;
}







APP_FTP.h


#ifndef __APP_THREAD_FTP__
#define __APP_THREAD_FTP__

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <sys/socket.h>
#include <linux/if.h>
#include <fcntl.h>
#include <arpa/inet.h>
int ftp_download(char *src, char *des,char *Host,int Port,char *username,char *password);
int ftp_upload(char *src, char *des,char *Host,int Port,char *username,char *password);
#endif // !__APP_THREAD_FTP__

文章参考了(60条消息) C语言实现的简易FTP客户端_星沉地动的博客-优快云博客_c语言实现ftp

在他的基础上对接口进行了进一步封装和优化

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值