c++网络编程实战——开发基于ftp协议的文件传输模块(三) 封装自己的ftp客户端

news/2024/9/23 6:38:11/

一.前言

在前面我们已经简单介绍过FTP协议的基本概念以及我们如何配置FTP服务和一些常用的ftp命令,这篇文章主要是介绍我们如何基于开源库去封装我们自己的ftp客户端。这篇文章也可以看做一篇介绍如何基于开源库去封装自己工具库的教程。

补充: 在上一篇文章中我犯了一个小错误,就是我们云服务器开放的端口协议是ipv4,但是我在ubuntu上编写ftp的配置文件vsftpd.conf上进行监听的端口所用的协议设成了IPV6,大家要记得修改一下,不然这篇文章下的部分代码会无法运行,大家也可以在看到这篇文章是使用上一篇文章的配置文件,此时我已经对它进行了改正。

二.将开源库编译成静态/动态库

首先 我们要将开源库编译成静态/动态库,项目的链接如下:
ftplib开源库
这里我们只要取用ftplib.cftplib.h即可,由于github访问的问题,后面我会将这个作为资源上传,大家按照需求自行取用。
获取了我们想要的开源库之后我们就要着手编译了,编译的makefile编写如下:

all: libftp.a libftp.solibftp.a:_ftplib.h _ftplib.cgcc -c -o libftp.a _ftplib.clibftp.so:_ftplib.h _ftplib.cgcc -fPIC -shared -o libftp.so _ftplib.cclean:rm -f libftp.a libftp.so

这要我们就可以在我们的代码中去使用这些开源库的代码了。

这里我们简单介绍一下该开源库提供的一些功能函数以及它们功能的介绍:

GLOBALREF void FtpInit(void);  //初始化函数
GLOBALREF char *FtpLastResponse(netbuf *nControl);  //获取上一次响应的函数
GLOBALREF int FtpConnect(const char *host, netbuf **nControl);  //连接函数
GLOBALREF int FtpOptions(int opt, long val, netbuf *nControl); //设置选项函数
GLOBALREF int FtpSetCallback(const FtpCallbackOptions *opt, netbuf *nControl); //设置回调函数
GLOBALREF int FtpClearCallback(netbuf *nControl); //清除回调函数
GLOBALREF int FtpLogin(const char *user, const char *pass, netbuf *nControl); //登录函数
GLOBALREF int FtpAccess(const char *path, int typ, int mode, netbuf *nControl,netbuf **nData); //访问函数
GLOBALREF int FtpRead(void *buf, int max, netbuf *nData); 
GLOBALREF int FtpWrite(const void *buf, int len, netbuf *nData);
GLOBALREF int FtpClose(netbuf *nData);
GLOBALREF int FtpSite(const char *cmd, netbuf *nControl); //site命令
GLOBALREF int FtpSysType(char *buf, int max, netbuf *nControl); //获取系统类型
GLOBALREF int FtpMkdir(const char *path, netbuf *nControl);  //创建文件夹
GLOBALREF int FtpChdir(const char *path, netbuf *nControl); //切换目录
GLOBALREF int FtpCDUp(netbuf *nControl);  //
GLOBALREF int FtpRmdir(const char *path, netbuf *nControl);  //删除文件夹
GLOBALREF int FtpPwd(char *path, int max, netbuf *nControl);  //获取当前路径
GLOBALREF int FtpNlst(const char *output, const char *path, netbuf *nControl); //列出远程服务器指定路径下的文件名
GLOBALREF int FtpDir(const char *output, const char *path, netbuf *nControl);   //列出远程服务器指定路径下的文件名和详细信息
GLOBALREF int FtpSize(const char *path, unsigned int *size, char mode, netbuf *nControl); //查询远程文件的大小
#if defined(__UINT64_MAX)
GLOBALREF int FtpSizeLong(const char *path, fsz_t *size, char mode, netbuf *nControl); 
#endif
GLOBALREF int FtpModDate(const char *path, char *dt, int max, netbuf *nControl); //获取远程文件的最后修改日期和时间。
GLOBALREF int FtpGet(const char *output, const char *path, char mode, netbuf *nControl);//从远程服务器下载文件。
GLOBALREF int FtpPut(const char *input, const char *path, char mode,netbuf *nControl); //上传文件到远程服务器
GLOBALREF int FtpRename(const char *src, const char *dst, netbuf *nControl); //在远程服务器上重命名文件或目录。
GLOBALREF int FtpDelete(const char *fnm, netbuf *nControl); //删除远程服务器上的文件。
GLOBALREF void FtpQuit(netbuf *nControl); //断开连接

