【问题标题】:Use of both easy and multi interfaces in libcurl for FTP在 libcurl 中为 FTP 使用简单和多接口
【发布时间】:2021-09-18 03:41:17
【问题描述】:

我正在编写一个程序,它可以连续递归地检查 FTP 服务器是否有新文件。当检测到文件时,将其下载。

我使用 curl 简单接口编写了所有内容,因为阻止对 curl_easy_perform() 的调用非常适合控制通道和列表操作。但是在下载文件时,多界面似乎更合适。我想过将整个东西切换到多,但目录列表变得非常复杂。

所以这是我的问题,我可以在同一个线程中同时使用简单和多接口吗?如果是这样,它们是否可以共享与服务器的相同连接?

编辑 1

不使用curl_easy_perform(),有没有办法检查单个传输状态?因此,我可以使用 curl_multi_* 接口进行所有传输,并且仅在执行后立即检查我的 LIST 命令状态。这将允许我模拟阻塞行为,而不会干扰将在其他地方处理和检查的文件传输。

据我所见,curl_multi_info_read() 不允许这样做:

当您使用此函数获取消息时,它会从内部队列中删除,因此再次调用此函数将不会再次返回相同的消息。

【问题讨论】:

    标签: c linux curl libcurl


    【解决方案1】:

    这是否回答了您的问题:

    当一个简单的句柄设置好并准备好传输时,而不是像使用简单接口传输时那样使用 curl_easy_perform,您应该使用 curl_multi_add_handle 将简单句柄添加到多句柄。您可以随时向多句柄添加更简单的句柄,即使其他传输已在运行。

    来自libcurl - multi interface overview(一个多手柄多个简单手柄)

    【讨论】:

    • 并非如此。我想要实现的是运行curl_multi_perform() 用于我的文件传输和curl_easy_perform() 用于我的控制传输(主要是LIST 命令)。我实际上需要保留curl_easy_perform() 的阻止行为以进行目录列表。
    • @agfline 但为什么呢?后续的 LIST 命令可以在之前启动的传输仍在传输时完成,对吧?
    • 是的,但我的主循环实际上是一个在 while 循环中调用的函数。此函数遍历每个 FTP 目录中的每个文件,并一次将一个文件返回给 while 循环。为此,该函数对curl_easy_perform() 进行了多次阻塞调用,以执行目录列表、检索文件大小......这就是为什么从FTP 控制通道的角度切换curl_multi_perform() 对我来说似乎有点矫枉过正。或者也许我错过了什么?
    【解决方案2】:

    我可以在同一个线程中同时使用两个接口,easy 和 multi 吗?

    绝对是,但请注意,简单 api 主要是阻塞的,而多 api 主要是非阻塞的,因此如果您将它们错误地组合在一起,您最终可能会遇到多重传输挂起/缓慢的情况,因为您的线程阻塞了 curl_easy~ 调用。

    如果可以,它们是否可以共享与服务器的相同连接?

    严格来说,是的,至少在某些情况下是这样,但你真的应该让 libcurl 担心连接重用细节,除非你处于微优化阶段(考虑到你的问题,你绝对不是)

    有没有办法检查单个传输状态

    从 curl_multi 传输列表中检查单个传输的状态? 老实说,当我使用 curl_multi 时,我通常只在它们不再活动时检查它们,正如 curl_multi_info_read() & co 所报告的那样。你可以使用自己的专用下载线程将每个传输包装在自己的对象中,并使用 CURLOPT_WRITEFUNCTION & co 跟踪每次传输,

    这个程序会输出

    transfer #1 is 4.70178% downloaded. running: true
    transfer #2 is 6.51742% downloaded. running: true
    transfer #3 is 6.14288% downloaded. running: true
    transfer #4 is 6.01199% downloaded. running: true
    transfer #0 is 12.3027% downloaded. running: true
    transfer #1 is 8.73407% downloaded. running: true
    transfer #2 is 14.0515% downloaded. running: true
    transfer #3 is 12.8638% downloaded. running: true
    transfer #4 is 11.8516% downloaded. running: true
    (...)
    transfer #0 is 94.8156% downloaded. running: true
    transfer #1 is 88.5291% downloaded. running: true
    transfer #2 is 98.8117% downloaded. running: true
    transfer #3 is 92.01% downloaded. running: true
    transfer #4 is 100% downloaded. running: false
    transfer #0 is 100% downloaded. running: false
    transfer #1 is 100% downloaded. running: false
    transfer #2 is 100% downloaded. running: false
    transfer #3 is 100% downloaded. running: false
    

    它在自己的线程中跟踪每个单独的传输,并且主线程可以通过传输[x]->状态轻松检查任何单独的传输〜

    #include <iostream>
    #include <thread>
    #include <string>
    #include <string_view>
    #include <atomic>
    #include <vector>
    #include <memory>
    
    #include <curl/curl.h>
    
    class Curl_Transfer
    {
    public:
        std::string url;
        std::string response_headers;
        std::string response_body;
        CURL *ch = nullptr;
        CURLcode curl_easy_perform_code = CURLcode(0);
        bool running = true;
        std::thread dedicated_thread;
        int64_t expected_size = 0; // << content-length reported size
    
        Curl_Transfer(std::string url) : url(url)
        {
            this->dedicated_thread = std::thread([&]() -> void
                                                 {
                                                     this->ch = curl_easy_init();
                                                     curl_easy_setopt(this->ch, CURLOPT_URL, this->url.c_str());
                                                     curl_easy_setopt(this->ch, CURLOPT_WRITEDATA,
                                                                      this);
                                                     curl_easy_setopt(this->ch, CURLOPT_HEADERDATA,
                                                                      this);
                                                     curl_easy_setopt(this->ch, CURLOPT_WRITEFUNCTION, this->WRITEFUNCTION_cb);
                                                     curl_easy_setopt(this->ch, CURLOPT_HEADERFUNCTION, this->HEADERFUNCTION_cb);
                                                     CURLcode code=curl_easy_perform(this->ch);
                                                     //std::cout << "code: " << code << std::endl;
                                                     this->curl_easy_perform_code = code;
                                                     this->running = false;
                                                 });
        }
        ~Curl_Transfer()
        {
            std::cout << "DESTRUCTING!" << std::endl;
            this->dedicated_thread.join();
            curl_easy_cleanup(this->ch);
        }
    
    private:
        // this function need to be static to be compatible with some C->C++ calling stuff... idk, but it also need access to this, so fthis=this...
        static size_t WRITEFUNCTION_cb(const char *data, size_t size, size_t nmemb, Curl_Transfer *fthis)
        {
            CURL *ch = fthis->ch;
            fthis->response_body.append(data, size * nmemb);
            //std::cout << "got body data! " << size*nmemb << "\n";
            return size * nmemb;
        };
        // this function need to be static to be compatible with some C->C++ calling stuff... idk, but it also need access to this, so fthis=this...
        static size_t HEADERFUNCTION_cb(const char *data, size_t size, size_t nmemb, Curl_Transfer *fthis)
        {
            CURL *ch = fthis->ch;
            //std::cout << "got headers! " << size*nmemb << "\n";
            fthis->response_headers.append(data, size * nmemb);
            std::string_view svd(data, size * nmemb);
            const std::string_view needle = "Content-Length: ";
            auto clp = svd.find(needle);
            if (clp != std::string::npos)
            {
                svd = svd.substr(needle.size());
                std::string fck(svd);
                fthis->expected_size = std::stoll(fck, nullptr, 0);
            }
            return size * nmemb;
        };
    };
    int main()
    {
        curl_global_init(~0); // << todo get proper constant
        std::vector<Curl_Transfer *> transfers;
        for (int i = 0; i < 5; ++i)
        {
            auto fck = new Curl_Transfer("http://speedtest.tele2.net/100MB.zip");
            transfers.push_back((fck));
        }
        for (;;)
        {
            std::this_thread::sleep_for(std::chrono::seconds(5));
            for (size_t i = 0; i < transfers.size(); ++i)
            {
                std::cout << "transfer #" << i << " is " << (double((transfers[i]->response_body.size()) / double(transfers[i]->expected_size))*100) << "% downloaded. running: " << (transfers[i]->running ? "true" : "false") << "\n";
            }
        }
    }
    
    • 可能有更好的方法来做到这一点.. 必须有。但在更聪明的人出现之前,*这至少可行......
    • 显然我做了所有的线程操作来避免使用 curl_multi api.. dafuq
    • 您使用的是 C,而不是 C++...抱歉,您也可以在 C 中完成以上所有操作,但我对 C 不太满意,无法享受用 C 重写它(任何人都可以免费如果他们愿意,可以用 C 重写代码)

    【讨论】:

    • 谢谢。当我使用curl_multi_add_handle() 将简单处理程序与多处理程序相关联时,我不能在其上使用curl_easy_perform()。当我这样做时,curl 调试会显示:easy handle 已用于多句柄。另一方面,当我不将简单处理程序与多处理程序相关联时,它可以正常工作,但是 curl 不会共享两个处理程序之间的连接并为每个处理程序打开一个新的...
    • @agfline 为什么你关心 tcp 连接重用呢? tcp 连接是否昂贵?
    • 因为我必须处理具有非常小的最大客户端(控制连接) 允许的专有服务器。当我运行我的程序时,控制通道连接显示220-You are user number 54 of 150 allowed.,然后当我开始下载文件时显示220-You are user number 12 of 150 allowed.。所以我的理解是每个连接都使用不同的控制通道,这可能是有关此服务器限制的问题。请注意,我目前没有针对生产服务器运行测试,这就是为什么它说 150 allowed
    • @agfline 我认为您应该将限制设置为最大 150 个并发下载并启用 CURLOPT_FORBID_REUSE 以确保完成的 curl 下载始终尽快关闭连接,而不是微调您的 curl 代码以重新使用连接,因此总连接数为
    猜你喜欢
    • 2016-03-10
    • 2019-06-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-16
    • 2019-04-19
    • 2011-09-18
    相关资源
    最近更新 更多