掌握libcurl:从GET请求到HTTPS验证全解析

发布时间:2026/7/22 16:35:54
掌握libcurl:从GET请求到HTTPS验证全解析 1. 初始化 libcurl在程序开始时调用一次#includestdio.h#includecurl/curl.hintmain(void){CURLcode res;rescurl_global_init(CURL_GLOBAL_DEFAULT);if(res!CURLE_OK){fprintf(stderr,curl_global_init failed: %s\n,curl_easy_strerror(res));return1;}// 后续执行网络请求curl_global_cleanup();return0;}注意curl_global_init()通常只需要调用一次。curl_global_cleanup()应该在程序结束时调用。如果程序中有多个CURL *对象也不应该重复调用全局初始化和清理函数。2. 创建 CURL 对象CURL*curlcurl_easy_init();if(curlNULL){fprintf(stderr,curl_easy_init failed\n);curl_global_cleanup();return1;}curl_easy_init()用于创建一个 easy handle也就是一次请求的控制对象。使用完成后必须调用curl_easy_cleanup(curl);3. 设置请求 URLconstchar*urlhttps://example.com;CURLcode rescurl_easy_setopt(curl,CURLOPT_URL,url);if(res!CURLE_OK){fprintf(stderr,CURLOPT_URL failed: %s\n,curl_easy_strerror(res));}也可以直接写成curl_easy_setopt(curl,CURLOPT_URL,https://example.com);curl_easy_setopt 的返回值curl_easy_setopt的函数形式是CURLcodecurl_easy_setopt(CURL*handle,CURLoption option,...);它返回一个CURLcode用于表示设置是否成功。常见的错误检查方式#defineCHECK_CURL(expr)\do{\CURLcode code(expr);\if(code!CURLE_OK){\fprintf(stderr,%s failed: %s\n,\#expr,curl_easy_strerror(code));\}\}while(0)4. 设置响应数据的处理函数libcurl 接收到服务器响应数据后会调用回调函数。回调函数原型为size_twrite_callback(char*ptr,size_tsize,size_tnmemb,void*userdata);参数说明参数含义ptr接收到的数据size每个数据单元的大小nmemb数据单元数量userdata用户自定义参数返回值实际处理的数据字节数实际数据长度为size * nmemb重要注意ptr指向的数据不一定以\0结尾不能直接当作 C 字符串使用可能包含二进制数据回调函数返回值必须表示已经处理的字节数。5. 将响应数据写入文件下面的回调函数可以将数据写入文件#includestdio.hsize_twrite_callback(char*ptr,size_tsize,size_tnmemb,void*userdata){FILE*fp(FILE*)userdata;returnfwrite(ptr,size,nmemb,fp)*size;}设置回调函数FILE*fpfopen(response.html,wb);if(fpNULL){perror(fopen);curl_easy_cleanup(curl);curl_global_cleanup();return1;}curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,write_callback);curl_easy_setopt(curl,CURLOPT_WRITEDATA,fp);这里CURLOPT_WRITEFUNCTION用于设置响应数据回调函数CURLOPT_WRITEDATA用于设置传递给回调函数的 userdata 参数。因此下面两项是配套使用的curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,write_callback);curl_easy_setopt(curl,CURLOPT_WRITEDATA,fp);回调函数被调用时userdata fp6. 将响应数据直接输出到标准输出如果想把服务器响应直接打印到终端curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,write_callback);curl_easy_setopt(curl,CURLOPT_WRITEDATA,stdout);也可以不设置CURLOPT_WRITEFUNCTIONlibcurl 默认会把响应写到标准输出。但显式设置更容易理解和控制。7. 执行网络请求CURLcode rescurl_easy_perform(curl);if(res!CURLE_OK){fprintf(stderr,curl_easy_perform failed: %s\n,curl_easy_strerror(res));}curl_easy_perform()会同步执行请求函数调用期间会阻塞当前线程接收到响应数据时会调用CURLOPT_WRITEFUNCTION设置的回调函数请求完成后才会返回。常见错误码错误码含义CURLE_OK操作成功CURLE_UNSUPPORTED_PROTOCOL不支持的协议CURLE_COULDNT_CONNECT无法连接服务器CURLE_OPERATION_TIMEDOUT请求超时CURLE_HTTP_RETURNED_ERRORHTTP 错误通常需要配合选项CURLE_SSL_CONNECT_ERRORSSL 连接错误8. 设置常用选项设置超时时间curl_easy_setopt(curl,CURLOPT_CONNECTTIMEOUT,10L);curl_easy_setopt(curl,CURLOPT_TIMEOUT,30L);含义CURLOPT_CONNECTTIMEOUT连接服务器的最长时间CURLOPT_TIMEOUT整个请求允许的最长时间。跟随重定向服务器可能返回 301 或 302 重定向可以开启自动跟随curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,1L);设置 User-Agentcurl_easy_setopt(curl,CURLOPT_USERAGENT,my-curl-client/1.0);设置 HTTPS 证书验证libcurl 默认会验证 HTTPS 证书建议保持默认配置curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,1L);curl_easy_setopt(curl,CURLOPT_SSL_VERIFYHOST,2L);不建议在生产环境中使用CURLOPT_SSL_VERIFYPEER0CURLOPT_SSL_VERIFYHOST0关闭验证会降低 HTTPS 的安全性只适合临时测试。9. 获取 HTTP 状态码curl_easy_perform()成功只代表网络层请求成功不一定代表 HTTP 返回码是 200。例如服务器可能返回200成功404资源不存在500服务器错误可以通过curl_easy_getinfo()获取 HTTP 状态码longresponse_code0;rescurl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE,response_code);if(resCURLE_OK){printf(HTTP response code: %ld\n,response_code);}10. 一个完整的 GET 请求示例#includestdio.h#includecurl/curl.hstaticsize_twrite_callback(char*ptr,size_tsize,size_tnmemb,void*userdata){FILE*fp(FILE*)userdata;/* ptr 不一定是以 \0 结尾的字符串 必须按照 size 和 nmemb 指定的长度处理。 */returnfwrite(ptr,size,nmemb,fp)*size;}intmain(void){CURL*curlNULL;CURLcode res;longresponse_code0;rescurl_global_init(CURL_GLOBAL_DEFAULT);if(res!CURLE_OK){fprintf(stderr,curl_global_init failed: %s\n,curl_easy_strerror(res));return1;}curlcurl_easy_init();if(curlNULL){fprintf(stderr,curl_easy_init failed\n);curl_global_cleanup();return1;}FILE*fpfopen(response.html,wb);if(fpNULL){perror(fopen);curl_easy_cleanup(curl);curl_global_cleanup();return1;}rescurl_easy_setopt(curl,CURLOPT_URL,https://example.com);if(res!CURLE_OK){fprintf(stderr,set URL failed: %s\n,curl_easy_strerror(res));gotocleanup;}rescurl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,write_callback);if(res!CURLE_OK){fprintf(stderr,set write callback failed: %s\n,curl_easy_strerror(res));gotocleanup;}rescurl_easy_setopt(curl,CURLOPT_WRITEDATA,fp);if(res!CURLE_OK){fprintf(stderr,set write data failed: %s\n,curl_easy_strerror(res));gotocleanup;}curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,1L);curl_easy_setopt(curl,CURLOPT_CONNECTTIMEOUT,10L);curl_easy_setopt(curl,CURLOPT_TIMEOUT,30L);curl_easy_setopt(curl,CURLOPT_USERAGENT,my-curl-client/1.0);rescurl_easy_perform(curl);if(res!CURLE_OK){fprintf(stderr,request failed: %s\n,curl_easy_strerror(res));gotocleanup;}rescurl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE,response_code);if(resCURLE_OK){printf(HTTP response code: %ld\n,response_code);}cleanup:fclose(fp);curl_easy_cleanup(curl);curl_global_cleanup();returnresCURLE_OK?0:1;}11. 编译程序Linux 下通常需要链接 libcurlgcc main.c-omain$(pkg-config--cflags--libslibcurl)也可以直接使用gcc main.c-omain-lcurl运行./main程序会请求https://example.com并将响应内容保存到response.html12. 将响应保存到内存如果不想写入文件而是想把响应内容保存到内存可以定义一个结构体#includestdlib.h#includestring.hstructMemory{char*data;size_tsize;};回调函数staticsize_tmemory_callback(char*ptr,size_tsize,size_tnmemb,void*userdata){size_ttotalsize*nmemb;structMemory*memory(structMemory*)userdata;char*new_datarealloc(memory-data,memory-sizetotal1);if(new_dataNULL){return0;}memory-datanew_data;memcpy(memory-datamemory-size,ptr,total);memory-sizetotal;memory-data[memory-size]\0;returntotal;}使用方式structMemorymemory{.dataNULL,.size0};curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,memory_callback);curl_easy_setopt(curl,CURLOPT_WRITEDATA,memory);CURLcode rescurl_easy_perform(curl);if(resCURLE_OK){printf(received %zu bytes\n,memory.size);printf(%s\n,memory.data);}free(memory.data);如果响应内容是二进制数据不应该使用%s输出而应该根据memory.size处理fwrite(memory.data,1,memory.size,stdout);13. 资源清理顺序基本清理顺序如下fclose(fp);curl_easy_cleanup(curl);curl_global_cleanup();如果不再使用 curl可以写curl_easy_cleanup(curl);curlNULL;但需要注意curl_easy_cleanup()之后不能再使用该 handlecurl_global_cleanup()应该在所有 easy handle 都清理之后调用free()、fclose()等用户自己创建的资源也要自行释放。14. 初学者容易犯的错误错误一没有检查 curl_easy_initCURL*curlcurl_easy_init();curl_easy_setopt(curl,...);如果初始化失败curl 可能为 NULL应该先判断。错误二把回调数据当作字符串错误示例printf(%s,ptr);ptr 不一定以\0结束正确做法是fwrite(ptr,1,size*nmemb,stdout);错误三忽略回调函数返回值如果回调函数返回的数据长度少于size * nmemblibcurl 通常会认为写入失败returntotal;错误四只判断 CURLcode不判断 HTTP 状态码curl_easy_perform(curl);返回CURLE_OK只说明请求过程完成仍然应该通过curl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE,response_code);检查 HTTP 状态码。错误五忘记设置超时默认情况下某些网络问题可能导致程序等待很长时间建议至少设置curl_easy_setopt(curl,CURLOPT_CONNECTTIMEOUT,10L);curl_easy_setopt(curl,CURLOPT_TIMEOUT,30L);总结一个最基本的 libcurl 请求流程是curl_global_init(...);// 全局初始化curlcurl_easy_init();// 创建 CURL 对象curl_easy_setopt(...);// 设置 URL、回调等参数curl_easy_perform(curl);// 执行请求curl_easy_cleanup(curl);// 清理 CURL 对象curl_global_cleanup();// 全局清理响应数据处理的核心是curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,callback);curl_easy_setopt(curl,CURLOPT_WRITEDATA,userdata);其中CURLOPT_WRITEFUNCTION指定数据处理函数CURLOPT_WRITEDATA指定传给回调函数的用户参数回调函数必须按照size * nmemb处理数据返回值必须是实际处理的字节数。