下面就要开始我们基于上述开源库的客户端的封装了。

3.ftp客户端的封装

在开始对我们封装代码具体讲解之前我们先看一下整体的框架(也就是.h文件):

//_ftp.h
// 此程序是开发框架的ftp客户端工具的类的声明文件。
#ifndef __FTP_H
#define __FTP_H#include "./_public.h"
#include "_ftplib.h"namespace idc
{class cftpclient{private:netbuf *m_ftpcoon; // ftp服务的连接句柄public:unsigned int m_msize; // 文件的大小string m_mtime;       // 最后一次进行文件修改的时间// 下面是用来存放ftp登录可能出现的问题bool m_connetfailed;                                // 网络连接失败bool m_loginfailed;                                 // 登录失败(用户名/密码错误或是没有登录权限)bool m_optionfailed;                                // 设置传输模式失败cftpclient();                                       // 构造函数~cftpclient();                                      // 析构函数cftpclient(const cftpclient &) = delete;            // 禁止拷贝构造函数cftpclient &operator=(const cftpclient &) = delete; // 禁止赋值操作符void initdata();                                    // 初始化数据// 登录ftp服务器// host:ftp服务器的ip地址与端口号,如:127.0.0.1:21// username:用户名// password:密码// fmode:传输模式,默认为被动模式bool login(const string &host, const string &username, const string &password, const int fmode =FTPLIB_PASSIVE);// 断开连接bool loginout();// 获取ftp服务器上文件的时间 返回值:false-失败;true-成功,获取到的文件大小存放在m_mtime成员变量中。bool mtime(const string &filename);// 获取ftp服务器上文件的大小//  返回值:false-失败;true-成功,获取到的文件大小存放在m_size成员变量中。bool size(const string &filename);// 修改ftp服务器的工作目录// 返回值: false-失败;true-成功bool chdir(const string &dir);// 在ftp服务器上创建目录// 返回值: false-失败;true-成功bool mkdir(const string &dir);// 删除ftp服务器上的目录// 返回值: false-失败;true-成功bool rmdir(const string &dir);// 发送NLST命令列出ftp服务器目录中的子目录名和文件名。// remotedir:ftp服务器的目录名。// listfilename:用于保存从服务器返回的目录和文件名列表。// 返回值:true-成功;false-失败。// 注意:如果列出的是ftp服务器当前目录,remotedir用"","*","."都可以,但是,不规范的ftp服务器可能有差别。bool nlst(const string &remotedir, const string &listfilename);// 从ftp服务器上获取文件。// remotefilename:待获取ftp服务器上的文件名。// localfilename:保存到本地的文件名。// bcheckmtime:文件传输完成后,是否核对远程文件传输前后的时间,保证文件的完整性。// 返回值:true-成功;false-失败。// 注意:文件在传输的过程中,采用临时文件命名的方法,即在localfilename后加".tmp",在传输// 完成后才正式改为localfilename。bool get(const string &remotefilename, const string &localfilename,const bool bcheckname = true);// 向ftp服务器发送文件。// localfilename:本地待发送的文件名。// remotefilename:发送到ftp服务器上的文件名。// bchecksize:文件传输完成后,是否核对本地文件和远程文件的大小,保证文件的完整性。// 返回值:true-成功;false-失败。// 注意:文件在传输的过程中,采用临时文件命名的方法,即在remotefilename后加".tmp",在传输// 完成后才正式改为remotefilename。bool put(const string &localfilename, const string &remotefilename,const bool bcheckname = true);//删除ftp服务器上的文件//返回值:true-成功;false-失败。bool deletefile(const string &remotefilename);//重命名ftp服务器上的文件//返回值:true-成功;false-失败。bool rename(const string &oldfilename, const string &newfilename);//向ftp服务器发送site命令//command:命令的具体内容//返回值:true-成功;false-失败。bool site(const string &command);//获取服务器返回信息的最后一条char* response();};
};#endif // __FTP_H

我们可以看到主要还是怼一些我们常用的ftp命令在ftplib.c的基础上对其进行了封装,接下来我们再来看一下它的具体实现

//_ftp.cpp
#include "_ftp.h"namespace idc{cftpclient::cftpclient(){m_ftpcoon=0;m_loginfailed=0;m_optionfailed=0;m_connetfailed=0;initdata();}cftpclient::~cftpclient(){loginout();}void cftpclient::initdata(){m_msize=0;m_mtime.clear();}bool cftpclient::login(const string& host,const string& username,const string& password,const int fmode){if(m_ftpcoon!=0){FtpQuit(m_ftpcoon);m_ftpcoon=0;}m_loginfailed=m_optionfailed=m_connetfailed=0;if(FtpConnect(host.c_str(),&m_ftpcoon)==0)  {m_connetfailed=1;return false;}if(FtpLogin(username.c_str(),password.c_str(),m_ftpcoon)==0) {m_loginfailed=1;return false;}if(FtpOptions(FTPLIB_CONNMODE,long(fmode),m_ftpcoon)==0)  {m_optionfailed=1;return false;}return true;}bool cftpclient::loginout(){if(m_ftpcoon==0){return false;}FtpQuit(m_ftpcoon);m_ftpcoon=0;return true;}bool cftpclient::mtime(const string& filename){if(m_ftpcoon==0)  return false;m_mtime.clear();string mtimestr;mtimestr.resize(14);if(FtpModDate(filename.c_str(),&mtimestr[0],14,m_ftpcoon)==0) return false;//将UTC时间转换为实际时间if(addtime(mtimestr,m_mtime,0,"yyyymmddhh24miss")==0) return false;return true;}bool cftpclient::size(const string& filename){if(m_ftpcoon==0)  return false;m_msize=0;if(FtpSize(filename.c_str(),&m_msize,FTPLIB_IDLETIME,m_ftpcoon)==0) return false;return true;}bool cftpclient::chdir(const string& dirname){if (m_ftpcoon==0)  return false;if(FtpChdir(dirname.c_str(),m_ftpcoon)==0) return false;return true;}bool cftpclient::mkdir(const string& dirname){if (m_ftpcoon==0)  return false;if(FtpMkdir(dirname.c_str(),m_ftpcoon)==0) return false;return true;}bool cftpclient::rmdir(const string& dirname){if (m_ftpcoon==0)  return false;if(FtpRmdir(dirname.c_str(),m_ftpcoon)==0) return false;return true;}bool cftpclient::nlst(const string& remotedir,const string& filenamelist){if (m_ftpcoon==0)  return false;newdir(filenamelist.c_str());if(FtpNlst(filenamelist.c_str(),remotedir.c_str(),m_ftpcoon)==0) return false;return true;}bool cftpclient::get(const string &remotefilename, const string &localfilename, bool bcheckname){if (m_ftpcoon==0) return false;newdir(localfilename);string tmpfile=localfilename+".tmp";//获取远程服务器文件的时间if(mtime(remotefilename)==0) return false;//下载文件到本地的临时文件中if(FtpGet(remotefilename.c_str(),tmpfile.c_str(),FTPLIB_IMAGE,m_ftpcoon)==0) return false;//对比文件下载前和下载后的时间,如果不同,说明文件发生了改变,终止if(bcheckname){string strtime=m_mtime;if(mtime(remotefilename)==false) return false;if(strtime!=m_mtime) return false;}//更新文件信息setmtime(remotefilename,m_mtime);//将临时文件重命名为正式文件if(rename(tmpfile.c_str(),localfilename.c_str())==0) return false;size(remotefilename);return true;}bool cftpclient::put(const string &localfilename, const string &remotefilename,const bool bcheckname){if (m_ftpcoon==0) return false;newdir(remotefilename);string tmpfile=remotefilename+".tmp";string filetime1,filetime2;//获取本地文件的时间filetime1=filemtime(localfilename,filetime1); //上传文件到远程服务器的临时文件中if(FtpPut(localfilename.c_str(),tmpfile.c_str(),FTPLIB_IMAGE,m_ftpcoon)==0) return false;//对比文件上传前和上传后的时间,如果不同,说明文件发生了改变,终止if(bcheckname){if(filemtime(remotefilename,filetime2)==false) return false;if(filetime2!=filetime1) return false;}//将临时文件重命名为正式文件if(rename(tmpfile.c_str(),remotefilename.c_str())==0) return false;if(size(localfilename)==0) return false;return true;}bool cftpclient::deletefile(const string &remotefilename){if (m_ftpcoon==0) return false;if(FtpDelete(remotefilename.c_str(),m_ftpcoon)==0) return false;return true;}bool cftpclient::rename(const string &oldfilename, const string &newfilename){if (m_ftpcoon==0) return false;if(FtpRename(oldfilename.c_str(),newfilename.c_str(),m_ftpcoon)==0) return false;return true;}bool cftpclient::site(const string &command){if (m_ftpcoon==0) return false;if(FtpSite(command.c_str(),m_ftpcoon)==0) return false;return true;}char* cftpclient::response(){if(m_ftpcoon==0) return nullptr;return FtpLastResponse(m_ftpcoon);}
};

