网络篇,书上672~674页,带进度的FTP下载的的实例程序,写了两遍,才写成功,而且第二遍写的时候,对易错的地方有所感悟,写篇心得记下来。
首先上代码:
#include <curl/curl.h>
#include <iostream>
#include <fstream>
#include <sstream> //stringstream
using namespace std;
int to_size(char* data, size_t size, size_t nmemb, void* userdata)
{
int result_code = 0;
string s(data, size*nmemb);
stringstream ss(s);
ss >> result_code;
if(!ss.bad() && result_code == 213)
{
int* pcode = static_cast <int*> (userdata);
ss >> *pcode;
}
return nmemb*size;
}
int to_stream(char* data, size_t size, size_t nmemb, void* userdata)
{
ostream& os = *static_cast <ostream*> (userdata);
std::string line(data, size*nmemb);
os << line;
return size*nmemb;
}
//当需要通知进度时,回调
int down_progress(void*
, curl_off_t dltotal, curl_off_t dlnow
, curl_off_t ultotal, curl_off_t ulnow)
{
if(dltotal == 0)
return 0;
int count = (dlnow * 1.0 / dltotal) * 50; //确定需要画多少个等号
cout << (dlnow * 100 / dltotal) << "%"; //确定百分比
for(int i = 0; i < count; ++i)
{
cout << '=';
}
cout << endl;
return 0;
}
//取FTP服务器指定文件的大小
int get_server_file_size(string const& server_url
, string const& username
, string const& password
, string const& pathfile)
{
CURL* handle = curl_easy_init();
curl_easy_setopt(handle, CURLOPT_URL, server_url.c_str());
//username和password也需要C形式的字符串
curl_easy_setopt(handle, CURLOPT_USERNAME, username.c_str());
curl_easy_setopt(handle, CURLOPT_PASSWORD, password.c_str());
string cmd = "SIZE " + pathfile; //SIZE后面要有个分号
curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, cmd.c_str());
int filesize = 0;
curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, to_size);
curl_easy_setopt(handle, CURLOPT_HEADERDATA, static_cast <void*> (&filesize));
curl_easy_perform(handle);
cout << "filesize = " << filesize << endl;
curl_easy_cleanup(handle);
return filesize;
}
int main()
{
curl_global_init(CURL_GLOBAL_DEFAULT);
CURL* handle = curl_easy_init();
ofstream ofs("a.zip", ios_base::out | ios_base::binary);
if(!ofs)
{
cerr << "无法打开本地文件a.zip。" << endl;
return -1;
}
string server_url = "ftp://127.0.0.1:21/";
string pathfile = "fengjie/meili/2.zip";
string username = "d2school";
string password = "123456";
//取服务端指定文件大小
size_t file_size = get_server_file_size(server_url, username, password, pathfile);
//告诉libcurl待下载文件的总大小
curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE, static_cast <curl_off_t> (file_size));
//开启进度通知
curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, down_progress);
//设置如何处理下载的数据
string url = server_url + pathfile;
curl_easy_setopt(handle, CURLOPT_URL, url.c_str());
// //username和password也需要C形式的字符串
curl_easy_setopt(handle, CURLOPT_USERNAME, username.c_str());
curl_easy_setopt(handle, CURLOPT_PASSWORD, password.c_str());
//本次下载采用直接定位到文件的方式,类似于http协议的下载,不需要使用ftp命令: RETR 文件名
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, to_stream);
curl_easy_setopt(handle, CURLOPT_WRITEDATA, static_cast <void*> (&ofs));
curl_easy_perform(handle);//启动下载
ofs.close();//关闭流
curl_easy_cleanup(handle);
curl_global_cleanup();
return 0;
}
易错点分析:
65行,要注意SIZE后面要有个分号
62,63, 107,108行,要注意username和password不要忘了使用c_str()转化成“C”形式的字符串
110行,本次下载采用直接定位到文件的方式,类似于http协议的下载,不需要使用ftp命令: RETR 文件名
116行,关闭流,这个容易遗忘。不过这一行即使遗忘了,应该也不会有问题,因为ofs是栈变量,会自动回收内存。
写的过程要注意:不要一口气把整个程序写完,否则出了错,会花费老大劲去寻找错误。
首先,main函数中,写到96行,要测试一下,看看能不能得到文件的大小,如果不能得到,则停下来,排查错误。把错误解决完,再继续往下写。
接下来,先写104-112行的内容,看看能否把文件下载下来,若不能,则同样排查错误。把错误解决完,再继续写98-101行的内容,把下载进度通知加上。