简化协议,

/**
 * Request
 * Cmd: 	| OP(1) | argc(1) | repeat { arglen(4) | arg } |
 *
 * Response
 * Rsp:		| OP(1) | status(1) | datalen(4) | data |
 */

封装客户端进行批处理和单条命令测试。
This commit is contained in:
2026-01-06 19:16:12 +08:00
parent 0dc86f5aa5
commit 144b374aa2
13 changed files with 815 additions and 634 deletions

221
test/test_client.c Normal file
View File

@@ -0,0 +1,221 @@
#include "test_client.h"
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
int kvs_need(const uint8_t *p, const uint8_t *end, size_t n) {
return (p + n <= end) ? 0 : -1;
}
// 注意u8类型不需要ntoh或者hton
int kvs_read_u8(const uint8_t **pp, const uint8_t *end, uint8_t *out) {
const uint8_t *p = *pp;
if (kvs_need(p, end, 1) < 0) return -1;
*out = *p;
*pp = p + 1;
return 0;
}
int kvs_read_u16(const uint8_t **pp, const uint8_t *end, uint16_t *out) {
const uint8_t *p = *pp;
if (kvs_need(p, end, 2) < 0) return -1;
uint16_t v;
memcpy(&v, p, 2);
*out = ntohs(v);
*pp = p + 2;
return 0;
}
int kvs_read_u32(const uint8_t **pp, const uint8_t *end, uint32_t *out) {
const uint8_t *p = *pp;
if (kvs_need(p, end, 4) < 0) return -1;
uint32_t v;
memcpy(&v, p, 4);
*out = ntohl(v);
*pp = p + 4;
return 0;
}
int kvs_write_u8(uint8_t **pp, const uint8_t *end, uint8_t v) {
uint8_t *p = *pp;
if (kvs_need(p, end, 1) < 0) return -1;
*p = v;
*pp = p + 1;
return 0;
}
int kvs_write_u16(uint8_t **pp, const uint8_t *end, uint16_t v) {
uint8_t *p = *pp;
if (kvs_need(p, end, 2) < 0) return -1;
uint16_t be = htons(v);
memcpy(p, &be, 2);
*pp = p + 2;
return 0;
}
int kvs_write_u32(uint8_t **pp, const uint8_t *end, uint32_t v) {
uint8_t *p = *pp;
if (kvs_need(p, end, 4) < 0) return -1;
uint32_t be = htonl(v);
memcpy(p, &be, 4);
*pp = p + 4;
return 0;
}
int getcmd(uint8_t op, const char *key, const char *value, uint8_t *buf){
if(!key || !buf) return -1;
uint8_t *end = buf + CMD_SIZE;
uint8_t *p = buf;
uint8_t argc = ((value == NULL)?1 : 2);
if (kvs_write_u8(&p, end, op) < 0) return -1;
if (kvs_write_u8(&p, end, argc) < 0) return -1;
// 写入 key
int keylen = strlen(key);
if (kvs_write_u32(&p, end, keylen) < 0) return -1;
if (kvs_need(p, end, keylen) < 0) return -1;
if (keylen > 0) {
memcpy(p, key, keylen);
p += keylen;
}
if(value){
int vallen = strlen(value);
if (kvs_write_u32(&p, end, vallen) < 0) return -1;
if (kvs_need(p, end, vallen) < 0) return -1;
if (vallen > 0) {
memcpy(p, value, vallen);
p += vallen;
}
}
return (p - buf);
}
int parse_response(const uint8_t *buf, int buflen, kvs_response_t *rsp) {
const uint8_t *p = buf;
const uint8_t *end = buf + buflen;
// 读取 OP
if (kvs_read_u8(&p, end, &rsp->op) < 0) {
fprintf(stderr, "Failed to read op\n");
return -1;
}
// 读取 status
if (kvs_read_u8(&p, end, &rsp->status) < 0) {
fprintf(stderr, "Failed to read status\n");
return -1;
}
// 读取 datalen
if (kvs_read_u32(&p, end, &rsp->datalen) < 0) {
fprintf(stderr, "Failed to read datalen\n");
return -1;
}
// 检查数据长度
if (kvs_need(p, end, rsp->datalen) < 0) {
fprintf(stderr, "Data length mismatch: expected %u bytes, but only %ld available\n",
rsp->datalen, end - p);
return -1;
}
// 指向数据部分
rsp->data = (uint8_t *)p;
return (p - buf) + rsp->datalen;
}
void print_response(const char *cmd_name, const kvs_response_t *rsp) {
printf("\n=== %s Response ===\n", cmd_name);
printf("OP: %u\n", rsp->op);
printf("Status: %u ", rsp->status);
switch (rsp->status) {
case KVS_STATUS_OK:
printf("(OK)\n");
break;
case KVS_STATUS_ERROR:
printf("(ERROR)\n");
break;
case KVS_STATUS_NO_EXIST:
printf("(NO_EXIST)\n");
break;
case KVS_STATUS_EXIST:
printf("(EXISTS)\n");
break;
default:
printf("(UNKNOWN)\n");
break;
}
printf("Data Length: %u\n", rsp->datalen);
if (rsp->datalen > 0 && rsp->data != NULL) {
printf("Data: ");
// 尝试以字符串形式打印(如果是可打印字符)
int is_printable = 1;
for (uint32_t i = 0; i < rsp->datalen; i++) {
if (rsp->data[i] < 32 || rsp->data[i] > 126) {
is_printable = 0;
break;
}
}
if (is_printable) {
printf("\"");
for (uint32_t i = 0; i < rsp->datalen; i++) {
printf("%c", rsp->data[i]);
}
printf("\"\n");
} else {
// 以十六进制打印
printf("0x");
for (uint32_t i = 0; i < rsp->datalen; i++) {
printf("%02x", rsp->data[i]);
}
printf("\n");
}
} else {
printf("Data: (empty)\n");
}
printf("==================\n");
}
int verify_response(const kvs_response_t *rsp, uint8_t expected_op,
uint8_t expected_status, const char *expected_data) {
if (rsp->op != expected_op) {
printf("❌ OP mismatch: expected %u, got %u\n", expected_op, rsp->op);
return 0;
}
if (rsp->status != expected_status) {
printf("❌ Status mismatch: expected %u, got %u\n", expected_status, rsp->status);
return 0;
}
if (expected_data != NULL) {
uint32_t expected_len = strlen(expected_data);
if (rsp->datalen != expected_len) {
printf("❌ Data length mismatch: expected %u, got %u\n", expected_len, rsp->datalen);
return 0;
}
if (memcmp(rsp->data, expected_data, expected_len) != 0) {
printf("❌ Data content mismatch\n");
return 0;
}
}
return 1;
}

