欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux(程序设计):36---OpenSSL库之(哈希表的使用)

程序员文章站 2024-03-01 08:39:46
...

一、哈希表

  • 在一般的数据结构如线性表和树中,记录在结构中的相对位置是与记录的关键字之间不存在确定的关系,在结构中查找记录时需进行一系列的关键字比较。这一类查找方法建立在 “比较”的基础上,查找的效率与比较次数密切相关。理想的情况是能直接找到需要的记 录,因此必须在记录的存储位置和它的关键字之间建立确定的对应关系,使每个关键字和结构中一个唯一的存储位置相对应。在查找时,只需根据这个对应关系找到给定值。这种对应关系既是哈希函数,按这个思想建立的表为哈希表
  • 哈希表存在冲突现象:不同的关键字可能得到同一哈希地址。在建造哈希表时不仅要设定一个好的哈希函数,而且要设定 一种处理冲突的方法

二、OpenSSL哈希表的实现

  • OpenSSL也自己实现了一套哈希算法,并能存放任意形式的数据,比如配置文件的读取、内存分配中被分配内存的信息等
  • 本文介绍OpenSSL1.1.1g版本的哈希实现,其源码位于OpenSSL源码目录的crypto/lhash目录下

Linux(程序设计):36---OpenSSL库之(哈希表的使用)

三、哈希表数据结构

  • 哈希表数据结构在lhash_local.h中定义基本的结构如下示图:

Linux(程序设计):36---OpenSSL库之(哈希表的使用)

struct lhash_node_st(单链表节点)

  • 本结构是一个单链表的节点。其中,data用于存放数据地址,next为下一个数据地址,hash为数据哈希计算值
struct lhash_node_st {
    void *data;
    struct lhash_node_st *next;
    unsigned long hash;
};

struct lhash_st(哈希表)

  • 其中,b指针数组用于存放所有的数据,数组中的每一个值为数据链表的头指针
  • comp用于存放数据比较函数地址
  • hash用于存放计算哈希值函数的地址
  • num_nodes为链表个数
  • num_alloc_nodes为b分配空间的大小
struct lhash_st {
    OPENSSL_LH_NODE **b;
    OPENSSL_LH_COMPFUNC comp;
    OPENSSL_LH_HASHFUNC hash;
    unsigned int num_nodes;
    unsigned int num_alloc_nodes;
    unsigned int p;
    unsigned int pmax;
    unsigned long up_load;      /* load times 256 */
    unsigned long down_load;    /* load times 256 */
    unsigned long num_items;
    unsigned long num_expands;
    unsigned long num_expand_reallocs;
    unsigned long num_contracts;
    unsigned long num_contract_reallocs;
    TSAN_QUALIFIER unsigned long num_hash_calls;
    TSAN_QUALIFIER unsigned long num_comp_calls;
    unsigned long num_insert;
    unsigned long num_replace;
    unsigned long num_delete;
    unsigned long num_no_delete;
    TSAN_QUALIFIER unsigned long num_retrieve;
    TSAN_QUALIFIER unsigned long num_retrieve_miss;
    TSAN_QUALIFIER unsigned long num_hash_comps;
    int error;
};

四、函数说明

OPENSSL_LH_new()

OPENSSL_LHASH *OPENSSL_LH_new(OPENSSL_LH_HASHFUNC h, OPENSSL_LH_COMPFUNC c);
  • 功能:创建哈希表
  • 参数:
    • 输入参数h为哈希函数
    • c为比较函数
    • 这两个函数都是回调函数。 因为哈希表用于存放任意的数据结构,哈希表存放、查询、删除等操作都需要比较数据和进行哈希运算,而哈希表不知道用户数据如何进行比较,也不知道用户数据结构中需要对哪些关键项进行散列运算。所以,用户必须提供这两个回调函数
  • 返回值:
    • 成功返回OPENSSL_LHASH
    • 失败返回NULL

OPENSSL_LH_free 

void OPENSSL_LH_free(OPENSSL_LHASH *lh);
  • 功能:释放哈希表
  • 但是不释放表中数据的内存,需要调用下面的doall方法遍历表中数据去释放

OPENSSL_LH_delete 

void *OPENSSL_LH_delete(OPENSSL_LHASH *lh, const void *data);
  • 功能:删除哈希表中的一个数据节点
  • 参数:
    • lh:哈希表
    • data:data为数据结构指针
  • 返回值:
    • 成功返回删除的该数据的地址

