为了从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;    //下载源URLchar *download_desc;    //下载目的URLchar *upload_src;      //上传源URLchar *upload_desc;      //上传目的URLchar *ip_addr;      //ftp服务端ipint port;       //ftp服务端端口char *username;        //usernamechar *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);//1024if(ret != 220){printf("[FTP]bad server, ret=%d!\r\n", ret);socket_close(m_socket_cmd);return 0;}printf("[FTP]login...\r\n");//发送USERsprintf(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;}//发送PASSsprintf(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客户端_星沉地动的博客-CSDN博客_c语言实现ftp

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

FTP客户端搭建(linux环境)相关推荐

  1. 从零开始写项目第七篇【搭建Linux环境】

    tags: 从零开发项目, title: 从零开始写项目第七篇[搭建Linux环境] 使用SSH连接Linux环境 经过十多天的时间,我的网站备案终于完成了...接下来我就收到了阿里云的邮件.它让我在 ...

  2. 在虚拟机Virtual box搭建linux环境

    在虚拟机Virtual box搭建linux环境 虚拟机Virtual box 为什么使用Virtual box 安装Virtual box Virtual box安装CentOS Linux 下载L ...

  3. Vagrant+VirtualBox快速搭建Linux环境

    Vagrant+VirtualBox快速搭建Linux环境 虚拟机 Oracle VM VirtualBox VMware 虚拟机的选择 Vagrant介绍 Vagrant安装centos7 虚拟机固 ...

  4. Android自学笔记(番外篇):全面搭建Linux环境(一)——前期准备工作

    本系列教程旨在记录针对Linux下搭建Android开发环境的整个过程,采用[size=large][b]VMware7.1.3+Ubuntu10.04(LTS)[/b][/size]的方式搭建Lin ...

  5. wps控件在springMVC和spring-boot搭建的项目中,客户端是Linux环境下,插件的保存方法无法请求后台的原因

    一.原因: 1.wps插件的请求是跨域请求的方式,在linux环境下出于安全机制,不允许获取浏览器的session内容,所以wps插件的请求中就缺失了session信息,导致访问到后台根本没有反应. ...

  6. linux校园网客户端,Ubuntu Linux环境下校园网客户端安装使用

    最近发现好多兄弟姐妹不用Linux原因是在我们学校大大环境下上校园网有点费劲,或者干脆就不会弄,亦或者根本就没想到上网^_^ 这里我写上这么一段简单的过程,为了大家能够顺利上网,大牛直接掠过,不要喷我 ...

  7. 小码农UU手把手教你云服务器搭建linux环境,还用什么虚拟机啊

    文章目录 linux环境搭建 主要有三种方式 云服务器优点 告诫 购买服务器(我买的是腾讯云) 1. 进入官网 2.登录 3.认证购买 找到自己的IP 设置root密码 小结: 使用 XShell 远 ...

  8. Ubuntu 美化和常用环境搭建 -- Linux 环境搭建(下)

    NVIDIA显卡驱动安装 通过"附加驱动"安装驱动 通过PPA安装最新驱动 美化和扩展插件 GNOME 扩展推荐(针对 GNOME 桌面) 主题推荐(GNOME 和 Unity 桌 ...

  9. linux 下搭建portal服务器搭建,Linux环境下IBM WebSphere Portal v8.5独立服务器安装记录...

    本文用于说明在Linux环境下IBM WebSphere Portal v8.5独立服务器的安装记录: 环境说明: 硬件环境:Lenovo E440, i7, 12GB, 500GB: 虚拟设备:VM ...

  10. linux自动化安装oracle,ftp的客户端软件 Linux环境一键自动化安装oracle软件的构想(附she...

    一.自动化批量安装ORACLE软件的构想 1.1构想从哪里来? 熟悉PXE+KICKSTART一键批量安装Liunx操作系统的童鞋都知道,该方式可实现快速定制,规范化,自动化的无人值守安装.基于此方式 ...

最新文章

  1. vue+mint-ui地址三级or四级联动
  2. OPENCV图像数据类型
  3. ios查看帧率的软件_程序员必看!直播软件开发弱网下保障高清流畅推流的方法...
  4. 【学习随手记】POSIX消息队列执行报Permission denied的问题。
  5. web报表工具finereport常用函数的用法总结(数组函数)
  6. 原价399,限时1元!7天人工智能入门训练营:带你从0掌握机器学习算法!
  7. 支持MacOS 12.x的虚拟机VMware Fusion Pro for Mac
  8. phpstudy使用mysql8.0_windows系统-phpstudy升级mysql8.0.12安装教程及修改密码和安装注意事项...
  9. 中国省市县JSON字符串(String)数据、2020年的数据、2021年12月整理
  10. SQL如何本地数据库连接服务器的数据库
  11. 小米手机qq邮箱收件服务器,小米手机无法添加邮箱?是你不会设置而已
  12. android 按钮带图标 阴影_Android Material Design系列之FloatingActionButton和Snackbar
  13. Ceph管理平台Calamari的架构与功能分析
  14. python start方法_进程方法 run和start的区别
  15. 消费升级背景下零食行业发展报告_上海日报奥纬陈闻:疫情之下,“小”零食,“大”产业...
  16. 什么是WebP图片格式?如何在线把Webp格式转换为JPEG格式?
  17. 因为专注所以成功 agile新版北京发布
  18. 单片机原理与应用实验——串口(C语言),使用串口发送或接收数据,定时器1作为波特率发生器
  19. Latex 图片及表格排列代码
  20. oracle 19c_windos_64位 百度云下载。WINDOWS.X64_193000_db_home.zip。不需要积分

热门文章

  1. 浏览器是如何工作的:Chrome V8让你更懂JavaScript
  2. HaaS EDU场景式应用学习 - 光照信息屏
  3. 微软开启imap服务器,连接到 IMAP 或 SMTP 服务器
  4. 一个牛人给的java九点建议
  5. PreferenceScreen使用
  6. DMX512标准发送及代码整理
  7. 基于STM32的DMX512开发
  8. 献给初学者-DSP入门教程
  9. 问题: 在Multisim中的 Tools»Show Breadboard,我找不到面包板视图的选项。
  10. 逻辑程序设计语言Prolog