这是一个非常具体且常见的需求,尤其是在高性能、低延迟的场景下,比如日志处理、数据分析管道等,C 语言提供了无与伦比的性能和资源控制能力。

(图片来源网络,侵删)
核心思想
C 语言本身不提供原生的 Elasticsearch 客户端,与 Java 的官方客户端不同,我们需要使用 HTTP 客户端库 来向 Elasticsearch 的 REST API 发送 HTTP 请求,并接收和解析 JSON 响应。
一个完整的 C 语言 Elasticsearch 客户端通常由以下几个部分组成:
- HTTP 客户端库:负责建立网络连接、发送 HTTP 请求和接收响应。
- JSON 解析库:负责将 Elasticsearch 返回的 JSON 响应解析成 C 语言中的数据结构(如结构体),以便于程序处理。
- 可选的封装库:一些第三方库已经将 HTTP 客户端和 JSON 解析器封装起来,提供了更简洁的 API,甚至实现了部分官方客户端的功能。
使用 libcurl + cURL (最常用、最灵活)
这是最主流、最推荐的方法。libcurl 是一个强大的、功能丰富的客户端 URL 传输库,支持几乎所有类型的网络协议,包括 HTTP/HTTPS,你需要自己处理 JSON 的序列化和反序列化。
安装依赖
你需要确保你的系统上安装了 libcurl 开发库。

(图片来源网络,侵删)
- 在 Debian/Ubuntu 上:
sudo apt-get update sudo apt-get install libcurl4-openssl-dev
- 在 CentOS/RHEL/Fedora 上:
sudo yum install libcurl-devel
- 在 macOS 上 (使用 Homebrew):
brew install curl
Homebrew 默认会安装头文件,所以通常不需要额外步骤。
选择一个 JSON 库
你需要一个 C 语言的 JSON 库。Jansson 是一个非常好的选择,它简单、易用且功能强大。
- 安装 Jansson:
- 从源码编译安装 (推荐):
wget https://github.com/akheron/jansson/archive/refs/tags/v2.14.tar.gz tar -xzf v2.14.tar.gz cd jansson-2.14 autoreconf -i ./configure make sudo make install
- 或者使用包管理器:
sudo apt-get install libjansson-dev(Debian/Ubuntu)sudo yum install jansson-devel(CentOS/RHEL)
- 从源码编译安装 (推荐):
编写代码示例
下面是一个完整的示例,演示如何使用 libcurl 和 jansson 来:
- 创建一个索引。
- 向索引中添加一个文档。
- 从索引中搜索一个文档。
文件结构:

