ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

libcurl-multi 的 cJSON_Parse 报错,把 Codex 的 Base URL 改到 TaoToken 通道后排查

libcurl-multi 的 cJSON_Parse 报错,把 Codex 的 Base URL 改到 TaoToken 通道后排查 从 cJSON_Parse 崩溃说起libcurl-multi 并发取 IP 定位时我把 Codex 的 Base URL 切到了 TaoToken这篇文章要解决的是一个很具体的排障场景你用 libcurl 的 multi 接口并发请求ip-api.com拿到 JSON 后交给 cJSON 解析countryCode、regionName、city、lat、lon结果程序在cJSON_Parse之后直接段错误或者parse_ip_json_str返回了 -1 却没人处理最后打印出一堆空字符串。更隐蔽的是chunk-memory这种写法——chunk本身是栈上的结构体不是指针写成chunk-memory编译器可能只给个警告运行时却读到野地址。这类问题靠肉眼盯代码很容易漏我现在的做法是先把 Codex 的 Base URL 改到 TaoToken 通道官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 拿到 Key 后让 Codex 对照 cJSON 的返回值语义把判空、释放、返回值检查逐条补上。TaoToken 在这里只负责提供 Key 和 Base URL不参与 libcurl 的请求过程也不碰 JSON 解析逻辑它解决的是我能不能稳定地把这段代码贴给模型、让它帮我查的问题。一、原问题与场景multi 并发 cJSON 解析错在哪几处先把场景还原清楚。原始代码的结构大致是mulit_get_ip_info(const char *curl_url)负责初始化CURLM、创建 easy handle、挂到 multi handle 上然后curl_multi_perform循环直到still_running 0WriteMemoryCallback把响应体追加到MemoryStruct chunk的memory缓冲区请求结束后调用cJSON_Parse(chunk-memory)再走parse_ip_json_str(root)提取字段parse_ip_json_str内部用cJSON_GetObjectItemCaseSensitive取status判断是不是success再逐个取countryCode、regionName、city、lat、lon。痛点集中在四个地方而且它们经常同时出现第一cJSON_Parse的返回值没有判空。cJSON_Parse在输入不是合法 JSON、或者缓冲区里混入了非 JSON 字节时会返回NULL。如果直接把这个NULL传给parse_ip_json_str函数内部第一句cJSON_GetObjectItemCaseSensitive(current_json_str, status)就会对空指针解引用段错误就是这么来的。第二parse_ip_json_str的返回值没有被处理。这个函数设计上返回 0 表示解析成功、-1 表示失败但调用方往往写成parse_ip_json_str(root);就完事既不判断也不打印。结果是解析失败时current_ip_info里全是初始化的 0 和空串print_ip_json_parse照样输出看起来跑通了实际数据是错的。第三chunk-memory这类指针访问写错。MemoryStruct chunk {0};声明的是结构体变量访问成员应该用chunk.memory。写成chunk-memory时编译器把chunk当成指针去解引用取到的地址完全不可控。这个错误在cJSON_Parse(chunk-memory)这一行尤其致命因为紧接着就是解析。第四WriteMemoryCallback里的realloc失败没有区分处理。char *ptr realloc(mem-memory, mem-size realsize 1);如果返回NULL原缓冲区还在但代码直接return 0curl 会认为写回调失败并中止传输。此时mem-memory仍然指向旧地址后续如果还去cJSON_Parse解析的就是不完整的 JSON。这四个问题里前两个是逻辑判空后两个是指针和内存管理。它们单独出现时可能只是数据不对叠在一起就是崩溃。我一开始想自己逐行改但parse_ip_json_str里goto end的跳转和cJSON_Delete的释放时机交织在一起改一处容易漏另一处于是决定把整段代码连同报错一起交给 Codex让它按 cJSON 的返回值约定来补。二、TaoToken 前置把 Codex 的 Base URL 指到通道上在让 Codex 看代码之前先要把它的请求出口配好。我用的方式是走 TaoToken 的兼容通道Base URL 填https://taotoken.net/apiKey 从官网创建。具体步骤打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 注册后进入控制台在 API Keys 页面创建一个 Key复制出来下面用YOUR_API_KEY代替找到 Codex 的配置文件。Codex 用的是config.toml通常在~/.codex/config.toml或者项目根目录下在config.toml里把base_url指向https://taotoken.net/api并把api_key设成你创建的 Key保存后重启 Codex或者重新加载配置。这里要强调一点TaoToken 只提供 Key 和 Base URL它不参与 libcurl 的 multi 请求也不参与 cJSON 的解析。你的程序该请求ip-api.com还是请求ip-api.comJSON 该怎么解析还是怎么解析。TaoToken 的作用是让你有一个稳定的通道把mulit_get_ip_info、WriteMemoryCallback、parse_ip_json_str和cJSON_Parse的报错一起贴给 Codex让它帮你查。如果你用的是 Claude Code配置方式不同要改的是settings.json里的ANTHROPIC_*环境变量把ANTHROPIC_BASE_URL指向https://taotoken.net/apiANTHROPIC_API_KEY填你的 Key。Codex 和 Claude Code 的配置文件不要混用一个走config.toml一个走settings.json。三、可复制配置Codex 的 config.toml 与请求侧参数先给 Codex 侧的配置。config.toml里至少要有这几项model 你的模型ID base_url https://taotoken.net/api api_key YOUR_API_KEY如果你习惯用环境变量也可以把 Key 放到OPENAI_API_KEY或对应的变量里config.toml里只留base_url。模型 ID 按你实际选用的填不要照抄别人的。再给 libcurl 请求侧的参数。原始代码里InitCurlRequest没有展开这里补一个最小可用的版本重点是CURLOPT_WRITEFUNCTION和CURLOPT_WRITEDATA要配对CURL *InitCurlRequest(const char *url, MemoryStruct *chunk) { CURL *handle curl_easy_init(); if (!handle) return NULL; curl_easy_setopt(handle, CURLOPT_URL, url); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(handle, CURLOPT_WRITEDATA, (void *)chunk); curl_easy_setopt(handle, CURLOPT_TIMEOUT, 10L); curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L); return handle; }CURLOPT_WRITEDATA传的是chunk的地址回调里userp收到的就是它然后MemoryStruct *mem (MemoryStruct *)userp;再访问mem-memory。注意这里mem是指针用-是对的而chunk是结构体变量在mulit_get_ip_info里访问它必须用chunk.memory。WriteMemoryCallback里realloc失败的处理建议改成char *ptr realloc(mem-memory, mem-size realsize 1); if (!ptr) { fprintf(stderr, realloc failed\n); return 0; }返回 0 会让 curl 中止但至少你能在 stderr 看到原因而不是静默拿到半截 JSON。四、验证请求与成功结果让 Codex 补判空和释放配置好之后把下面这段贴给 Codex让它对照 cJSON 的返回值语义来改mulit_get_ip_info的完整实现WriteMemoryCallbackparse_ip_json_str报错信息比如Segmentation fault或者parse_ip_json_str returned -1你期望的行为解析成功打印五个字段失败时打印原因并返回非 0。Codex 通常会给出几处关键修改。第一处是cJSON_Parse之后必须判空cJSON *root cJSON_Parse(chunk.memory); if (!root) { fprintf(stderr, cJSON_Parse failed, raw: %s\n, chunk.memory ? chunk.memory : (null)); goto end; } if (parse_ip_json_str(root) 0) { print_ip_json_parse(); } else { fprintf(stderr, parse_ip_json_str failed\n); } cJSON_Delete(root);注意这里把chunk-memory改成了chunk.memory并且cJSON_Delete(root)只在root非空时执行。goto end跳到清理段curl_multi_remove_handle、curl_easy_cleanup、curl_multi_cleanup、curl_global_cleanup和free(chunk.memory)都在那里顺序不能乱。第二处是parse_ip_json_str内部对status的判断。原始代码用cJSON_GetObjectItemCaseSensitive取status然后cJSON_IsString(status)和strcmp(status-valuestring, success)。这里status本身可能是NULLcJSON_IsString(NULL)返回 false短路后不会走到strcmp所以是安全的。但countryCode、regionName、city这几个字段原始代码用strncpy拷贝没有保证目标缓冲区以\0结尾。strncpy(dst, src, sizeof(dst)-1)之后应该手动补一个dst[sizeof(dst)-1] \0;否则strlen可能越界。第三处是lat和lon的判断。原始代码用current_ip_info.lat ! 0作为成功条件之一但纬度 0 是合法值赤道经度 0 也是合法值本初子午线。用! 0判断会把合法数据当成失败。更稳妥的做法是单独用一个int has_lat 0;标志取到cJSON_IsNumber就置 1。改完之后重新编译运行成功的输出应该类似current ip info: countryCode ID: CN regionName: Beijing city: Beijing lat: 39.9042 lon: 116.4074如果cJSON_Parse失败你会看到cJSON_Parse failed, raw: ...并把原始响应打出来这时就能判断是ip-api.com返回了非 JSON比如限流页面还是chunk.memory里混入了多余字节。五、本篇常见错排查错误一chunk-memory编译通过但运行崩溃。这是最典型的。MemoryStruct chunk {0};是结构体变量chunk.memory才是成员访问。写成chunk-memory时编译器把chunk的值当作地址而chunk在栈上它的值是栈上的字节内容解引用后读到的是随机地址。修复方式就是全文搜索chunk-改成chunk.。如果你确实用了MemoryStruct *chunk malloc(...)那才用-但此时chunk本身也要判空。错误二cJSON_Parse返回NULL没判直接传给parse_ip_json_str。表现是段错误栈回溯会指向cJSON_GetObjectItemCaseSensitive。修复就是在cJSON_Parse之后立刻if (!root) { ... goto end; }并且在goto end之前不要调用cJSON_Delete。错误三parse_ip_json_str返回 -1 但调用方不处理。表现是程序正常退出但打印的countryCode、regionName、city都是空串lat、lon都是 0。修复是判断返回值失败时打印status字段的内容ip-api.com在失败时会返回{status:fail,message:...}把message打出来能直接看到原因。错误四WriteMemoryCallback里realloc失败返回 0但mem-memory没置空。表现是后续cJSON_Parse解析到不完整 JSON或者free时重复释放。修复是realloc失败时保留原指针不要free也不要覆盖mem-memory直接返回 0 让 curl 中止然后在mulit_get_ip_info的清理段统一free。错误五curl_multi_perform循环里没有处理CURLM_OK以外的返回值。原始代码在res ! CURLM_OK时break但break之后直接走到解析段此时请求可能没完成chunk.memory里是半截数据。修复是break之后设置一个错误标志跳过解析直接进清理段。错误六curl_global_init和curl_global_cleanup的配对。如果你在多个线程里调用mulit_get_ip_infocurl_global_init应该只在程序启动时调用一次而不是每次进函数都调。原始代码在函数内调用curl_global_init多线程下会有竞态。修复是把curl_global_init提到main里mulit_get_ip_info里只做curl_multi_init。六、语义一致的 CTA这段 libcurl-multi 加 cJSON 的代码排查到最后其实就两类问题指针访问写错和返回值没判空。前者靠编译器警告和全文搜索能抓一部分后者必须对着 cJSON 的文档逐个函数确认返回值语义。我现在的流程是先把 Codex 的 Base URL 配到https://taotoken.net/apiKey 从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建然后把mulit_get_ip_info、WriteMemoryCallback、parse_ip_json_str和报错一起贴过去让 Codex 按 cJSON 的返回值约定补判空和释放逻辑。如果你也在做类似的接入和排障可以按这个顺序走先到 API Keys 页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 创建 Key再对照接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 把 Codex 的config.toml或 Claude Code 的settings.json配好。配好之后像cJSON_Parse判空、parse_ip_json_str返回值处理、chunk.memory指针访问这类问题都可以直接贴给模型让它逐条核对。如果你需要长期在编码和 Agent 场景里用可以看一下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 如果只是想先验证模型对话能不能通用模型对话页面https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 发一条请求即可。
返回列表