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

一.前言

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

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

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

首先 我们要将开源库编译成静态/动态库,项目的链接如下:
ftplib开源库

这里我们只要取用ftplib.cftplib.h即可,由于github访问的问题,后面我会将这个作为资源上传,大家按照需求自行取用。

获取了我们想要的开源库之后我们就要着手编译了,编译的makefile编写如下:

makefile 复制代码
all: libftp.a libftp.so

libftp.a:_ftplib.h _ftplib.c
	gcc -c -o libftp.a _ftplib.c

libftp.so:_ftplib.h _ftplib.c
	gcc -fPIC -shared -o libftp.so _ftplib.c

clean:
	rm -f libftp.a libftp.so

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

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

c 复制代码
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文件):

cpp 复制代码
//_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的基础上对其进行了封装,接下来我们再来看一下它的具体实现

cpp 复制代码
//_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函数

cpp 复制代码
        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:获取文件的最后一次修改时间

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

cpp 复制代码
#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/tmp
    if (ftp.chdir("/home")==false) { printf("ftp.chdir() failed.\n"); return -1; }

    return 0;
}

结语

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

相关推荐
.生产的驴几秒前
SpringBoot 消息队列RabbitMQ 消费者确认机制 失败重试机制
java·spring boot·分布式·后端·rabbitmq·java-rabbitmq
Code哈哈笑18 分钟前
【C++ 学习】多态的基础和原理(10)
java·c++·学习
Aurora_th25 分钟前
树与图的深度优先遍历(dfs的图论中的应用)
c++·算法·深度优先·图论·dfs·树的直径
神秘的土鸡1 小时前
Linux中使用Docker容器构建Tomcat容器完整教程
linux·运维·服务器·docker·容器·tomcat
苹果酱05671 小时前
一文读懂SpringCLoud
java·开发语言·spring boot·后端·中间件
shuxianshrng1 小时前
大气网格化精细化监管监测系统
大数据·服务器·windows·经验分享
2401_847056551 小时前
Altium Designer脚本工具定制
网络·数据库
qmx_071 小时前
MFC-基础架构
c++·mfc
万象.1 小时前
STL_vector实现及干货以及vector迭代器失效问题
c++
想变成自大狂1 小时前
C++中的异构容器
开发语言·c++