Linux下使用libcurl库实现ftp上传文件

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/stayneckwind2/article/details/72614786

一、背景

FTP协议是基于TCP协议实现的文件传输协议,本文通过使用libcurl库编程来熟悉一下FTP协议。


二、相关知识

2.1 FTP(File transfer protocol)

FTP 是 TCP/IP 协议组中的协议之一,该协议是Internet文件传送的基础,它由一系列规格说明文档组成,目标是提高文件的共享性,提供非直接使用远程计算机,使存储介质对用户透明和可靠高效地传送数据。简单的说,FTP就是完成两台计算机之间的拷贝,从远程计算机拷贝文件至自己的计算机上,称之为下载(download)文件。若将文件从自己计算机中拷贝至远程计算机上,则称之为上载(upload)文件[1,2]。

FTP协议通过TCP协议实现,通过一个命令通道和数据通道完成,由于有动态协商的数据通道,所以考虑网络中的防火墙规则,在工作方式上又分为主动模式和被动模式[3]。

FTP地址格式如下:ftp://user:[email protected]/readme.txt

2.2 libcurl

libcurl库是一个实现了各种客户端协议的网络编程库。目前它支持12种以上的协议,包括 FTP、HTTP、Telnet以及其他安全变体[4]。

libcurl 库为 C 和 C++ 之类的语言添加了类似的功能,但是它可以在不同的语言之间移植。

三、实现

  源码参考 curl-7.54.0/docs/examples/ftpupload.c 进行修改;

首先封装了一个简单的结构,用于维护FTP模块的上下文,其中 ftp_host就是FTP主机地址;

typedef struct mod_ftp
{
    CURL *curl;
    char ftp_host[SIZE_NAME_LONG];
} mod_ftp_t;
    初始化函数,一个是库的初始化函数curl_global_init(),多线程小心多次调用;

    然后是申请 curl实例,最后对 ftp地址进行拼接,用户名密码不是必须的;

int mod_ftp_init(mod_ftp_t *pftp,
        const char *ftp_addr, u16 ftp_port,
        const char *username, const char *password)
{
    CURLcode ret = CURLE_FAILED_INIT;

    if ( !pftp || !ftp_addr ) {
        LOGW("NULL\n");
        return FAILURE;
    }

    if ( pftp->curl ) {
        LOGW("mod_ftp has exist\n");
        return SUCCESS;
    }

    curl_global_init(CURL_GLOBAL_DEFAULT);

    pftp->curl = curl_easy_init();
    if ( !pftp->curl ) {
        LOGW("curl_easy_init failed\n");
        return FAILURE;
    }

    snprintf(pftp->ftp_host, sizeof(pftp->ftp_host), "ftp://%s:%s@%s:%hu/",
            username, password, ftp_addr, ftp_port);
    return SUCCESS;
}

下来是文件上传函数,主要完成两部分,设置属性curl_easy_set_opt,执行动作curl_easy_perform

然后有个小细节就是上传文件时先把数据写到一个临时文件中,写完整后才把文件重命名过去,防止中间出错导致文件不完整;

int mod_ftp_upload_local_file(mod_ftp_t *pftp,
        const char *local_file, u64 filesize,
        const char *remote_tmp, const char *remote_file)
{
    CURLcode ret = CURLE_FAILED_INIT;
    struct curl_slist *headerlist = NULL;
    char ftp_rnfr[SIZE_NAME_LONG] = {0};
    char ftp_rnto[SIZE_NAME_LONG] = {0};
    char ftp_url [SIZE_NAME_LONG] = {0};
    FILE *fp = NULL;

    if ( !pftp || !local_file || !remote_tmp || !remote_file ) {
        return FAILURE;
    }

    if ( !pftp->curl ) {
        return FAILURE;
    }

    fp = fopen(local_file, "rb");
    if ( !fp ) {
        return FAILURE;
    }

    snprintf(ftp_rnfr, sizeof(ftp_rnfr), "RNFR %s", remote_tmp);
    snprintf(ftp_rnto, sizeof(ftp_rnto), "RNTO %s", remote_file);
    snprintf(ftp_url,  sizeof(ftp_url),  "%s%s", pftp->ftp_host, remote_tmp);
    
    /* Alloc and execute ftp commands after upload */
    headerlist = curl_slist_append(headerlist, ftp_rnfr);
    headerlist = curl_slist_append(headerlist, ftp_rnto);
    curl_easy_setopt(pftp->curl, CURLOPT_UPLOAD, 1L);
    curl_easy_setopt(pftp->curl, CURLOPT_URL, ftp_url);
    curl_easy_setopt(pftp->curl, CURLOPT_POSTQUOTE, headerlist);
    curl_easy_setopt(pftp->curl, CURLOPT_READDATA, fp);
    curl_easy_setopt(pftp->curl, CURLOPT_READFUNCTION, readfile_cb);
    curl_easy_setopt(pftp->curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)filesize);

    ret = curl_easy_perform(pftp->curl);
    if ( CURLE_OK != ret ) {
        LOGW("curl_easy_perform fail: %s\n", curl_easy_strerror(ret));
    }

    curl_slist_free_all(headerlist);
    curl_easy_reset(pftp->curl);
    CLOSE_FILE(fp);
    return (ret != CURLE_OK) ? FAILURE: SUCCESS;
}

去初始化函数,调用curl_easy_cleanup 释放连接;

int mod_ftp_cleanup(mod_ftp_t *pftp)
{
    if ( !pftp ) {
        LOGW("NULL\n");
        return FAILURE;
    }

    if ( pftp->curl ) {
        curl_easy_cleanup(pftp->curl);
        pftp->curl = NULL;
    }

    curl_global_cleanup();
    return SUCCESS;
}

测试程序如下:

int main(int argc, char *argv[])
{
    mod_ftp_t ftp = {0};
    mod_ftp_init(&ftp, "127.0.0.1", 21, "test01", "test01");
    mod_ftp_upload_local_file(&ftp, "/tmp/test.dat", 1024, "__tmp__.test.dat", "test.dat");
    mod_ftp_cleanup(&ftp);
    return EXIT_SUCCESS;
}

四、结果分析

使用libcurl 确实比自己基于tcp连接写ftp解析器来的方便;
通过netstat 查看网络状态,libcurl 多次执行upload 控制通道是自动复用的,只有调用了cleanup 才会关闭连接;
但是若多个upload之间碰见了FTP服务器断开你的连接(空闲剔除),libcurl并未进行及时的close套接字动作,仅在下一次perform恢复;

禁止复用可以开启选项 CURLOPT_FORBID_REUSE;
注意到一个地方就是 libcurl 都是偏向阻塞的套接字使用场景,即不好配合 I/O复用场景,更适用于多线程、多进程场景;

参考文章:
[1] https://en.wikipedia.org/wiki/File_Transfer_Protocol
[2] http://www.cnblogs.com/sunada2005/articles/2781712.html
[3] http://www.cnblogs.com/xiaohh/p/4789813.html
[4] http://www.oschina.net/question/54100_8602


 

猜你喜欢

转载自blog.csdn.net/stayneckwind2/article/details/72614786