最近工作中需要用代码实现从文件服务器下载文件的功能,起初使用poco库提供的FTPClientSession,代码封装完善,开发简单。但实际使用过程中,发现经常文件下载超时,且不好定位问题,所以自己使用libcurl自己手搓一个,具体实现如下:
FTPSession.h
#pragma once
#include <cstdlib>
#include <ctime>
#include <memory>
#include <string>
#include <vector>
#include "CurlHandle.h"
class FTPSession {
public:
FTPSession(const char *ip, uint16_t port, const char *username, const char *password);
~FTPSession();
bool list(const std::string &remoteFolder, std::vector<std::string> &filelist, bool bOnlyNames = true);
bool downLoadFile(const std::string &filePath, const std::string &localFolder);
bool removeFile(const std::string &filePath);
protected:
// Curl callbacks
static size_t writeInStringCallback(void *ptr, size_t size, size_t nmemb, void *data);
static size_t writeToFileCallback(void *buff, size_t size, size_t nmemb, void *data);
bool ensureDirectoryExists(std::string &filePath);
private:
std::string m_url;
std::string m_username;
std::string m_password;
CurlHandle &m_curlHandle;
};
FTPSession.cpp
#include "FTPSession.h"
#include <Poco/Exception.h>
#include <Poco/File.h>
#include <curl/curl.h>
#include <spdlog/spdlog.h>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include "Util/FileSystemHelper.h"
size_t FTPSession::writeInStringCallback(void *ptr, size_t size, size_t nmemb, void *data) {
std::string *strWriteHere = reinterpret_cast<std::string *>(data);
if (strWriteHere != nullptr) {
strWriteHere->append(reinterpret_cast<char *>(ptr), size * nmemb);
return size * nmemb;
}
return 0;
}
size_t FTPSession::writeToFileCallback(void *buff, size_t size, size_t nmemb, void *data) {
if ((size == 0) || (nmemb == 0) || ((size * nmemb) < 1) || (data == nullptr)) return 0;
std::ofstream *pFileStream = reinterpret_cast<std::ofstream *>(data);
if (pFileStream->is_open()) {
pFileStream->write(reinterpret_cast<char *>(buff), size * nmemb);
}
return size * nmemb;
}
bool FTPSession::ensureDirectoryExists(std::string &filePath) {
std::string localFolder = Util::ExtractFilePath(filePath);
try {
Poco::File path(localFolder);
if (!path.exists()) {
path.createDirectories();
}
} catch (const Poco::Exception exc) {
SPDLOG_ERROR("failed to createDirectories {}", exc.displayText());
return false;
}
return true;
}
FTPSession::FTPSession(const char *ip, uint16_t port, const char *username, const char *password)
: m_curlHandle(CurlHandle::instance()) {
m_url = "ftp://" + std::string(ip) + ":" + std::to_string(port);
m_username = username;
m_password = password;
};
FTPSession::~FTPSession(){};
bool FTPSession::list(const std::string &remoteFolder, std::vector<std::string> &filelist, bool bOnlyNames) {
if (remoteFolder.empty()) {
SPDLOG_ERROR("remoteFolder is empty.");
return false;
}
std::string temp_url = m_url + "/" + remoteFolder + "/";
bool bRet = true;
CURL *curl = curl_easy_init();
if (curl) {
std::string strList;
curl_easy_setopt(curl, CURLOPT_URL, temp_url.c_str());
curl_easy_setopt(curl, CURLOPT_USERNAME, m_username.c_str());
curl_easy_setopt(curl, CURLOPT_PASSWORD, m_password.c_str());
if (bOnlyNames) curl_easy_setopt(curl, CURLOPT_FTPLISTONLY, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeInStringCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &strList);
CURLcode res = curl_easy_perform(curl);
// Check for errors
if (res != CURLE_OK) {
bRet = false;
SPDLOG_ERROR("curl error: {}", curl_easy_strerror(res));
}
std::istringstream iss(strList);
std::string line;
while (iss >> line) {
std::cout << line << std::endl;
filelist.emplace_back(line);
}
// always cleanup
curl_easy_cleanup(curl);
}
return bRet;
}
bool FTPSession::downLoadFile(const std::string &filePath, const std::string &localFolder) {
if (filePath.empty() || localFolder.empty()) {
SPDLOG_ERROR("filePath or localFolder is empty.");
return false;
}
std::string remoteFilePath = m_url + "/" + filePath;
std::string fullLocalPath = localFolder + filePath;
if (!ensureDirectoryExists(fullLocalPath)) {
return false;
}
std::ofstream ofsOutput;
ofsOutput.open(fullLocalPath, std::ofstream::out | std::ofstream::binary | std::ofstream::trunc);
bool bRet = true;
CURL *curl = curl_easy_init();
if (curl) {
curl_easy_setopt(curl, CURLOPT_URL, remoteFilePath.c_str());
curl_easy_setopt(curl, CURLOPT_USERNAME, m_username.c_str());
curl_easy_setopt(curl, CURLOPT_PASSWORD, m_password.c_str());
curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, 0);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeToFileCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ofsOutput);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
CURLcode res = curl_easy_perform(curl);
// Check for errors
if (res != CURLE_OK) {
bRet = false;
SPDLOG_ERROR("curl error: {}", curl_easy_strerror(res));
}
// always cleanup
curl_easy_cleanup(curl);
}
return bRet;
}
bool FTPSession::removeFile(const std::string &filePath) {
if (filePath.empty()) {
SPDLOG_ERROR("filePath is empty.");
return false;
}
// Splitting file name
std::string remoteFolder;
std::string remoteFileName;
std::size_t found = filePath.find_last_of("/");
if (found != std::string::npos) {
remoteFolder = m_url + "/" + filePath.substr(0, found) + "/";
remoteFileName = filePath.substr(found + 1);
} else { // the file to be deleted is located in the root directory
remoteFolder = m_url + "/";
remoteFileName = filePath;
}
bool bRet = true;
CURL *curl = curl_easy_init();
if (curl) {
curl_easy_setopt(curl, CURLOPT_URL, remoteFolder.c_str());
curl_easy_setopt(curl, CURLOPT_USERNAME, m_username.c_str());
curl_easy_setopt(curl, CURLOPT_PASSWORD, m_password.c_str());
struct curl_slist *headerList = nullptr;
std::string cmd = "DELE " + remoteFileName;
headerList = curl_slist_append(headerList, cmd.c_str());
curl_easy_setopt(curl, CURLOPT_POSTQUOTE, headerList);
curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
curl_easy_setopt(curl, CURLOPT_HEADER, 1L);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
CURLcode res = curl_easy_perform(curl);
// Check for errors
if (res != CURLE_OK) {
bRet = false;
SPDLOG_ERROR("curl error: {}", curl_easy_strerror(res));
}
curl_slist_free_all(headerList);
// always cleanup
curl_easy_cleanup(curl);
}
return bRet;
}
CurlHandle.h
#pragma once
class CurlHandle {
public:
static CurlHandle &instance() {
static CurlHandle inst{};
return inst;
}
CurlHandle(CurlHandle const &) = delete;
CurlHandle(CurlHandle &&) = delete;
CurlHandle &operator=(CurlHandle const &) = delete;
CurlHandle &operator=(CurlHandle &&) = delete
~CurlHandle();
private:
CurlHandle();
};
CurlHandle.cpp
#include "CurlHandle.h"
#include <curl/curl.h>
#include <stdexcept>
CurlHandle::CurlHandle() {
const auto eCode = curl_global_init(CURL_GLOBAL_ALL);
if (eCode != CURLE_OK) {
throw std::runtime_error{"Error initializing libCURL"};
}
}
CurlHandle::~CurlHandle() { curl_global_cleanup(); }