在我们的项目中,数次遇到 libcurl
导致的应用程序崩溃问题,这里总结了一下使用 libcurl
的正确姿势。
1: #include <curl/curl.h> 2: #include <stdint.h> 3: #include <string.h> 4: 5: 6: #define RESPONSE_BODY_SIZE 128 7: 8: static size_t write_function(const void *buffer, const size_t size, const size_t nmemb, void *user_p) 9: { 10: char* response_body = (char*)user_p; 11: uint32_t response_body_len = strlen(response_body); 12: uint32_t len = size*nmemb; 13: if (len > RESPONSE_BODY_SIZE - response_body_len - 1) { 14: len = RESPONSE_BODY_SIZE - response_body_len - 1; 15: } 16: memcpy(response_body + response_body_len, buffer, len); 17: return size*nmemb; 18: } 19: 20: int main(int argc, char *argv[]) 21: { 22: const char* url = "http://www.example.com/dns_servers"; 23: struct curl_slist *headers = NULL; 24: headers = curl_slist_append(headers, "Content-Type: application/json"); 25: const char* request_body = "{\"host\": \"8.8.8.8\", \"port\": 53}"; 26: 27: CURL *curl; 28: CURLcode res; 29: char response_body[RESPONSE_BODY_SIZE] = {'\0'}; 30: long response_code = 0; 31: 32: curl = curl_easy_init(); 33: if(curl) { 34: curl_easy_setopt(curl, CURLOPT_URL, url); 35: curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); 36: curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request_body); 37: curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(request_body)); 38: curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); 39: curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L); 40: curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_function); 41: curl_easy_setopt(curl, CURLOPT_WRITEDATA, response_body); 42: res = curl_easy_perform(curl); 43: if (res == CURLE_OK) { 44: res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); 45: } 46: if(res != CURLE_OK) { 47: fprintf(stderr, "request to %s error(%d): %s", url, res, curl_easy_strerror(res)); 48: } 49: curl_easy_cleanup(curl); 50: } 51: 52: curl_slist_free_all(headers); 53: if (response_code == 201) { 54: fprintf(stderr, "request to %s successful: %s\n", url, response_body); 55: return 0; 56: } 57: 58: fprintf(stderr, "request to %s response failed(%ld): %s\n", url, response_code, response_body); 59: return 1; 60: }
上面的示例代码要注意的地方:
- 行 16
- buffer不是
\0
结尾的 - 行 17
- 总是返回
size*nmemb
- 行 38
总是设置这个选项
libcurl
不支持异步dns
解析时,会通过signal
的方式实现dns
解析设置超时,signal
会导致多线程程序崩溃,后台服务通常都是多线程的,所以应该总是设置这个选项(但是libcurl
不支持异步dns
解析时,超时选项将被忽略)。可以通过运行
curl --version
命令或调用curl_version
函数查看libcurl
是否支持异步dns
解析,调用curl_version_info
函数还可以获得具体的c-ares
库版本号。编译
libcurl
时,通过为configure
指定--enable-threaded-resolver
或--enable-ares
选项启用异步dns
解析。- 行 44
状态响应码变量必须是
long
类型否则会由于内存越界导致程序崩溃。
强烈建议 gcc
编译时添加 -Wall
选项, libcurl
为 gcc
提供了类型检查,能够在编译期检查一些类型不匹配的错误,如下编译输出:
install.c: In function 'install': /usr/include/curl/typecheck-gcc.h:120:36: error: call to '_curl_easy_getinfo_err_long' declared with attribute warning: curl_easy_getinfo expects a pointer to long for this info [-Werror] _curl_easy_getinfo_err_long(); \ ^ install.c:58:19: note: in expansion of macro 'curl_easy_getinfo' res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); ^ cc1: all warnings being treated as errors