154
test/test_client.h Normal file
View File

@@ -0,0 +1,154 @@
/**
* Request
* Cmd: | OP(1) | argc(1) | repeat { arglen(4) | arg } |
*
* Response
* Rsp: | OP(1) | status(1) | datalen(4) | data |
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define CMD_SIZE (4096)
#define BATCH_SIZE (65536)
#define TIME_SUB_MS(tv1, tv2) ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)
#define PRESP print_response
typedef enum {
KVS_STATUS_OK = 0,
KVS_STATUS_ERROR = 1,
KVS_STATUS_NO_EXIST = 2,
KVS_STATUS_EXIST = 3,
KVS_STATUS_BADREQ = 4
}rsp_ret_status_e;
enum {
KVS_CMD_START = 0,
// array
KVS_CMD_SET = KVS_CMD_START,
KVS_CMD_GET,
KVS_CMD_DEL,
KVS_CMD_MOD,
KVS_CMD_EXIST,
// rbtree
KVS_CMD_RSET,
KVS_CMD_RGET,
KVS_CMD_RDEL,
KVS_CMD_RMOD,
KVS_CMD_REXIST,
// hash
KVS_CMD_HSET,
KVS_CMD_HGET,
KVS_CMD_HDEL,
KVS_CMD_HMOD,
KVS_CMD_HEXIST,
KVS_CMD_COUNT,
};
typedef struct {
uint8_t op;
uint8_t status;
uint32_t datalen;
uint8_t *data;
} kvs_response_t;
int kvs_need(const uint8_t *p, const uint8_t *end, size_t n);
int kvs_read_u8(const uint8_t **pp, const uint8_t *end, uint8_t *out);
int kvs_read_u16(const uint8_t **pp, const uint8_t *end, uint16_t *out);
int kvs_read_u32(const uint8_t **pp, const uint8_t *end, uint32_t *out);
int kvs_write_u8(uint8_t **pp, const uint8_t *end, uint8_t v);
int kvs_write_u16(uint8_t **pp, const uint8_t *end, uint16_t v);
int kvs_write_u32(uint8_t **pp, const uint8_t *end, uint32_t v);
int getcmd(uint8_t op, const char *key, const char *value, uint8_t *buf);
int parse_response(const uint8_t *buf, int buflen, kvs_response_t *rsp);
void print_response(const char *cmd_name, const kvs_response_t *rsp);
int verify_response(const kvs_response_t *rsp, uint8_t expected_op,
uint8_t expected_status, const char *expected_data);
#define KVS_BATCH_MAX 64
typedef struct {
uint8_t buf[BATCH_SIZE];
int len; // 当前已写入的 batch 字节数
int cnt; // 当前 batch 里命令条数
int cmd_len[KVS_BATCH_MAX];
} kvs_batch_t;
static void kvs_batch_init(kvs_batch_t *b)
{
b->len = 0;
b->cnt = 0;
memset(b->cmd_len, 0, sizeof(b->cmd_len));
}
/**
* 用 getcmd() 生成单条命令,然后 append 到 batch buffer
* 返回0 成功,-1 失败(太多条 or buffer 不够)
*/
static int kvs_batch_add(kvs_batch_t *b, uint8_t op, const char *key, const char *value)
{
if (b->cnt >= KVS_BATCH_MAX) return -1;
uint8_t tmp[CMD_SIZE];
int n = getcmd(op, key, value, tmp); // 你提供的函数
if (n <= 0) return -1;
if (b->len + n > (int)sizeof(b->buf)) return -1;
memcpy(b->buf + b->len, tmp, n);
b->cmd_len[b->cnt] = n;
b->cnt++;
b->len += n;
return 0;
}
/**
* 一次性发送 batch
* 返回:发送字节数,<0 表示失败
*/
static int kvs_batch_send(int fd, const kvs_batch_t *b)
{
return (int)send(fd, b->buf, b->len, 0);
}
/**
* 一次 recv 收回所有响应,然后批量解析为 rsp 数组
*
* 返回:成功解析出的响应条数(期望是 b->cnt
*/
static int kvs_batch_recv_parse(int fd,
const kvs_batch_t *b,
kvs_response_t *rsps, // 输出数组,长度 >= b->cnt
uint8_t *recvbuf,
int recvbuf_cap)
{
int nrecv = (int)recv(fd, recvbuf, recvbuf_cap, 0);
if (nrecv <= 0) return -1;
int off = 0;
int parsed = 0;
while (parsed < b->cnt && off < nrecv) {
int consumed = parse_response(recvbuf + off, nrecv - off, &rsps[parsed]);
if (consumed <= 0) break; // 不够解析/失败,简单处理:直接退出
off += consumed;
parsed++;
}
return parsed;
}

