文章目录
- 一、项目简介
- 二、项目流程图
- 三、网络
- 3.1、epoll实现
- 3.2、io_uring实现
- 四、协议
- 五、存储
- 5.1、array实现
- 5.2、rbtree实现
- 5.3、hash实现
- 六、测试
一、项目简介
key-value存储其实是一个小型的redis,用户在客户端输入存储相关的指令发送给服务器端,服务器在接受到指令之后对指令做对应的操作。在整个过程中,涉及到客户端和服务器端使用什么样的网络模型,如epoll,io_uring,ntyco等等,服务器端对指令做操作涉及到的数据结构,如array,rbtree,hash等等。
在本项目中,网络层的组件选择了epoll,io_uring,存储数据结构选择了array,rbtree,hash。
二、项目流程图
三、网络
在网络具体实现中,把收发数据存放在不同的数组当中,下面是结构体定义:
struct conn_item{int fd;char rbuffer[BUFFER_LENGTH];char wbuffer[BUFFER_LENGTH];int rlen;int wlen;union {RCALLBACK accept_callback;RCALLBACK recv_callback;}recv_t;RCALLBACK send_callback;};
3.1、epoll实现
如果对epoll不熟悉,可以参考我的博文:高性能网络编程
下面是具体实现
#include "kvstore.h"#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>#include <stdio.h>
#include <string.h>
#include <unistd.h>#include <pthread.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#include <sys/time.h>#define PORT 8000
#define SERVER_ADR "127.0.0.1"
struct conn_item connlist[1048576] = {0};int epfd = 0;int init_serv(unsigned short port)
{int fd = socket(PF_INET, SOCK_STREAM, 0);struct sockaddr_in sever_adr;memset(&sever_adr, 0, sizeof(sever_adr));sever_adr.sin_family = AF_INET;sever_adr.sin_port = htons(port);sever_adr.sin_addr.s_addr = htonl(INADDR_ANY);if ((bind(fd, (const struct sockaddr *)&sever_adr, sizeof(sever_adr))) == -1){perror("bind error");return -1;}if ((listen(fd, 10)) == -1){perror("listen error");return -1;}return fd;
}int set_event(int fd, int event, int flag)
{if (flag){struct epoll_event ev;ev.data.fd = fd;ev.events = event;epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);}else{struct epoll_event ev;ev.data.fd = fd;ev.events = event;epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);}
}int send_cb(int fd)
{char *buffer = connlist[fd].wbuffer;int idx = connlist[fd].wlen;int count = send(fd, buffer, idx, 0);set_event(fd, EPOLLIN, 0);memset(connlist[fd].wbuffer, 0, BUFFER_LENGTH);connlist[fd].wlen = 0;return count;
}int recv_cb(int fd)
{char *buffer = connlist[fd].rbuffer;int idx = connlist[fd].rlen;int count = recv(fd, buffer, BUFFER_LENGTH, 0);if (count == 0){printf("disconnect\n");epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);close(fd);return -1;}connlist[fd].rlen = count;kvstore_request(&connlist[fd]);connlist[fd].wlen = strlen(connlist[fd].wbuffer);set_event(fd, EPOLLOUT, 0);memset(connlist[fd].rbuffer, 0, BUFFER_LENGTH);connlist[fd].rlen = 0;return count;
}int accept_cb(int fd)
{struct sockaddr_in clnt_adr;memset(&clnt_adr, 0, sizeof(clnt_adr));socklen_t len = sizeof(clnt_adr);int connfd = accept(fd, (struct sockaddr *)&clnt_adr, &len);if (connfd < 0){perror("accept error");return -1;}set_event(connfd, EPOLLIN, 1);connlist[connfd].fd = connfd;memset(connlist[connfd].rbuffer, 0, BUFFER_LENGTH);connlist[connfd].rlen = 0;memset(connlist[connfd].wbuffer, 0, BUFFER_LENGTH);connlist[connfd].wlen = 0;connlist[connfd].send_callback = send_cb;connlist[connfd].recv_t.recv_callback = recv_cb;return connfd;
}int epoll_entry()
{int port_count = 20;unsigned short port = 2048;int i = 0;epfd = epoll_create(1);int sockfd;for (i = 0; i < port_count; i++){sockfd = init_serv(port + i);connlist[sockfd].fd = sockfd;connlist[sockfd].recv_t.accept_callback = accept_cb;set_event(sockfd, EPOLLIN, 1);}struct epoll_event events[1024] = {0};while (1){int nfds = epoll_wait(epfd, events, 1024, -1);if (nfds < 0){perror("epoll wait error");return -1;}int i;for (i = 0; i < nfds; i++){int connfd = events[i].data.fd;if (events[i].events & EPOLLIN){int count = connlist[connfd].recv_t.recv_callback(connfd);}else if (events[i].events & EPOLLOUT){int count = connlist[connfd].send_callback(connfd);}}}
}
其实,这部分代码也是针对epoll实现的一个改写,关键还是在于掌握epoll如何使用。项目只是把这些知识点组织起来。
3.2、io_uring实现
如果对io_uring不熟悉,可以参考我的博文:【C】io_uring原理与实战
下面是具体实现
#include "kvstore.h"
#include <unistd.h>
#include <liburing.h>
#include <netinet/in.h>struct conn_info
{int fd;int event;
};int init_server(unsigned short port)
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(struct sockaddr_in));serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);serveraddr.sin_port = htons(port);if (-1 == bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr))){perror("bind");return -1;}listen(sockfd, 10);return sockfd;
}#define ENTRIES_LENGTH 1024#define EVENT_ACCEPT 0
#define EVENT_READ 1
#define EVENT_WRITE 2struct conn_item connlist[1048576] = {0};int set_event_accept(struct io_uring *ring, int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags)
{struct io_uring_sqe *sqe = io_uring_get_sqe(ring);struct conn_info accept_info;accept_info.fd = sockfd;accept_info.event = EVENT_ACCEPT;io_uring_prep_accept(sqe, sockfd, addr, addrlen, flags);memcpy(&sqe->user_data, &accept_info, sizeof(accept_info));
}int set_event_recv(struct io_uring *ring, int sockfd, int flags)
{struct io_uring_sqe *sqe = io_uring_get_sqe(ring);struct conn_info accept_info;accept_info.fd = sockfd;accept_info.event = EVENT_READ;io_uring_prep_recv(sqe, sockfd, connlist[sockfd].rbuffer, BUFFER_LENGTH, flags);memcpy(&sqe->user_data, &accept_info, sizeof(accept_info));
}int set_event_send(struct io_uring *ring, int sockfd, int flags)
{struct io_uring_sqe *sqe = io_uring_get_sqe(ring);struct conn_info accept_info;accept_info.fd = sockfd;accept_info.event = EVENT_WRITE;io_uring_prep_send(sqe, sockfd, connlist[sockfd].wbuffer, connlist[sockfd].wlen, flags);memcpy(&sqe->user_data, &accept_info, sizeof(accept_info));
}int io_uring_entry()
{unsigned short port = 8001;int sockfd = init_server(port);struct io_uring_params params;memset(¶ms, 0, sizeof(params));struct io_uring ring;memset(&ring, 0, sizeof(ring));io_uring_queue_init_params(ENTRIES_LENGTH, &ring, ¶ms);struct sockaddr_in clntadr;socklen_t len = sizeof(clntadr);set_event_accept(&ring, sockfd, (struct sockaddr *)&clntadr, &len, 0);char buffer[BUFFER_LENGTH] = {0};while (1){io_uring_submit(&ring);struct io_uring_cqe *cqe;io_uring_wait_cqe(&ring, &cqe);struct io_uring_cqe *cqes[128];int nready = io_uring_peek_batch_cqe(&ring, cqes, 128);int i;for (i = 0; i < nready; i++){struct io_uring_cqe *entry = cqes[i];struct conn_info result;memcpy(&result, &entry->user_data, sizeof(result));if (result.event == EVENT_ACCEPT){set_event_accept(&ring, sockfd, (struct sockaddr *)&clntadr, &len, 0);int connfd = entry->res;connlist[connfd].fd = connfd;memset(connlist[connfd].rbuffer, 0, BUFFER_LENGTH);connlist[connfd].rlen = 0;memset(connlist[connfd].wbuffer, 0, BUFFER_LENGTH);connlist[connfd].wlen = 0;set_event_recv(&ring, connfd, 0);}else if (result.event == EVENT_READ){kvstore_request(&connlist[result.fd]);connlist[result.fd].wlen = strlen(connlist[result.fd].wbuffer);// printf("wbuffer is %s\n",connlist[result.fd].wbuffer);int ret = entry->res;if (ret == 0){close(result.fd);}else if (ret > 0){memset(connlist[result.fd].rbuffer, 0, BUFFER_LENGTH);connlist[result.fd].rlen = 0;set_event_send(&ring, result.fd, 0);}}else if (result.event == EVENT_WRITE){memset(connlist[result.fd].wbuffer, 0, BUFFER_LENGTH);connlist[result.fd].wlen = 0;set_event_recv(&ring, result.fd, 0);}}io_uring_cq_advance(&ring, nready);}
}
四、协议
协议的设置也是参照redis的一些命令格式,实现一些CRUD的操作,下面是具体的协议格式
"SET","GET","DEL","MOD","COUNT","RSET","RGET","RDEL","RMOD","RCOUNT","HSET","HGET","HDEL","HMOD","HCOUNT"
其中R代表rbtree,H代表hash。
下面是关于如何实现协议的具体代码
int kvstore_spilt_token(char *msg, char **tokens)
{if (msg == NULL || tokens == NULL)return -1;int idx = 0;char *ch = strtok(msg, " ");while (ch != NULL){tokens[idx++] = ch;ch = strtok(NULL, " ");}return idx;
}int kvstore_parser_protocol(struct conn_item *item, char **tokens, int count)
{if (item == NULL || tokens[0] == NULL || count == 0)return -1;int cmd = KVS_CMD_START;for (cmd = KVS_CMD_START; cmd < KVS_CMD_SIZE; cmd++){if (strcmp(commands[cmd], tokens[0]) == 0){break;}}char *msg = item->wbuffer;char *key = tokens[1];char *value = tokens[2];memset(msg, 0, BUFFER_LENGTH);switch (cmd){
#if ENABLE_ARRAY_KVENGINE// arraycase KVS_CMD_SET:{int ress = kvstore_array_set(key, value);if (!ress){snprintf(msg, BUFFER_LENGTH, "SUCCESS");}else{snprintf(msg, BUFFER_LENGTH, "FAILED");}break;}case KVS_CMD_GET:{char *val = kvstore_array_get(key);if (val){snprintf(msg, BUFFER_LENGTH, "%s", val);}else{snprintf(msg, BUFFER_LENGTH, "NO EXIST");}break;}case KVS_CMD_DEL:{ // printf("del\n");int resd = kvstore_array_delete(key);if (resd < 0){ // serversnprintf(msg, BUFFER_LENGTH, "%s", "ERROR");}else if (resd == 0){snprintf(msg, BUFFER_LENGTH, "%s", "SUCCESS");}else{snprintf(msg, BUFFER_LENGTH, "NO EXIST");}break;}case KVS_CMD_MOD:{ // printf("mod\n");int resm = kvstore_array_modify(key, value);if (resm < 0){ // serversnprintf(msg, BUFFER_LENGTH, "%s", "ERROR");}else if (resm == 0){snprintf(msg, BUFFER_LENGTH, "%s", "SUCCESS");}else{snprintf(msg, BUFFER_LENGTH, "NO EXIST");}break;}case KVS_CMD_COUNT:{int counta = kvstore_array_count();if (counta < 0){ // serversnprintf(msg, BUFFER_LENGTH, "%s", "ERROR");}else{snprintf(msg, BUFFER_LENGTH, "%d", counta);}break;}
#endif#if ENABLE_RBTREE_KVENGINE// rbtreecase KVS_CMD_RSET:{int resr = kvstore_rbtree_set(key, value);if (!resr){snprintf(msg, BUFFER_LENGTH, "SUCCESS");}else{snprintf(msg, BUFFER_LENGTH, "FAILED");}break;}case KVS_CMD_RGET:{char *valr = kvstore_rbtree_get(key);if (valr){snprintf(msg, BUFFER_LENGTH, "%s", valr);}else{snprintf(msg, BUFFER_LENGTH, "NO EXIST");}break;}case KVS_CMD_RDEL:{int resrd = kvstore_rbtree_delete(key);if (resrd < 0){ // serversnprintf(msg, BUFFER_LENGTH, "%s", "ERROR");}else if (resrd == 0){snprintf(msg, BUFFER_LENGTH, "%s", "SUCCESS");}else{snprintf(msg, BUFFER_LENGTH, "NO EXIST");}break;}case KVS_CMD_RMOD:{int resrm = kvstore_rbtree_modify(key, value);if (resrm < 0){ // serversnprintf(msg, BUFFER_LENGTH, "%s", "ERROR");}else if (resrm == 0){snprintf(msg, BUFFER_LENGTH, "%s", "SUCCESS");}else{snprintf(msg, BUFFER_LENGTH, "NO EXIST");}break;}case KVS_CMD_RCOUNT:{int countr = kvstore_rbtree_count();if (countr < 0){ // serversnprintf(msg, BUFFER_LENGTH, "%s", "ERROR");}else{snprintf(msg, BUFFER_LENGTH, "%d", countr);}break;}
#endif#if ENABLE_HASH_KVENGINEcase KVS_CMD_HSET:{int res = kvstore_hash_set(key, value);if (!res){snprintf(msg, BUFFER_LENGTH, "SUCCESS");}else{snprintf(msg, BUFFER_LENGTH, "FAILED");}break;}// hashcase KVS_CMD_HGET:{char *val = kvstore_hash_get(key);if (val){snprintf(msg, BUFFER_LENGTH, "%s", val);}else{snprintf(msg, BUFFER_LENGTH, "NO EXIST");}break;}case KVS_CMD_HDEL:{int res = kvstore_hash_delete(key);if (res < 0){ // serversnprintf(msg, BUFFER_LENGTH, "%s", "ERROR");}else if (res == 0){snprintf(msg, BUFFER_LENGTH, "%s", "SUCCESS");}else{snprintf(msg, BUFFER_LENGTH, "NO EXIST");}break;}case KVS_CMD_HMOD:{int res = kvstore_hash_modify(key, value);if (res < 0){ // serversnprintf(msg, BUFFER_LENGTH, "%s", "ERROR");}else if (res == 0){snprintf(msg, BUFFER_LENGTH, "%s", "SUCCESS");}else{snprintf(msg, BUFFER_LENGTH, "NO EXIST");}break;}case KVS_CMD_HCOUNT:{int count = kvstore_hash_count();if (count < 0){ // serversnprintf(msg, BUFFER_LENGTH, "%s", "ERROR");}else{snprintf(msg, BUFFER_LENGTH, "%d", count);}break;}
#endifdefault:break;}
}int kvstore_request(struct conn_item *item)
{char *msg = item->rbuffer;char *tokens[KVSTORE_MAX_TOKENS];int count = kvstore_spilt_token(msg, tokens);kvstore_parser_protocol(item, tokens, count);return 0;
}
具体流程,服务器端通过网络获取到客户端传来的指令,将指令发送到kvstore_request,由kvstore_spilt_token对指令进行解析,由kvstore_parser_protocol执行具体的指令。
五、存储
5.1、array实现
Array其实就是数组,在数组中CRUD,下面是具体的实现
首先,定义一个结构体
struct kvs_array_item {char *key;char *value;
};#define KVS_ARRAY_SIZE 1024typedef struct array_s {struct kvs_array_item *array_table;int array_idx;
} array_t;extern array_t Array;
然后,数组执行CRUD的具体实现
#include "kvstore.h"array_t Array;int kvstore_array_create(array_t *arr)
{if (!arr)return -1;arr->array_table = (struct kvs_array_item *)kvstore_malloc(KVS_ARRAY_SIZE * sizeof(struct kvs_array_item));if (!arr->array_table){return -1;}memset(arr->array_table, 0, KVS_ARRAY_SIZE * sizeof(struct kvs_array_item));arr->array_idx = 0;return 0;
}void kvstore_array_destory(array_t *arr)
{if (!arr)return;if (arr->array_table)kvstore_free(arr->array_table);return;
}int kvs_array_set(array_t *arr, char *key, char *value)
{if (arr == NULL || key == NULL || value == NULL)return -1;if (arr->array_idx == KVS_ARRAY_SIZE)return -1;char *kcopy = kvstore_malloc(strlen(key)+1);if (kcopy == NULL)return -1;memset(kcopy,0,strlen(key)+1);strncpy(kcopy, key, strlen(key));char *vcopy = kvstore_malloc(strlen(value)+1);if (vcopy == NULL){kvstore_free(kcopy);return -1;}memset(vcopy,0,strlen(value)+1);strncpy(vcopy, value, strlen(value)+1);int i = 0;for (i = 0; i < arr->array_idx; i++){if (arr->array_table->key == NULL){arr->array_table[i].key = kcopy;arr->array_table[i].value = vcopy;arr->array_idx++;return 0;}}if (i < KVS_ARRAY_SIZE && i == arr->array_idx){arr->array_table[arr->array_idx].key = kcopy;arr->array_table[arr->array_idx].value = vcopy;arr->array_idx++;}return 0;
}char *kvs_array_get(array_t *arr, char *key)
{if (arr == NULL){return NULL;}int i;for (i = 0; i < arr->array_idx; i++){if (arr->array_table[i].key == NULL){return NULL;}if (strcmp(arr->array_table[i].key, key) == 0){return arr->array_table[i].value;}}return NULL;
}int kvs_array_delete(array_t *arr, char *key)
{int i;if (arr == NULL || key == NULL)return -1;for (i = 0; i < arr->array_idx; i++){if (strcmp(arr->array_table[i].key, key) == 0){kvstore_free(arr->array_table[i].value);arr->array_table[i].value = NULL;kvstore_free(arr->array_table[i].key);arr->array_table[i].key = NULL;arr->array_idx--;return 0;}}return i;
}int kvs_array_modify(array_t *arr, char *key, char *value)
{int i = 0;if (arr == NULL || key == NULL || value == NULL)return -1;for (i = 0; i < arr->array_idx; i++){if (strcmp(arr->array_table[i].key, key) == 0){kvstore_free(arr->array_table[i].value);arr->array_table[i].value = NULL;char *vcopy = kvstore_malloc(strlen(value) + 1);strncpy(vcopy, value, strlen(value) + 1);arr->array_table[i].value = vcopy;return 0;}}return i;
}int kvs_array_count(array_t *arr)
{if (!arr)return -1;return arr->array_idx;
}
5.2、rbtree实现
红黑树是一个比较难的内容,在实现具体操作前需要充分理解原理。如果对红黑树的原理并不熟悉的可以参考我的上一篇博文:一文速学—红黑树。
在上一篇博文中讲解了红黑树实现原理,以及如何实现,在这里只需要对红黑树稍微改写一下就能够达成目的。下面是实现代码
具体实现前需要再头文件中做一些声明
typedef struct _rbtree rbtree_t;extern rbtree_t Tree;
下面是具体实现
rbtree Tree;int kvstore_rbtree_create(rbtree_t *tree)
{if (tree == NULL){perror(" rbtree error");return -1;}memset(tree, 0, sizeof(rbtree));tree->nil = (rbtree_node *)malloc(sizeof(rbtree_node));tree->nil->key = malloc(1);*(tree->nil->key) = '\0';tree->root = tree->nil;tree->nil->color = BLACK;return 0;
}
void kvstore_rbtree_destory(rbtree_t *tree)
{if (!tree)return;if (tree->nil->key)kvstore_free(tree->nil->key);rbtree_node *node = tree->root;while (node != tree->nil){node = rbtree_mini(tree, tree->root);if (node == tree->nil)break;node = rbtree_delete(tree, node);if (!node){kvstore_free(node->key);kvstore_free(node->value);kvstore_free(node);}}
}
int kvs_rbtree_set(rbtree_t *tree, char *key, char *value)
{rbtree_node *node = (rbtree_node *)malloc(sizeof(rbtree_node));if (!node)return -1;node->key = kvstore_malloc(strlen(key) + 1);if (node->key == NULL){kvstore_free(node);return -1;}memset(node->key, 0, strlen(key) + 1);strcpy(node->key, key);node->value = kvstore_malloc(strlen(value) + 1);if (node->value == NULL){kvstore_free(node->key);kvstore_free(node);return -1;}memset(node->value, 0, strlen(value) + 1);strcpy(node->value, value);rbtree_insert(tree, node);tree->count++;return 0;
}char *kvs_rbtree_get(rbtree_t *tree, char *key)
{rbtree_node *node = rbtree_search(tree, key);if (node == tree->nil){return NULL;}return node->value;
}
int kvs_rbtree_delete(rbtree_t *tree, char *key)
{rbtree_node *node = rbtree_search(tree, key);if (node == tree->nil)return -1;rbtree_node *cur = rbtree_delete(tree, node);if (!cur){kvstore_free(cur->key);kvstore_free(cur->value);kvstore_free(cur);}tree->count--;return 0;
}
int kvs_rbtree_modify(rbtree_t *tree, char *key, char *value)
{rbtree_node *node = rbtree_search(tree, key);if (node == tree->nil){return -1;}char *temp = node->value;kvstore_free(temp);node->value = kvstore_malloc(strlen(value) + 1);if (node->value == NULL)return -1;strcpy(node->value, value);return 0;
}
int kvs_rbtree_count(rbtree_t *tree)
{return tree->count;
}
5.3、hash实现
哈希表其实不是一个很难理解的概念,网上有很多关于哈希表的介绍。哈希表是通过哈希函数定位元素在哈希表的哪个位置。哈希表还存在另一个问题,哈希冲突。
哈希冲突有多种解决方法,具体解决方法可以参考:哈希冲突
本文使用的是链式解决方法。
如果大家对于算法感兴趣可以阅读:Hello算法
里面实现了很多常见的算法,并且具有大多数编程语言实现的版本。
具体实现前需要再头文件中做一些声明
typedef struct hashtable_s hashtable_t;extern hashtable_t Hash;
下面是具体代码实现
#include "kvstore.h"
#include <string.h>#define MAX_KEY_LEN 128
#define MAX_VALUE_LEN 512#define MAX_TABLE_SIZE 102400
#define ENABLE_POINTER_KEY 1typedef struct hashnode_s
{
#if ENABLE_POINTER_KEYchar *key;char *value;
#elsechar key[MAX_KEY_LEN];char value[MAX_VALUE_LEN];
#endifstruct hashnode_s *next;} hashnode_t;typedef struct hashtable_s
{int count;int max_slots;hashnode_t **nodes;
} hashtable_t;hashtable_t Hash;static int _hash(char *key, int size)
{if (!key)return -1;int sum = 0;int i = 0;while (key[i] != 0){sum += key[i];i++;}return sum % size;
}hashnode_t *_create_node(char *key, char *value)
{hashnode_t *node = (hashnode_t *)kvstore_malloc(sizeof(hashnode_t));if (!node)return NULL;#if ENABLE_POINTER_KEYnode->key = kvstore_malloc(strlen(key) + 1);if (!node->key){kvstore_free(node);return NULL;}strcpy(node->key, key);node->value = kvstore_malloc(strlen(value) + 1);if (!node->value){kvstore_free(node->key);kvstore_free(node);return NULL;}strcpy(node->value, value);#endifnode->next = NULL;return node;
}int put_kv_hashtable(hashtable_t *hash, char *key, char *value)
{if (!hash || !key || !value)return -1;int idx = _hash(key, MAX_TABLE_SIZE);hashnode_t *node = hash->nodes[idx];while (node != NULL){if (strcmp(node->key, key) == 0){return 1;}node = node->next;}hashnode_t *new_node = _create_node(key, value);new_node->next = hash->nodes[idx];hash->nodes[idx] = new_node;hash->count++;return 0;
}char *get_kv_hashtable(hashtable_t *hash, char *key)
{if (!hash || !key)return NULL;int idx = _hash(key, MAX_TABLE_SIZE);hashnode_t *node = hash->nodes[idx];while (node != NULL){if (strcmp(node->key, key) == 0){return node->value;}node = node->next;}return NULL;
}int init_hashtable(hashtable_t *hash)
{if (!hash)return -1;hash->nodes = (hashnode_t **)kvstore_malloc(sizeof(hashnode_t *) * MAX_TABLE_SIZE);if (!hash->nodes)return -1;hash->count = 0;hash->max_slots = MAX_TABLE_SIZE;return 0;
}int delete_kv_hashtable(hashtable_t *hash, char *key)
{if (!hash || !key)return -2;int idx = _hash(key, MAX_TABLE_SIZE);hashnode_t *head = hash->nodes[idx];if (head == NULL)return -1;if (strcmp(head->key, key) == 0){hashnode_t *tmp = head->next;hash->nodes[idx] = tmp;
#if ENABLE_POINTER_KEYif (head->key){kvstore_free(head->key);}if (head->value){kvstore_free(head->value);}kvstore_free(head);
#elsefree(head);
#endifhash->count--;return 0;}hashnode_t *cur=head;while (cur->next!=NULL){if(strcmp(cur->next->key,key)==0) break;cur=cur->next;}if(cur->next==NULL){return-1;}hashnode_t *tmp=cur->next;cur->next=tmp->next;
#if ENABLE_POINTER_KEYif (tmp->key) {kvstore_free(tmp->key);}if (tmp->value) {kvstore_free(tmp->value);}kvstore_free(tmp);
#elsefree(tmp);
#endifhash->count --;return 0;}void dest_hashtable(hashtable_t *hash){if(!hash) return;int i=0;for(i=0;i<hash->max_slots;i++){hashnode_t* node=hash->nodes[i];while (node!=NULL){hashnode_t *tmp=node;node=node->next;hash->nodes[i]=node;kvstore_free(tmp);}}kvstore_free(hash->nodes);
}int count_kv_hashtable(hashtable_t *hash) {return hash->count;
}int kvs_hash_count(hashtable_t *hash) {return hash->count;
}int exist_kv_hashtable(hashtable_t *hash, char *key) {char *value = get_kv_hashtable(hash, key);if (value) return 1;else return 0;}int kvs_hash_modify(hashtable_t *hash, char *key, char *value) {if(!hash || !key || !value) return -1;int idx=_hash(key,MAX_TABLE_SIZE);hashnode_t *node=hash->nodes[idx];while(node!=NULL){if(strcmp(node->key,key)==0){kvstore_free(node->value);node->value=kvstore_malloc(strlen(value)+1);if(node->value){strcpy(node->value,value);return 0;}else{assert(0);}}node=node->next;}return -1;
}int kvstore_hash_create(hashtable_t *hash)
{return init_hashtable(hash);
}int kvs_hash_set(hashtable_t *hash, char *key, char *value)
{return put_kv_hashtable(hash, key, value);
}char *kvs_hash_get(hashtable_t *hash, char *key)
{return get_kv_hashtable(hash, key);
}int kvs_hash_delete(hashtable_t *hash, char *key)
{return delete_kv_hashtable(hash, key);
}void kvstore_hash_destory(hashtable_t *hash)
{return dest_hashtable(hash);
}
六、测试
针对上面的框架我们可以制定下面几种测试样例:
针对上面的样例,使用下面的测试代码,进行10w次的CRUD测试
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include <getopt.h>#include <sys/socket.h>
#include <sys/time.h>#include <arpa/inet.h>#define MAX_MAS_LENGTH 512
#define TIME_SUB_MS(tv1, tv2) ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)int connect_tcpserver(const char *ip, unsigned short port)
{int connfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in tcpserver_addr;memset(&tcpserver_addr, 0, sizeof(struct sockaddr_in));tcpserver_addr.sin_family = AF_INET;tcpserver_addr.sin_addr.s_addr = inet_addr(ip);tcpserver_addr.sin_port = htons(port);int ret = connect(connfd, (struct sockaddr *)&tcpserver_addr, sizeof(struct sockaddr_in));if (ret){perror("connect");return -1;}return connfd;
}int send_msg(int connfd, char *msg, int length)
{int res = send(connfd, msg, length, 0);if (res < 0){perror("send error");exit(1);}return res;
}int recv_msg(int connfd, char *msg, int length)
{int res = recv(connfd, msg, length, 0);if (res < 0){perror("recv error");exit(1);}return res;
}void equals(char *pattern, char *result, char *casename)
{if (strcmp(pattern, result) == 0){// printf("==> PASS --> %s\n", casename);}else{printf("==> FAILED --> %s, '%s' != '%s'\n", casename, pattern, result);}
}void test_case(int connfd, char *msg, char *pattern, char *casename)
{if (!msg || !pattern || !casename)return;send_msg(connfd, msg, strlen(msg));char result[MAX_MAS_LENGTH] = {0};recv_msg(connfd, result, MAX_MAS_LENGTH);equals(pattern, result, casename);
}void array_testcase(int connfd)
{test_case(connfd, "SET Name King", "SUCCESS", "SETCase");test_case(connfd, "GET Name", "King", "GETCase");test_case(connfd, "MOD Name Darren", "SUCCESS", "MODCase");test_case(connfd, "GET Name", "Darren", "GETCase");test_case(connfd, "DEL Name", "SUCCESS", "DELCase");test_case(connfd, "GET Name", "NO EXIST", "GETCase");
}void rbtree_testcase(int connfd)
{test_case(connfd, "RSET Name King", "SUCCESS", "SETCase");test_case(connfd, "RGET Name", "King", "GETCase");test_case(connfd, "RMOD Name Darren", "SUCCESS", "MODCase");test_case(connfd, "RGET Name", "Darren", "GETCase");test_case(connfd, "RDEL Name", "SUCCESS", "DELCase");test_case(connfd, "RGET Name", "NO EXIST", "GETCase");
}void hash_testcase(int connfd)
{test_case(connfd, "HSET Name King", "SUCCESS", "HSETCase");test_case(connfd, "HGET Name", "King", "HGETCase");test_case(connfd, "HMOD Name Darren", "SUCCESS", "HMODCase");test_case(connfd, "HGET Name", "Darren", "HGETCase");test_case(connfd, "HDEL Name", "SUCCESS", "HDELCase");test_case(connfd, "HGET Name", "NO EXIST", "HGETCase");
}void array_testcase_10w(int connfd)
{int count = 100000;int i = 0;while (i++ < count){array_testcase(connfd);}
}void rbtree_testcase_10w(int connfd)
{int count = 100000;int i = 0;while (i++ < count){rbtree_testcase(connfd);}
}void hash_testcase_10w(int connfd)
{int count = 100000;int i = 0;while (i++ < count){hash_testcase(connfd);}
}int main(int argc, char *argv[])
{int ret;char ip[16] = {0};int port = 0;int mode = 1;int opt;while ((opt = getopt(argc, argv, "s:p:m:?")) != -1){switch (opt){case 's':strcpy(ip, optarg);break;case 'p':port = atoi(optarg);break;case 'm':mode = atoi(optarg);break;default:return -1;}}int connfd = connect_tcpserver(ip, port);if (mode & 0x1){struct timeval tv_begin;gettimeofday(&tv_begin, NULL);array_testcase_10w(connfd);struct timeval tv_end;gettimeofday(&tv_end, NULL);int time_used = TIME_SUB_MS(tv_end, tv_begin);printf("array testcase--> time_used: %d, qps: %d\n", time_used, 600000 * 1000 / time_used);}if (mode & 0x2){struct timeval tv_begin;gettimeofday(&tv_begin, NULL);rbtree_testcase_10w(connfd);struct timeval tv_end;gettimeofday(&tv_end, NULL);int time_used = TIME_SUB_MS(tv_end, tv_begin);printf("rbtree testcase--> time_used: %d, qps: %d\n", time_used, 600000 * 1000 / time_used);}if (mode & 0x4){struct timeval tv_begin;gettimeofday(&tv_begin, NULL);hash_testcase_10w(connfd);struct timeval tv_end;gettimeofday(&tv_end, NULL);int time_used = TIME_SUB_MS(tv_end, tv_begin);printf("hash testcase--> time_used: %d, qps: %d\n", time_used, 600000 * 1000 / time_used);}return 0;
}
测试结果如下:
整体来说,数据结构相同情况下,io_uring会比epoll的时间要更少,性能更优。数据结构自身对比,hash的qps最高,array的qps最低,说明在KV存储中hash的性能更好(这个结果也可能受限于测试样例,其他类型的测试样例可能是红黑树会优于哈希表,但两者都会优于array)。