OPENSSL_LH_doall

void OPENSSL_LH_doall(OPENSSL_LHASH *lh, OPENSSL_LH_DOALL_FUNC func);
  • 功能:遍历哈希表中的所有数据
  • 参数:
    • lh:哈希表
    • func:为外部提供的回调函数,哈希表中的每个节点会依次传递给func
  • 无返回值

OPENSSL_LH_doall_arg

void OPENSSL_LH_doall_arg(OPENSSL_LHASH *lh, OPENSSL_LH_DOALL_FUNCARG func, void *arg);
  • 功能:处理哈希表中所有数据
  • 参数:
    • lh:哈希表
    • func:为外部提供的回调函数,哈希表中的每个节点和参数3都会传递给该函数
    • arg:作为func回调函数的参数2使用
  • 此参数类似于lh_doall 函数,只不过func是为两个参数的

OPENSSL_LH_insert

void *OPENSSL_LH_insert(OPENSSL_LHASH *lh, void *data);
  • 功能:往哈希表中添加数据
  • 参数:
    • lh:​​​​​​​哈希表
    • data:为需要添加数据结构的指针地址
  • 当表中有该数据时,会进行替换
  • 返回值:
    • 成功返回插入的该数据的地址

OPENSSL_LH_retrieve 

void *OPENSSL_LH_retrieve(OPENSSL_LHASH *lh, const void *data);
  • 功能:查询数据
  • 参数:
    • lh:​​​​​​​哈希表
    • data:要查询的数据的地址
  • 参数2必须提供关键项(这些关键项对应于用户提供的哈希函数和比较函数)以供查询,比如SSL握手中服务端查询以前存储的SESSION时,它需要提供其中关键的几项:
SSL_SESSION *ret=NULL,data;
data.ssl_version=s->version;
data.session_id_length=len;
memcpy(data.session_id,session_id,len);
ret=(SSL_SESSION *)lh_retrieve(s->ctx->sessions,&data); 
  • 返回值:
    • 成功返回查找到的该数据的地址

OPENSSL_LH_num_items 

unsigned long OPENSSL_LH_num_items(const OPENSSL_LHASH *lh);
  • 功能: 获取哈希表中元素的个数
  • 参数:返回元素的个数

OPENSSL_LH_strhash 

unsigned long OPENSSL_LH_strhash(const char *c);
  • 功能: 计算一条数据的哈希值
  • 参数:
    • c:用于保存查询到的内容
  • 参数:返回哈希值

 OPENSSL_LH_stats

void OPENSSL_LH_stats(const OPENSSL_LHASH *lh, FILE *fp);
  • 功能:打印哈希表的统计信息,此函数调用了lh_stats_bio
  • 参数:
    • lh:​​​​​​​哈希表
    • fp:用于保存查询到的内容

OPENSSL_LH_node_stats

void OPENSSL_LH_node_stats(const OPENSSL_LHASH *lh, FILE *fp);
  • 功能: 将哈希表中每个链表下数据到个数输出到FILE中。
  • 参数:
    • lh:​​​​​​​哈希表
    • fp:用于保存查询到的内容
  • 此函数调用了lh_node_stats_bio函数

OPENSSL_LH_node_usage_stats

void OPENSSL_LH_node_usage_stats(const OPENSSL_LHASH *lh, FILE *fp);
  •  功能: 查看哈希表节点的使用状态
  • 此函数调用了lh_node_usage_stats_bio函数

OPENSSL_LH_stats_bio 

void OPENSSL_LH_stats_bio(const OPENSSL_LHASH *lh, BIO *out);
  • 功能: 输出哈希表统计信息到BIO中
  • 参数:
    • lh:哈希表
    • out:用于保存查询到的内容

OPENSSL_LH_node_stats_bio 

void OPENSSL_LH_node_stats_bio(const OPENSSL_LHASH *lh, BIO *out);
  • 功能: 将哈希表中每个链表下的数据状态输出到BIO中
  • 参数:
    • lh:哈希表
    • out:保存输出信息

OPENSSL_LH_node_usage_stats_bio 

void OPENSSL_LH_node_usage_stats_bio(const OPENSSL_LHASH *lh, BIO *out);
  • 功能: 将哈希表的使用状态输出到BIO中
  • 参数:
    • lh:哈希表
    • out:用于保存查询到的内容