143
test/testcase.c Normal file
View File

@@ -0,0 +1,143 @@
#include "test_client.h"
#include <arpa/inet.h>
int connect_tcpserver(const char *ip, unsigned short port) {
int connfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(ip);
server_addr.sin_port = htons(port);
if (0 != connect(connfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in))) {
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");
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");
exit(1);
}
return res;
}
void testcase(int connfd, uint8_t op, const char* key, const char* value, rsp_ret_status_e st, const char* rsp_value, const char* command_name){
uint8_t buf[CMD_SIZE];
uint8_t result[CMD_SIZE];
kvs_response_t rsp;
int len, recv_len;
len = getcmd(op, key, value, buf);
send_msg(connfd, buf, len);
recv_len = recv_msg(connfd, result, CMD_SIZE);
if (parse_response(result, recv_len, &rsp) > 0) {
PRESP(command_name, &rsp);
if(!verify_response(&rsp, op, st, rsp_value)) printf("%s\n", command_name);
}else{
printf("parser error\n");
}
return ;
}
void array_testcase_1w(int connfd) {
int count = 1;
int i = 0;
struct timeval tv_begin;
gettimeofday(&tv_begin, NULL);
for (i = 0;i < count;i ++) {
testcase(connfd, KVS_CMD_SET, "nage", "lian", KVS_STATUS_OK, NULL, "SET NAME");
testcase(connfd, KVS_CMD_GET, "nage", NULL, KVS_STATUS_OK, "lian", "GET NAME");
testcase(connfd, KVS_CMD_MOD, "nage", "liu", KVS_STATUS_OK, NULL, "MOD NAME");
testcase(connfd, KVS_CMD_GET, "nage", NULL, KVS_STATUS_OK, "liu", "GET NAME");
testcase(connfd, KVS_CMD_EXIST, "nage", NULL, KVS_STATUS_EXIST, NULL, "EXIST NAME");
testcase(connfd, KVS_CMD_DEL, "nage", NULL, KVS_STATUS_OK, NULL, "DEL NAME");
testcase(connfd, KVS_CMD_EXIST, "nage", NULL, KVS_STATUS_NO_EXIST, NULL, "NOT EXIST NAME");
}
struct timeval tv_end;
gettimeofday(&tv_end, NULL);
int time_used = TIME_SUB_MS(tv_end, tv_begin); // ms
printf("array testcase --> time_used: %d, qps: %d\n", time_used, 70000 * 1000 / time_used);
}
void do_batch_example(int fd)
{
kvs_batch_t batch;
kvs_batch_init(&batch);
// 组 batch最多 64 条)
kvs_batch_add(&batch, KVS_CMD_SET, "k1", "v1");
kvs_batch_add(&batch, KVS_CMD_SET, "k2", "v2");
kvs_batch_add(&batch, KVS_CMD_GET, "k1", NULL);
kvs_batch_add(&batch, KVS_CMD_GET, "k2", NULL);
// 一次性发送
kvs_batch_send(fd, &batch);
// 一次性 recv + parse
uint8_t recvbuf[BATCH_SIZE];
kvs_response_t rsps[KVS_BATCH_MAX];
int nrsp = kvs_batch_recv_parse(fd, &batch, rsps, recvbuf, sizeof(recvbuf));
// 打印/处理
for (int i = 0; i < nrsp; i++) {
PRESP("BATCH", &rsps[i]);
}
printf("%d\n", nrsp);
testcase(fd, KVS_CMD_GET, "k1", NULL, KVS_STATUS_OK, "v1", "GET k1");
testcase(fd, KVS_CMD_GET, "k2", NULL, KVS_STATUS_OK, "v2", "GET k2");
}
int main(int argc, char *argv[]) {
if (argc != 3) {
printf("arg error\n");
return -1;
}
char *ip = argv[1];
int port = atoi(argv[2]);
int connfd = connect_tcpserver(ip, port);
// array_testcase_1w(connfd);
do_batch_example(connfd);
return 0;
}