如何用C++和libcurl向ONVIF网络摄像机发送SOAP请求并获取响应?
问题:C++ 结合libcurl向ONVIF摄像机发送SOAP请求返回空响应
我尝试用C++和libcurl向ONVIF网络摄像机发送SOAP请求,实现代码如下:
#include <iostream> #include <stdlib.h> #include <string.h> #include <curl/curl.h> struct MemoryStruct { char *memory; size_t size; }; int main() { std::string pstFld = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:tds=\"http://www.onvif.org/ver10/device/wsdl\"><SOAP-ENV:Body><tds:GetSystemDateAndTime/></SOAP-ENV:Body></SOAP-ENV:Envelope>"; CURL *curl; CURLcode res; struct MemoryStruct chunk; chunk.memory = (char *) malloc(1); chunk.size = 0; curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); if (curl) { struct curl_slist *slist = NULL; slist = curl_slist_append(slist, "Host: my_ip_address"); slist = curl_slist_append(slist, "Content-Type: text/xml; charset=utf-8"); std::string contentLength = "Content-Length: " + std::to_string(pstFld.size()); slist = curl_slist_append(slist, contentLength.c_str()); curl_easy_setopt(curl, CURLOPT_URL, "http://my_ip_address/onvif/device_service"); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); curl_easy_setopt(curl, CURLOPT_POST, true); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, pstFld); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &chunk); curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0"); res = curl_easy_perform(curl); if (res != CURLE_OK) { fprintf(stderr, "Failed: %s\n", curl_easy_strerror(res)); } else { printf("%lu bytes retrieved.\n", (unsigned long) chunk.size); } curl_easy_cleanup(curl); curl = NULL; } free(chunk.memory); curl_global_cleanup(); return 0; }
执行后收到错误:
* upload completely sent off: 6 out of 6 bytes * Empty reply from server Failed: Server returned nothing (no headers, no data)
注:用PHP CURL向同一设备发送相同SOAP请求能正常获取XML响应,但C++实现仅能发送请求却收不到响应。
解决方案
核心问题分析
代码存在几个关键问题导致无法接收响应:
- 未实现
WriteMemoryCallback函数:设置了CURLOPT_WRITEFUNCTION但未提供具体实现,curl无法将服务器响应写入内存缓冲区。 CURLOPT_POSTFIELDS参数错误:直接传入std::string对象而非其C风格字符串指针,可能导致curl无法正确读取POST内容长度。- 手动设置
Content-Length易出错:手动计算的长度可能与实际发送的字节数不符,建议交给curl自动处理。
修改后的完整代码
#include <iostream> #include <stdlib.h> #include <string.h> #include <curl/curl.h> struct MemoryStruct { char *memory; size_t size; }; // 实现WriteMemoryCallback函数,用于将响应写入内存缓冲区 static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; struct MemoryStruct *mem = (struct MemoryStruct *)userp; char *ptr = (char *)realloc(mem->memory, mem->size + realsize + 1); if(!ptr) { // 内存分配失败 fprintf(stderr, "realloc failed\n"); return 0; } mem->memory = ptr; memcpy(&(mem->memory[mem->size]), contents, realsize); mem->size += realsize; mem->memory[mem->size] = 0; // 确保字符串以NULL结尾 return realsize; } int main() { std::string pstFld = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:tds=\"http://www.onvif.org/ver10/device/wsdl\"><SOAP-ENV:Body><tds:GetSystemDateAndTime/></SOAP-ENV:Body></SOAP-ENV:Envelope>"; CURL *curl; CURLcode res; struct MemoryStruct chunk; chunk.memory = (char *)malloc(1); chunk.size = 0; curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init(); if (curl) { struct curl_slist *slist = NULL; slist = curl_slist_append(slist, "Host: my_ip_address"); // 替换为设备实际IP slist = curl_slist_append(slist, "Content-Type: text/xml; charset=utf-8"); // 移除手动设置的Content-Length,由curl自动计算 curl_easy_setopt(curl, CURLOPT_URL, "http://my_ip_address/onvif/device_service"); // 替换为设备实际地址 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); curl_easy_setopt(curl, CURLOPT_POST, true); // 传入std::string的C风格字符串指针 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, pstFld.c_str()); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk); curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0"); res = curl_easy_perform(curl); if (res != CURLE_OK) { fprintf(stderr, "Failed: %s\n", curl_easy_strerror(res)); } else { printf("%lu bytes retrieved.\n", (unsigned long)chunk.size); // 打印响应内容 if(chunk.size > 0) { printf("Response:\n%s\n", chunk.memory); } } // 清理HTTP头列表 curl_slist_free_all(slist); curl_easy_cleanup(curl); } free(chunk.memory); curl_global_cleanup(); return 0; }
关键修改点说明
- 添加
WriteMemoryCallback实现:这是curl接收响应的核心函数,负责动态扩容缓冲区并写入响应数据。 - 修正
CURLOPT_POSTFIELDS参数:使用pstFld.c_str()获取C风格字符串指针,确保curl正确读取POST内容。 - 移除手动
Content-Length设置:curl会自动计算并设置该头,避免手动计算的误差。 - 添加
curl_slist_free_all(slist):释放HTTP头列表的内存,避免内存泄漏。 - 新增响应内容打印:方便查看服务器返回的XML数据。
内容的提问来源于stack exchange,提问作者O Connor
相关产品推荐
相关产品推荐