五、编程示例1

代码如下

//hash_test1.c
#include <stdio.h>
#include <string.h>
#include <openssl/lhash.h>

#define NAME_LENGTH 32

typedef struct _Person
{
    char name[NAME_LENGTH];
    double hight;
} Person;


int personCompare(const void *arg1, const void *arg2)
{
    char *name1 = ((Person*)arg1)->name;
    char *name2 = ((Person*)arg2)->name;

    return strcmp(name1, name2);
}

void printNode(void *arg)
{
    Person *person = (Person*)arg;
    printf("name :%s, height: %f\n", person->name, person->hight);
}

int main()
{
    //1.创建哈希表
    OPENSSL_LHASH *_hash = OPENSSL_LH_new(NULL, personCompare);
    if(_hash == NULL)
    {
        printf("OPENSSL_LH_new error\n");
        return -1;
    }

    //2.向哈希表中插入三条数据
    Person person1 = {"Tom", 170.5};
    Person person2 = {"Joke", 180.5};
    Person person3 = {"Alan", 175.5};
    OPENSSL_LH_insert(_hash, &person1);
    OPENSSL_LH_insert(_hash, &person2);
    OPENSSL_LH_insert(_hash, &person3);

    //3.遍历所有的数据
    OPENSSL_LH_doall(_hash, printNode);
    printf("\n\n");

    //4.删除一条数据之后再遍历
    if(OPENSSL_LH_delete(_hash, (const void*)"Tom") == NULL)
    {
        printf("OPENSSL_LH_delete error\n");
        return -1;
    }
    OPENSSL_LH_doall(_hash, printNode);
    printf("\n\n");

    //5.查找一条数据
    void *findData = OPENSSL_LH_retrieve(_hash, "Alan");
    if(findData == NULL)
    {
        printf("OPENSSL_LH_retrieve error\n");
        return -1;
    }
    printf("find success:\n\t");
    printNode(findData);


    //6.销毁哈希表
    OPENSSL_LH_free(_hash);

    return 0;
}
  • 编译运行结果如下所示: 
gcc -o hash_test1 hash_test1.c -lssl -lcrypto

Linux(程序设计):36---OpenSSL库之(哈希表的使用)

五、编程示例2

代码如下

//hash_test2.c
#include <stdio.h>
#include <string.h>
#include <openssl/lhash.h>

unsigned long hashff(void *hf)
{
    printf("%s\n",hf);
    return 100;
}

int hashfCmp(int  *a,int  *b)
{
    return *a > *b;
    
}

void printArg(int *a,char *b)
{
    printf("doall_arg: %d  %s\n",*a,b);
}

void printValue(int *value)
{
    printf("doall: %d\n",*value);
}


int main(int argc, const char * argv[]) {
    
    
    OPENSSL_LHASH *lh = OPENSSL_LH_new(NULL, NULL);
    
    int item = 1;
    OPENSSL_LH_insert(lh, &item);
    
    
    int item2 = 10;
    OPENSSL_LH_insert(lh, &item2);
    
    int item3 = 5;
    OPENSSL_LH_insert(lh, &item3);
    
    
    //因为表中已经存在数据5,如果再插入,将会替换之前的数据5
    int item4 = 5;
    int *ret=0;
    ret = OPENSSL_LH_insert(lh, &item4);
    if (*ret==item4) {
        printf("insert replace PASS\n");
    }
    
    
    int *fd = 0;
    fd = OPENSSL_LH_retrieve(lh,&item2);
    if (*fd == item2) {
        printf("find value PASS\n");
    }
    
    OPENSSL_LH_doall(lh, printValue);
    OPENSSL_LH_doall_arg(lh, printArg, "arg");
    
    
    int *delRet = 0;
    delRet = OPENSSL_LH_delete(lh, &item4);
    if (*delRet==item4) {
        printf("delete value PASS\n");
    }
    
    
    int numLen = OPENSSL_LH_num_items(lh);
    printf("len=%d\n");
    
    
    OPENSSL_LH_stats(lh, stdout);
    
    
    OPENSSL_LH_free(lh);
    
    
    
    return 0;
}
  • 编译运行结果如下所示: 
gcc -o hash_test2 hash_test2.c -lssl -lcrypto

Linux(程序设计):36---OpenSSL库之(哈希表的使用)