其实绝大多数的函数都不是需要过多的解释,因为主要就是对ftplib.c这个库的调用,主要要注意的是get函数与put函数,这里我们来讲一下:

-get函数

        bool cftpclient::get(const string &remotefilename, const string &localfilename, bool bcheckname){if (m_ftpcoon==0) return false;newdir(localfilename);string tmpfile=localfilename+".tmp";//获取远程服务器文件的时间if(mtime(remotefilename)==0) return false;//下载文件到本地的临时文件中if(FtpGet(remotefilename.c_str(),tmpfile.c_str(),FTPLIB_IMAGE,m_ftpcoon)==0) return false;//对比文件下载前和下载后的时间,如果不同,说明文件发生了改变,终止if(bcheckname){string strtime=m_mtime;if(mtime(remotefilename)==false) return false;if(strtime!=m_mtime) return false;}//更新文件信息setmtime(remotefilename,m_mtime);//将临时文件重命名为正式文件if(rename(tmpfile.c_str(),localfilename.c_str())==0) return false;size(remotefilename);return true;}

get函数是从ftp服务器中下载文件,这里我们的实现思路是:

  1. 检查是否连接服务器
  2. 在本地创建文件
  3. 创建临时文件接收文件
  4. 检查文件下载是否有误
  5. 将临时文件改为正式文件

put函数是从ftp服务器中下载文件,这里我们的实现思路是:

  1. 检查是否连接服务器
  2. 服务器创建文件
  3. 创建临时文件接收文件
  4. 检查文件下载是否有误
  5. 将临时文件改为正式文件

拓展

在什么的文件中有一些函数是我自己封装的,这里就不一一展示,我和大家大概介绍一下它们的功能,大家可以尝试自己去实现一下:

  • addtime:将电脑获取的UTC时间改成指定格式的时间
  • newdir:创建指定文件/文件夹
  • setmtime:谁知文件的修改信息
  • filemtime:获取文件的最后一次修改时间

最后我还写了一个简单的测试文件,大家也可以试试或者自己写一个测试样例去试试:

#include "./_ftp.h"
using namespace idc;int main(int argc,char *argv[])
{cftpclient ftp;if(ftp.login(argv[1],argv[2],argv[3])==false){perror("ftp.login() failed.");return -1;}cout<<"ftp.login() success."<<endl;if(ftp.nlst("/public/","/tmp/list/tmp.list")==false){perror("ftp.dir() failed.");return -1;}cout<<"ftp.nlst() success."<<endl;// 在ftp服务器上创建/home/wucz/tmp,注意,如果目录已存在,会返回失败。if (ftp.mkdir("/home")==false) { printf("ftp.mkdir() failed.\n"); return -1; }// 把ftp服务器上的工作目录切换到/home/wucz/tmpif (ftp.chdir("/home")==false) { printf("ftp.chdir() failed.\n"); return -1; }return 0;
}

结语

经过上面的步骤我们就基于开源库实现了一个我们自己的ftp客户端,这篇文章一方面是写如何封装ftp客户端,另外一方面也是想介绍如何基于开源库去封装自己工具库,希望大家可以有所收获,下篇见!


http://www.ppmy.cn/news/1501323.html

相关文章

Vue3通用页面(表格,页头,分页,编辑,删除)

<script lang"ts" setup name"Canset-JDfund"> import { AddJDsetAPI, EditJDsetAPI, DelJDsetAPI, GetWLFsetListtAPI } from /api/canset/tfset // #region *****************XXXX模块**************** // #endregion const params {pageNo: 1, /…

Symfony 入门指南:快速安装与基础配置

Symfony 入门指南&#xff1a;快速安装与基础配置 Symfony 是一个强大而灵活的 PHP 框架&#xff0c;广泛应用于构建现代 Web 应用程序。本指南将带您一步一步地了解如何快速安装 Symfony&#xff0c;并完成基本配置&#xff0c;以便您能够开始使用这个强大的框架。 目录 引…

【java基础】spring springMVC springboot 的区别

Spring, Spring MVC, 和 Spring Boot 是三个紧密相关的技术&#xff0c;它们都是由 Pivotal 团队&#xff08;原SpringSource&#xff09;开发的&#xff0c;主要用于构建企业级的Java应用程序。尽管它们在功能上有所交集&#xff0c;但各自也有独特的定位和用途。 Spring Fra…

vue + xterm 前端终端terminal

引入 import {Terminal} from "xterm"; import {FitAddon} from "xterm-addon-fit"; import "xterm/css/xterm.css";html <div id"terminal"></div>vue onMounted(() > {nextTick(() > {initTerm();}) })const i…

c++中的哈希查找(Hash Search)和B树查找(B-Tree Search)

前言 hello大家好啊&#xff0c;我是文宇&#xff0c;不是文字&#xff0c;是文宇哦&#xff0c;这期也是关于查找算法的。 哈希查找&#xff08;Hash Search&#xff09; 哈希查找&#xff08;Hash Search&#xff09;是一种基于哈希表的查找算法&#xff0c;它可以在常数时…

微信小程序之调查问卷

一、设计思路 1、界面 调查问卷又称调查表&#xff0c;是以问题的形式系统地记载调查内容的一种形式。微信小程序制作的调查问卷&#xff0c;可以在短时间内快速收集反馈信息。具体效果如下所示&#xff1a; 2、思路 此调查问卷采用服务器客户端的方式进行设计&#xff0c;服…

vue3 快速入门 (五) : Flex布局

1. 如何变成Flex布局 变成Flex容器&#xff0c;只需在容器布局的节点的CSS中&#xff0c;增加display : flex .mylayout {/* 省略了其他代码 */display: flex; }2. flex direction : 方向 row : 以行排列 row-reverse &#xff1a; 以行反向排列 column &#xff1a;以列排列…

设计模式实战:电子邮件客户端的设计与实现

问题描述 设计一个电子邮件客户端,用户可以发送、接收和查看电子邮件。系统需要支持邮件通知、邮件内容的增强(如加密、签名等),并能够处理各种邮件请求(如垃圾邮件过滤、病毒扫描等)。 设计分析 观察者模式 观察者模式定义了对象间的一对多依赖关系,当一个对象的状…