libcurl使用时疑难问题【如:文件下载】

本文介绍了一种使用libcurl进行文件下载的优化方案,通过设置超时时间和速度限制避免网络异常时的程序阻塞,同时提供了详细的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

场景:

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);接口时被阻塞,不返回;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值