最近业余时间在搞Qt,其中的一个功能是实现FTP的上传下载。
之前版本的Qt提供了一个FTP操作的类,但是5.x(4.x?)之后因为性能问题就弃用了。貌似CSDN上有人发帖问过这个问题,记得应该是put多大的文件时导致占用内存过大。现在Qt的官方手册推荐使用QNetworkAccessManager用于TCP/IP以及FTP的传输。说实话这玩意对于FTP的兼容并不好。于是想到了CURL。
网上关于CURL和libCURL的文章很多,但是用于FTP操作的文章大多数停留于试验性的代码。别的不说,忽略了一个比较重要的问题就是FTP的传输模式。
FTP有两种传输模式,ASCII和Binary,即TYPE A 和TYPE I。大家的文章里面都没有提到这个事情。我用libCURL主要是传输文本文件,所以这个问题一下就浮现出来了。
libCURL的下载什么的就不说了,到官网上找windows的包就可以,记得下载develop版本。
在QT里面使用动态库,首先要做的肯定是修改pro文件。
1 LIBS += C:\your-curl-lib-location\curl-7.34.0-devel-mingw32\lib\libcurldll.a 2 3 INCLUDEPATH += C:\your-curl-lib-location\curl-7.34.0-devel-mingw32\include
首先指定静态库位置,接着指定include路径。这个没什么好说的。然后把curl路径下的bin目录中的dll拷贝到debug文件夹。否则run的时候会“莫名其妙”的停止。
这里使用的是CURL的easy借口(貌似网上的文章都是使用的这个)。
先来看下下载代码。
1 CURLcode ret; 2 curl_global_init(CURL_GLOBAL_ALL); 3 CURL* curl = curl_easy_init(); 4 5 curl_easy_setopt(curl,CURLOPT_URL,remoteFile.toLatin1().data()); 6 curl_easy_setopt(curl,CURLOPT_USERPWD,"username:password"); 7 ret = curl_easy_setopt(curl,CURLOPT_WRITEDATA,localFile); 8 ret = curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,curlWriteFunction); 9 10 curl_easy_setopt(curl,CURLOPT_TRANSFERTEXT,1); 11 12 curl_easy_setopt(curl,CURLOPT_VERBOSE,1); 13 ret = curl_easy_perform(curl); 14 15 curl_easy_cleanup(curl); 16 curl_global_cleanup();
解释几点。
1) 声明CURLcode,获取curl接口的返回值。
2) 使用 curl_global_init对CURL进行初始化。传递CURL_GLOBAL_ALL进去表明对所有平台进行初始化,这个是最保险的,也保证了代码的可移植性。
3) curl_easy_setopt函数对curl的选项进行设定。
该函数的第一个参数为curl指针,第二个为CURL选项名称(其实就是个整数),第三个为实际选项的值,可以是字符串、指针,函数指针等,根据不同的选项有不同的值。
CURLOPT_URL: 设定需要上传/下载的文件URL。
CURLOPT_USERPWD:设定登录服务器的账号密码,格式为user:passwd。
CURLOPT_WRITEDATA:传递DATA的指针。引用一句被用烂的话,“CURL对该指针不做任何处理,只做传递作用。”。这个指针有什么用,我们一会儿再看。
CURLOPT_WRITEFUNCTION:设定写文件的函数。当curl从服务器获取数据之后,便会调用这个函数做写入处理。函数原型如下:
size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
看见最后一个参数了米有?这个参数就是CURLOPT_WRITEDATA中指定的指针。也就是,如果你想指定几个参数贯穿CURL“获取-写入"这个过程,就把他们的指针通过CURLOPT_WRITEDATA传给CURL。这里我给了个QFile指针进去。
需要说明的一点是,如果在C++中只用CURL,这个函数在类中应当被声明为static的。
CURLOPT_TRANSFERTEXT: 指定CURL的FTP传输模式。后面的值为1,则为ASCII,0位二进制传输。如果使用了错误的传输模式,在下载的过程中,服务器可能给你一个553。最开始这个错误让我很莫名其妙。因为我使用了CURLOPT_PREQUOTE去追加了一个TYPE A的命令给CURL。理论上应该是可行的,但实际上文件传输总是到一半就被服务器关闭链接。猜测这个可能与CURL的多线程有关?仅仅做猜测了。
CURLOPT_VERBOSE 设定调试级别。
在libCURL的网站上,有一个链接专门解释这些宏。
1 http://curl.haxx.se/libcurl/c/curl_easy_setopt.html
4) curl_easy_perform 执行curl动作。默认为下载。
对CURL设置结束后,就可以编写之前指定的写入函数了。
1 int FTPThread::curlWriteFunction(void *buffer, size_t size, size_t nmemb, void *stream){ 2 QFile* file = static_cast<QFile*>(stream); 3 if(!file->isOpen()){ 4 file->open(QFile::ReadWrite | QIODevice::Text); 5 } 6 return file->write((char*)buffer,nmemb); 7 8 }
在QT中,这个函数是很容易被实现的。只要确保返回值为实际的写入字节就可以,因为CURL会检查返回值,如果与nmemb不相等的话,会报错。
上传的代码与下载很类似,直接上代码了。
1 CURLcode FTPThread::CURLUpload(){ 2 CURLcode ret; 3 curl_global_init(CURL_GLOBAL_ALL); 4 CURL* curl = curl_easy_init(); 5 QFile* localFile = new QFile("FTPCache/"+fileName); 6 qDebug()<<localFile->fileName()<<endl; 7 QString remoteFile = "ftp://your.ftp.server/"+fileName; 8 9 if(!localFile->open(QFile::ReadWrite | QIODevice::Text)){ 10 qDebug()<<"open file fail"<<endl; 11 return CURLE_READ_ERROR; 12 } 13 14 curl_easy_setopt(curl,CURLOPT_URL,remoteFile.toLatin1().data()); 15 curl_easy_setopt(curl,CURLOPT_USERPWD,"username:password"); 16 17 curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); 18 curl_easy_setopt(curl,CURLOPT_FTP_CREATE_MISSING_DIRS,CURLFTP_CREATE_DIR); 19 ret = curl_easy_setopt(curl,CURLOPT_READDATA,localFile); 20 curl_easy_setopt(curl,CURLOPT_INFILESIZE_LARGE,localFile->size()); 21 ret = curl_easy_setopt(curl,CURLOPT_READFUNCTION,curlReadFunction); 22 23 //Timeout 24 curl_easy_setopt(curl,CURLOPT_FTP_RESPONSE_TIMEOUT,60000); 25 26 27 //Set transfer mode to ASCII 28 curl_easy_setopt(curl,CURLOPT_TRANSFERTEXT,1); 29 curl_easy_setopt(curl,CURLOPT_BUFFERSIZE,200); 30 31 32 curl_easy_setopt(curl,CURLOPT_VERBOSE,1); 33 ret = curl_easy_perform(curl); 34 if(ret!=CURLE_OK) 35 qDebug()<<"perform fail"<<endl; 36 37 curl_easy_cleanup(curl); 38 curl_global_cleanup(); 39 localFile->close(); 40 return ret; 41 }
只需说明一点,curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); 指定了CURL的动作为上传。
读取文件的函数原型如下:
1 size_t read_callback(char *buffer, size_t size, size_t nitems, void *instream);
实现如下:
1 int FTPThread::curlReadFunction(void *buffer, size_t size, size_t nmemb, void *stream){ 2 QFile* file = static_cast<QFile*>(stream); 3 return file->read((char*)buffer,size*nmemb); 4 }
全文完。