场景:
1. 下载过程中,遇设备突然断网,在使用libcurl提供的API时,出现阻塞不返回的情况,影响了后续的业务。
问题:
curl_easy_perform是阻塞的方式进行下载的, curl_easy_perform执行后,程序会在这里阻塞等待下载结束(成功结束或者失败结束).此时若正常下载一段时间后,进行网络中断, curl_easy_perform并不会返回失败,而是阻塞整个程序卡在这里,此时即使网络连接重新恢复, curl_easy_perform也无法恢复继续下载,导致整个程序出现”死机”状态.
之前提供的解决办法:
2.1在下载中,另起一个线程,若发现下载状态卡死(可以通过定期检查文件大小来实现),则从外部中断下载线程.此方法需另起线程,而且直接中断线程,会给整个程序带来不稳定.
2.2.下载过程中,设置超时时间为30秒, 30秒后若下载未完成就重新连接进行下载.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3、我的解决办法(实际代码):封装libcurl,设置超时时间,以及必要的变量控制
需要注意的设置项
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L); //timeout for the connect phase
/* abort if slower than 1 bytes/sec during 6 seconds */
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1);
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 6);
头文件:
typedef struct http_set_opt
{
int timeout;
const char *ca_file;
const char *ca_path;
const char *content_type;
const char *digest_value;
const char *token;
void *write_func;
void *write_data;
void *progress_func;
void *progress_data;
const char *upload_file_path;
void *read_func;
void *read_data;
int read_data_size;
}http_set_opt_t;
typedef int(*download_file_progress_cbk)(void *lpUserData, int64_t dltotal, int64_t dlnow);
typedef struct http_download_param
{
http_set_opt_t * http_set_opt;
/*
* request
*/
const char * serv_url;
const char * store_path;
/*
* callback
*/
download_file_progress_cbk progress_cbk;
void * progress_cbk_userdata;
/*
* attribute
*/
int running; // abort: < 0, running: 1
/*
* defaults
*/
char defaults[16];
}http_download_param_t;
/*
接口
*/
OpenSDK_API int Open_http_client_download_file(http_download_param_t*download_param);
/*
回调
*/
typedef struct
{
int(*Open_http_client_download_file)(http_download_param_t *download_param);
}http_client_interface;
CPP文件:
#include <curl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
static size_t download_write_func(char *ptr, size_t size, size_t nmemb, void *userdata)
{
FILE *fp = (FILE *)userdata;
if(fwrite(ptr, size, nmemb, fp) != (size*nmemb))
{
fprintf(stderr, "file:<%s> func:<%s> line:<%d>: fwrite failed\n", __FILE__, __FUNCTION__, __LINE__);
}
return (size*nmemb);
}
/*
回调
*/
static int http_client_download_file_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
{
http_download_param_t *download_param = (http_download_param_t *)clientp;
if (download_param == NULL || download_param->running < 0) //特别注意running的使用,可以快速返回
return -1;
if (download_param != NULL && download_param->progress_cbk != NULL){
int64_t totalSize = (int64_t)dltotal;
int64_t downloadedSize = (int64_t)dlnow;
download_param->progress_cbk(download_param->progress_cbk_userdata, totalSize, downloadedSize);
}
return 0;
}
/*
实现
*/
int Open_http_client_download_file(http_download_param_t *download_param)
{
if (download_param->serv_url == NULL || download_param->store_path == NULL)
{
fprintf(stderr, "file:<%s> func:<%s> line:<%d>: argument for sky_http_client_download_file should not be NULL\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
if (download_param->progress_cbk == NULL)
{
fprintf(stderr, "file:<%s> func:<%s> line:<%d>: download progress callback func should not be NULL\n", __FILE__, __FUNCTION__, __LINE__);
return -1;
}
long local_file_length = 0;// get_localfile_length(download_param->store_path);
//fprintf(stderr, "local file length is: %ld\n", local_file_length);
FILE *fp = fopen(download_param->store_path, "wb");
if (fp == NULL)
{
fprintf(stderr, "file:<%s> func:<%s> line:<%d>: can not open %s\n", __FILE__, __FUNCTION__, __LINE__, download_param->store_path);
return -1;
}
CURL *curl = curl_easy_init();
if (curl == NULL) {
fprintf(stderr, "file:<%s> func:<%s> line:<%d>: curl_easy_init failed\n", __FILE__, __FUNCTION__, __LINE__);
fclose(fp);
return -1;
}
download_param->running = 1; //注意赋值
/* 1: set http server url */
curl_easy_setopt(curl, CURLOPT_URL, download_param->serv_url);
/* 2:set debug opt */
#if HTTP_DEBUG
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, http_debug);
#endif
/* 3: set http request method */
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, download_write_func);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, http_client_download_file_progress);
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, download_param);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
// curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE, local_file_length);
/* abort if slower than 1 bytes/sec during 6 seconds */
/*
注意该此处的设置,断网等情况下的死锁
*/
//timeout for the connect phase
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
/* abort if slower than 1 bytes/sec during 6 seconds */
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1L);
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 6L);
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1L);
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 6L);
if (download_param->http_set_opt != NULL)
{
/* 4:set time out by user */
if (download_param->http_set_opt->timeout)
{
curl_easy_setopt(curl, CURLOPT_TIMEOUT, download_param->http_set_opt->timeout);
}
/* 5:set CA */
if (download_param->http_set_opt->ca_file)
{
curl_easy_setopt(curl, CURLOPT_CAINFO, download_param->http_set_opt->ca_file);
}
if (download_param->http_set_opt->ca_path)
{
curl_easy_setopt(curl, CURLOPT_CAPATH, download_param->http_set_opt->ca_path);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
}
}
else
{
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
}
/* 6:set no signal */
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
CURLcode ret_code = curl_easy_perform(curl);
if (ret_code != CURLE_OK)
{
fprintf(stderr, "file:<%s> func:<%s> line:<%d>: curl_easy_perform failed, error code: %d\n", __FILE__, __FUNCTION__, __LINE__, ret_code);
//LOG_WRITE(LOG_LEVEL_ERROR, "curl_easy_perform failed, error code: %d", ret_code);
if (ret_code == CURLE_COULDNT_CONNECT || ret_code == CURLE_COULDNT_RESOLVE_HOST || ret_code == CURLE_COULDNT_RESOLVE_PROXY)
{
fprintf(stderr, "file:<%s> func:<%s> line:<%d>: network problem\n", __FILE__, __FUNCTION__, __LINE__);
}
else if (ret_code == CURLE_OPERATION_TIMEDOUT){
fprintf(stderr, "file:<%s> func:<%s> line:<%d>: curl_easy_perform time out\n", __FILE__, __FUNCTION__, __LINE__);
}
else if (ret_code == CURLE_SSL_CONNECT_ERROR) {
fprintf(stderr, "file:<%s> func:<%s> line:<%d>: SSL connect error\n", __FILE__, __FUNCTION__, __LINE__);
}
else if (ret_code == CURLE_SSL_CACERT) {
fprintf(stderr, "file:<%s> func:<%s> line:<%d>: CA file error\n", __FILE__, __FUNCTION__, __LINE__);
}
curl_easy_cleanup(curl);
fclose(fp);
return -1;
}
curl_easy_cleanup(curl);
fclose(fp);
return 0;
}
以上为封装的文件下载过程,应用层在使用该接口的时候,需要根据实际的情况,给 http_download_param_t http_download对象先初始化,memset(&http_download, 0, sizeof(http_download)),在具体的情境下赋值,比如上层的业务突然关闭或者停止,需要将 http_download.running = -1; 这样就内部就能及时地返回结束,而不会使得上层调用 int result= Open_http_client_download_file(&http_download);接口时被阻塞,不返回;