一、基本使用流程

  1. 全局初始化,curl_global_init();
  2. 申请句柄,pcurl = curl_easy_init();
  3. 设置参数,curl_easy_setopt(pcurl,  …,  …);
  4. 执行请求,curl_easy_perform(pcurl);
  5. 释放句柄,curl_easy_cleanup(pcurl);
  6. 全局释放,curl_global_cleanup();

强烈建议在程序的开始处显示的调用函数curl_global_init()进行全局初始化。此函数至少需要调用一次,可多次重复调用。如果没有显示的调用,则在第一次执行curl_easy_init()时会被调用,由于curl_global_init()是非线程安全的,所以由curl_easy_init()隐式触发调用不是一个好的方式,当两个线程同时执行curl_easy_init()时可能会导致线程安全问题。

申请句柄CURL *pcurl = curl_easy_init(),所有后续操作都依赖于此句柄,特别注意句柄pcurl不可同一时刻在多个线程中同时被使用。

设置参数,多次调用函数curl_easy_setopt()设置不同类型的参数。返回数据是靠CURLOPT_WRITEFUNCTION设置回调函数和CURLOPT_WRITEDATA设置数据地址实现。CURLOPT_URL用于设置URL。

执行请求,CURLcode curlCode = curl_easy_perform(pcurl)。需要对返回值curlCode进行判断是否执行成功。

释放句柄,curl_easy_cleanup(pcurl)。

强烈建议在程序退出的地方显示的调用函数curl_global_cleanup()执行全局释放,否则可能会提示内存泄漏。

二、OpenSSL注意事项

如果以静态库方式使用了OpenSSL库,在程序退出时会提示有内存泄漏,这是因为OpenSSL分配的一些内存没有被释放,需要显示的调用OpenSSL中的接口进行释放。

OpenSSL>=1.1.0是多线程安全的,OpenSSL<=1.0.2不是多线程安全的,必须通过设置回调函数加锁才能多线程安全,参考:https://curl.haxx.se/libcurl/c/threadsafe.html。OpenSSL官方建议都升级到1.1.1版本。

这里使用的是1.0.2版本,通过加锁实现多线程安全。按如下封装全局初始化和全局释放函数,便于在程序开始和退出的地方调用。如果无法确定OpenSSL释放函数的位置,可以在OpenSSL库的所有头文件中搜索相应的函数,然后包含对应头文件即可。

CURL库全局初始化及释放接口代码如下:

// CURL库全局初始化
XHTTPLIB_API
BOOL
XHTTP_CURLGlobalInit(void);

// CURL库全局释放
// 内部对OpenSSL库进行了释放,否则会有报内存泄漏
XHTTPLIB_API
void
XHTTP_CURLGlobalCleanUp(void);

//////////////////////////////////////////////////////////////////////////
// CURL库全局初始化
XHTTPLIB_API
BOOL
XHTTP_CURLGlobalInit(void)
{
	// 初始化curl
	CURLcode curlCode = curl_global_init(CURL_GLOBAL_ALL);

	// 设置OpenSSL安全锁
	OPENSSL_ThreadSafeLockSetUp();

	// 返回初始化是否成功
	return (curlCode == CURLE_OK) ? TRUE : FALSE;
}

// CURL库全局释放
// 内部对OpenSSL库进行了释放,否则会有报内存泄漏
XHTTPLIB_API
void
XHTTP_CURLGlobalCleanUp(void)
{
	// 释放OpenSSL安全锁
	OPENSSL_ThreadSafeLockCleanUp();

	// CURL库释放
	curl_global_cleanup();

	//////////////////////////////////////////////////////////////////////////
	// OpenSSL库释放,否则会有内存泄漏
	// thread-local cleanup
	ERR_remove_state(0);

	// thread-safe cleanup
	ENGINE_cleanup();
	CONF_modules_unload(1);

	// global application exit cleanup (after all SSL activity is shutdown)
	ERR_free_strings();
	EVP_cleanup();
	CRYPTO_cleanup_all_ex_data();
}

OpenSSL加锁及释放接口代码如下:

// 设置OpenSSL线程安全锁
BOOL OPENSSL_ThreadSafeLockSetUp(void);

// 清除OpenSSL线程安全锁
BOOL OPENSSL_ThreadSafeLockCleanUp(void);

//////////////////////////////////////////////////////////////////////////
#define MUTEX_TYPE            HANDLE
#define MUTEX_SETUP(x)        (x) = ::CreateMutex(NULL, FALSE, NULL)
#define MUTEX_CLEANUP(x)      ::CloseHandle(x)
#define MUTEX_LOCK(x)         ::WaitForSingleObject((x), INFINITE)
#define MUTEX_UNLOCK(x)       ::ReleaseMutex(x)
#define THREAD_ID             ::GetCurrentThreadId()

// This array will store all of the mutexes available to OpenSSL.
static MUTEX_TYPE *mutex_buf = NULL;

static void locking_function(int mode, int n, const char *file, int line)
{
	// 未申请
	if (mutex_buf == NULL)
	{
		return;
	}

	if(mode & CRYPTO_LOCK)
	{
		MUTEX_LOCK(mutex_buf[n]);
	}
	else
	{
		MUTEX_UNLOCK(mutex_buf[n]);
	}
}

static unsigned long id_function(void)
{
	return ((unsigned long)THREAD_ID);
}

// 设置OpenSSL线程安全锁
BOOL OPENSSL_ThreadSafeLockSetUp(void)
{
	// 已申请则不再处理
	if (mutex_buf != NULL)
	{
		return TRUE;
	}

	// 申请内存
	mutex_buf = (MUTEX_TYPE*)malloc(CRYPTO_num_locks() * sizeof(MUTEX_TYPE));
	if (mutex_buf == NULL)
	{
		return FALSE;
	}
	
	// 创建锁
	for(int i=0; i<CRYPTO_num_locks(); i++)
	{
		MUTEX_SETUP(mutex_buf[i]);
	}
	
	// 设置回调ID及锁回调函数
	CRYPTO_set_id_callback(id_function);
	CRYPTO_set_locking_callback(locking_function);

	// 设置成功
	return TRUE;
}

// 清除OpenSSL线程安全锁
BOOL OPENSSL_ThreadSafeLockCleanUp(void)
{
	// 未申请内存则不处理
	if (mutex_buf == NULL)
	{
		return FALSE;
	}
	
	// 清除设置的回调
	CRYPTO_set_id_callback(NULL);
	CRYPTO_set_locking_callback(NULL);

	// 释放锁
	for(int i=0; i<CRYPTO_num_locks(); i++)
	{
		MUTEX_CLEANUP(mutex_buf[i]);
	}

	// 释放内存
	free(mutex_buf);
	mutex_buf = NULL;

	// 清理成功
	return TRUE;
}