(图片来源网络,侵删)
.
├── es_client.c
└── Makefile
es_client.c
#include <stdio.h>
#include <string.h>
#include <curl/curl.h>
#include <jansson.h>
// 定义 Elasticsearch 的地址
#define ES_URL "http://localhost:9200"
#define INDEX_NAME "my_c_index"
#define TYPE_NAME "_doc" // Elasticsearch 7.x 之后,type 默认为 _doc
// 用于存储 libcurl 响应的结构体
struct MemoryStruct {
char *memory;
size_t size;
};
// 回调函数,用于 libcurl 接收数据
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 = realloc(mem->memory, mem->size + realsize + 1);
if(!ptr) {
/* out of memory! */
printf("not enough memory (realloc returned NULL)\n");
return 0;
}
mem->memory = ptr;
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
return realsize;
}
// 1. 创建索引
int create_index(CURL *curl) {
char url[256];
snprintf(url, sizeof(url), "%s/%s", ES_URL, INDEX_NAME);
CURLcode res;
struct MemoryStruct chunk;
chunk.memory = malloc(1); // will be grown by realloc
chunk.size = 0;
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
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");
// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); // 启用详细输出,方便调试
printf("Creating index '%s'...\n", INDEX_NAME);
res = curl_easy_perform(curl);
if(res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
} else {
printf("Response from create_index: %s\n\n", chunk.memory);
}
free(chunk.memory);
return (int)res;
}
// 2. 索引一个文档
int index_document(CURL *curl) {
char url[256];
snprintf(url, sizeof(url), "%s/%s/%s/1", ES_URL, INDEX_NAME, TYPE_NAME);
// 使用 Jansson 构建 JSON 负载
json_t *json_obj = json_object();
json_object_set_new(json_obj, "user", json_string("kimchy"));
json_object_set_new(json_obj, "post_date", json_string("2009-11-15T14:12:12"));
json_object_set_new(json_obj, "message", json_string("Trying out Elasticsearch, so far so good?"));
const char *json_payload = json_dumps(json_obj, 0); // 将 JSON 对象转为字符串
printf("Indexing document with payload: %s\n", json_payload);
CURLcode res;
struct MemoryStruct chunk;
chunk.memory = malloc(1);
chunk.size = 0;
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_payload); // 设置 POST 数据
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
res = curl_easy_perform(curl);
if(res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
} else {
printf("Response from index_document: %s\n\n", chunk.memory);
}
// 清理
free(chunk.memory);
json_decref(json_obj); // 释放 JSON 对象
free((void*)json_payload); // 释放 JSON 字符串
return (int)res;
}
// 3. 搜索文档
int search_document(CURL *curl) {
char url[256];
snprintf(url, sizeof(url), "%s/%s/_search", ES_URL, INDEX_NAME);
// 构建查询 JSON
json_t *query_obj = json_object();
json_t *query = json_object();
json_t *term = json_object();
json_object_set_new(term, "user", json_string("kimchy"));
json_object_set_new(query, "term", term);
json_object_set_new(query_obj, "query", query);
const char *json_query = json_dumps(query_obj, 0);
printf("Searching with query: %s\n", json_query);
CURLcode res;
struct MemoryStruct chunk;
chunk.memory = malloc(1);
chunk.size = 0;
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_query);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
res = curl_easy_perform(curl);
if(res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
} else {
printf("Response from search_document: %s\n\n", chunk.memory);
}
// 清理
free(chunk.memory);
json_decref(query_obj);
free((void*)json_query);
return (int)res;
}
int main(void) {
CURL *curl;
CURLcode res;
// 初始化 libcurl 全局环境
curl_global_init(CURL_GLOBAL_ALL);
// 初始化一个 easy 句柄
curl = curl_easy_init();
if(!curl) {
fprintf(stderr, "Failed to initialize curl easy handle.\n");
return 1;
}
// 执行操作
create_index(curl);
index_document(curl);
search_document(curl);
// 清理 easy 句柄
curl_easy_cleanup(curl);
// 清理 libcurl 全局环境
curl_global_cleanup();
return 0;
}
Makefile
CC = gcc
CFLAGS = -Wall -g -O2
LIBS = -lcurl -ljansson
TARGET = es_client
all: $(TARGET)
$(TARGET): es_client.c
$(CC) $(CFLAGS) -o $(TARGET) es_client.c $(LIBS)
clean:
rm -f $(TARGET)
编译和运行
- 将代码保存为
es_client.c和Makefile。 - 确保你的 Elasticsearch 服务正在运行 (
localhost:9200)。 - 在终端中执行:
make ./es_client
你应该能看到程序的输出,包括创建索引、索引文档和搜索文档的过程,以及 Elasticsearch 返回的 JSON 响应。
使用现成的 C 客户端库 (更高级)
如果你不希望手动处理 HTTP 和 JSON,可以使用一些已经封装好的 C 语言 Elasticsearch 客户端库,这些库提供了更接近官方客户端 API 的接口。
libesclient
这是一个比较知名的轻量级库,它封装了 libcurl 和 cjson (另一个流行的 JSON 库)。
特点:
- API 设计简洁。
- 支持基本的 CRUD 操作。
- 需要你自行管理内存。
使用示例 (伪代码):
#include <libesclient.h>
// ... 初始化 ...
es_client_t *client = es_client_init("localhost", 9200, NULL, NULL);
// 索引文档
json_t *doc = json_pack("{s:s, s:i}", "name", "test_doc", "value", 123);
es_client_index(client, "my_index", "my_type", "1", doc, NULL);
// 搜索文档
json_t *query = json_pack("{s:{s:{s:s}}}", "query", "match", "name", "test_doc");
json_t *results = es_client_search(client, "my_index", query, NULL);
// ... 解析 results ...
json_decref(doc);
json_decref(query);
json_decref(results);
es_client_cleanup(client);
CCR (C REST Client for Elasticsearch)
这是一个更现代、功能更全面的库,灵感来源于官方的 Java 客户端。
特点:
- 支持连接池。
- 提供了更丰富的 API,包括聚合、滚动查询等。
- 更复杂的构建和依赖管理。
使用示例 (伪代码):
#include <elasticsearch/ccr.h>
// ... 初始化连接池 ...
ccr_pool_t *pool = ccr_pool_create("localhost", 9200);
// 创建文档
ccr_document_t *doc = ccr_document_new();
ccr_document_set_field(doc, "user", "c_user");
ccr_document_set_field(doc, "message", "Hello from CCR!");
// 索入文档
ccr_index_t *index = ccr_index_new(pool, "my_ccr_index");
ccr_index_create(index, doc);
// 搜索
ccr_search_t *search = ccr_search_new(pool, "my_ccr_index");
ccr_search_query(search, ccr_query_match("user", "c_user"));
ccr_search_response_t *response = ccr_search_perform(search);
// ... 处理响应 ...
ccr_document_free(doc);
ccr_pool_destroy(pool);
总结与对比
| 特性 | 方案一: libcurl + Jansson | 方案二: libesclient / CCR |
|---|---|---|
| 灵活性 | 极高,你可以控制每一个 HTTP 请求头、参数和细节。 | 中等,API 是预设好的,自定义能力受限。 |
| 易用性 | 较低,需要手动处理 JSON 序列化/反序列化、错误处理等。 | 较高,封装了底层细节,API 更直观。 |
| 依赖 | 两个独立的、轻量级的库 (libcurl, jansson)。 |
一个集成的库,但可能内部依赖其他库(如 cjson)。 |
| 功能 | 完整,只要你能构造出合法的 JSON,就可以调用任何 ES API。 | 基本覆盖,通常只实现了最常用的 CRUD 和搜索功能。 |
| 学习曲线 | 陡峭,需要了解 HTTP 协议、JSON 格式以及这两个库的用法。 | 平缓,学习库提供的 API 即可。 |
| 适用场景 | 需要极致控制、性能优化,或实现非常特殊、非标准的 ES API 的场景。 | 快速开发,对标准 CRUD 和搜索操作有需求的常规应用。 |
推荐选择
- 对于初学者或快速原型开发:如果你不想陷入 HTTP 和 JSON 的细节,
libesclient是一个不错的选择。 - 对于生产环境或高性能需求:强烈推荐使用
libcurl + Jansson(方案一),虽然初期搭建麻烦一些,但它给你带来了无与伦比的灵活性、控制力和对性能的精确把握,在长期维护和复杂业务逻辑面前,这种灵活性是巨大的优势。
