如何用OpenLDAP 2.6 C客户端高效收集LDAP搜索属性值至char*[]
需求背景
使用OpenLDAP 2.6 C客户端库实现LDAP搜索,仅查询mail、mailAlias和mailAccount三个属性。这三个属性均为多值IA5字符串(RFC822邮件地址格式),需要将所有返回的属性值合并为char*[]格式,无需关心值所属的属性或条目。
示例输入结果:
dn: uid=johndoe,ou=users,dc=my-domain,dc=tld mail: john.doe@somewhere.tld mail: jdoe@here.tld mailAccount: john.doe@my-domain.tld mailAlias: doe@my-domain.tld
期望输出:
char* result[] = { "john.doe@somewhere.tld", "jdoe@here.tld", "john.doe@my-domain.tld", "doe@my-domain.tld" };
当前核心代码
LDAPMessage* ldap_result_msg = NULL; int result_code = ldap_search_ext_s( ldap_handle, ..., &ldap_result_msg ); // ... 省略错误处理 ... struct string_array_t* result = create_string_array(); // 类似C++的std::vector<std::string>的动态数组 for( LDAPMessage* ldap_entry_msg = ldap_first_entry( ldap_handle, ldap_result_msg ); ldap_entry_msg != NULL; ldap_entry_msg = ldap_next_entry( ldap_handle, ldap_entry_msg ) ) { BerElement* berptr; for( char* ldap_attr = ldap_first_attribute( ldap_handle, ldap_entry_msg, &berptr ); ldap_attr != NULL; ldap_attr = ldap_next_attribute( ldap_handle, ldap_entry_msg, berptr ) ) { struct berval** values = ldap_get_values_len( ldap_handle, ldap_entry_msg, ldap_attr ); char* value = NULL; for( int i = 0; values[i] != NULL; ++i ) { value = malloc( values[i]->bv_len + 1 ); strncpy( value, values[i]->bv_val, values[i]->bv_len ); value[ values[i]->bv_len ] = '\0'; push_onto_string_array( result, value ); free( value ); } ldap_value_free_len( values ); ldap_memfree( ldap_attr ); } ber_free( berptr, 0 ); } ldap_msgfree( ldap_result_msg );
待优化问题
遍历效率低下
当前通过ldap_next_entry遍历条目,再用ldap_next_attribute遍历属性,最后调用ldap_get_values_len获取值。而ldap_get_values_len会从头搜索目标属性,对于仅需所有值合集的场景,这种嵌套遍历存在冗余,希望找到更高效的遍历方式。不必要的内存分配与释放
push_onto_string_array( result, value )会对传入的char * const做深拷贝,但当前循环中:
- 为每个属性分配释放
berval** values - 为每个属性值额外执行malloc、拷贝并添加终止符,之后又被
push_onto_string_array再次拷贝,存在冗余的内存操作。
优化方案
1. 优化遍历效率:直接针对目标属性取值
由于已经明确指定了要查询的三个属性,无需遍历所有返回属性,直接循环这三个属性名调用ldap_get_values_len,省去属性遍历的开销:
// 定义目标属性列表 const char* target_attrs[] = { "mail", "mailAlias", "mailAccount", NULL }; LDAPMessage* ldap_result_msg = NULL; int result_code = ldap_search_ext_s( ldap_handle, ..., target_attrs, &ldap_result_msg ); // ... 错误处理 ... struct string_array_t* result = create_string_array(); for( LDAPMessage* ldap_entry_msg = ldap_first_entry( ldap_handle, ldap_result_msg ); ldap_entry_msg != NULL; ldap_entry_msg = ldap_next_entry( ldap_handle, ldap_entry_msg ) ) { // 直接遍历目标属性,无需处理所有属性 for( int attr_idx = 0; target_attrs[attr_idx] != NULL; attr_idx++ ) { struct berval** values = ldap_get_values_len( ldap_handle, ldap_entry_msg, target_attrs[attr_idx] ); if( values == NULL ) continue; // 属性不存在时跳过 // 处理属性值(后续内存优化逻辑放在此处) for( int i = 0; values[i] != NULL; ++i ) { // 内存优化代码 } ldap_value_free_len( values ); } } ldap_msgfree( ldap_result_msg );
这种方式避免了ldap_first_attribute/ldap_next_attribute的遍历,也减少了ldap_get_values_len的无效查找,因为我们只针对已知存在的目标属性调用。
2. 优化内存操作:减少冗余拷贝
根据是否能修改push_onto_string_array接口,提供两种优化方案:
方案A:不修改现有接口
使用strndup直接生成带终止符的字符串,替代手动malloc+strncpy的操作:
// 在属性值循环中 for( int i = 0; values[i] != NULL; ++i ) { // strndup自动分配内存并添加终止符 char* value = strndup( values[i]->bv_val, values[i]->bv_len ); if( value != NULL ) { push_onto_string_array( result, value ); free( value ); // 因push_onto_string_array会深拷贝,释放临时内存 } }
方案B:修改接口支持长度参数
新增push_onto_string_array_len(struct string_array_t* arr, const char* data, size_t len)接口,内部直接根据长度拷贝数据,无需提前添加终止符:
// 在属性值循环中 for( int i = 0; values[i] != NULL; ++i ) { push_onto_string_array_len( result, values[i]->bv_val, values[i]->bv_len ); }
这种方式彻底省去中间的内存分配与拷贝,直接复用OpenLDAP返回的berval数据,大幅减少内存操作开销。
内容的提问来源于stack exchange,提问作者user2690527

