协议定义与实现, 协议支持 批处理、特殊字符如\r\n\0。与单条命令测试。

/**
 * Header: 	| magic(4) | payloadLen(4) |
 *
 * Request
 * Payload: | opcount(4) | repeat Cmd |
 * Cmd: 	| OP(1) | argc(4) | repeat Arg |
 * Arg:		| arglen(4) | arg |
 *
 * Response
 * Payload: | opcount(4) | repeat Rsp |
 * Rsp:		| OP(1) | status(1) | datalen(4) | data |
 */

 kvstore层,先解析,再执行,最后构建返回体。
 一个是半包问题,没有处理。
 另一个是感觉协议结构有点麻烦,
This commit is contained in:
2026-01-05 23:20:37 +08:00
parent 7524c57442
commit 0dc86f5aa5
10 changed files with 890 additions and 9 deletions

299
testcase2.c Normal file
View File

@@ -0,0 +1,299 @@
// kvs_client_min.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define KVS_MAGIC_U32 0x4B565331u // 'KVS1'
#define KVS_HDR_LEN 16
#define KVS_TYPE_REQ 1
#define KVS_TYPE_RESP 2
// OP mapping (match your server enum)
#define OP_SET 0
#define OP_GET 1
#define OP_DEL 2
#define OP_MOD 3
#define OP_EXIST 4
static int write_u8(uint8_t **pp, uint8_t *end, uint8_t v) {
if (*pp + 1 > end) return -1;
**pp = v;
(*pp)++;
return 0;
}
static int write_u32(uint8_t **pp, uint8_t *end, uint32_t v) {
if (*pp + 4 > end) return -1;
uint32_t be = htonl(v);
memcpy(*pp, &be, 4);
(*pp) += 4;
return 0;
}
static int write_bytes(uint8_t **pp, uint8_t *end, const void *buf, size_t n) {
if (*pp + n > end) return -1;
memcpy(*pp, buf, n);
(*pp) += n;
return 0;
}
static int read_u8(const uint8_t **pp, const uint8_t *end, uint8_t *out) {
if (*pp + 1 > end) return -1;
*out = **pp;
(*pp)++;
return 0;
}
static int read_u32(const uint8_t **pp, const uint8_t *end, uint32_t *out) {
if (*pp + 4 > end) return -1;
uint32_t be;
memcpy(&be, *pp, 4);
*out = ntohl(be);
(*pp) += 4;
return 0;
}
static ssize_t send_all(int fd, const void *buf, size_t len) {
const uint8_t *p = (const uint8_t*)buf;
size_t sent = 0;
while (sent < len) {
ssize_t n = send(fd, p + sent, len - sent, 0);
if (n < 0) {
if (errno == EINTR) continue;
return -1;
}
if (n == 0) return -1;
sent += (size_t)n;
}
return (ssize_t)sent;
}
static ssize_t recv_all(int fd, void *buf, size_t len) {
uint8_t *p = (uint8_t*)buf;
size_t recvd = 0;
while (recvd < len) {
ssize_t n = recv(fd, p + recvd, len - recvd, 0);
if (n < 0) {
if (errno == EINTR) continue;
return -1;
}
if (n == 0) return -1;
recvd += (size_t)n;
}
return (ssize_t)recvd;
}
// Build one request packet: header + payload(opcount + cmds...)
static int build_req_packet(uint32_t reqId,
uint8_t opcount,
uint8_t op,
int argc, const char *argv[],
uint8_t *out, size_t out_cap, size_t *out_len) {
if (!out || out_cap < KVS_HDR_LEN || !out_len) return -1;
uint8_t *w = out + KVS_HDR_LEN;
uint8_t *end = out + out_cap;
// payload: opcount(4)
if (write_u32(&w, end, (uint32_t)opcount) < 0) return -1;
// one cmd (repeat if you want multiple, keep minimal here)
if (write_u8(&w, end, op) < 0) return -1;
if (write_u32(&w, end, (uint32_t)argc) < 0) return -1;
for (int i = 0; i < argc; i++) {
uint32_t alen = (uint32_t)strlen(argv[i]);
if (write_u32(&w, end, alen) < 0) return -1;
if (write_bytes(&w, end, argv[i], alen) < 0) return -1;
}
uint32_t payloadLen = (uint32_t)(w - (out + KVS_HDR_LEN));
uint32_t magic_be = htonl(KVS_MAGIC_U32);
uint32_t payload_be = htonl(payloadLen);
uint32_t reqid_be = htonl(reqId);
// header layout:
// | magic(4) | type(1) | payloadLen(4) | reqId(4) | flag1(1) | flag2(2) |
memcpy(out + 0, &magic_be, 4);
out[4] = KVS_TYPE_REQ;
memcpy(out + 5, &payload_be, 4);
memcpy(out + 9, &reqid_be, 4);
out[13] = 0; // flag1
out[14] = 0; out[15] = 0; // flag2
*out_len = (size_t)(KVS_HDR_LEN + payloadLen);
return 0;
}
/**
* KVS_STATUS_OK = 0,
KVS_STATUS_ERROR = 1,
KVS_STATUS_NO_EXIST = 2,
KVS_STATUS_EXIST = 3,
KVS_STATUS_BADREQ = 4
*/
const char *rsp_status[5] = {"OK", "ERROR", "NO_EXIST", "EXIST", "ERROR"};
static int recv_and_print_resp(int fd) {
uint8_t hdr[KVS_HDR_LEN];
if (recv_all(fd, hdr, KVS_HDR_LEN) < 0) {
perror("recv header");
return -1;
}
uint32_t magic_be, payload_be, reqid_be;
memcpy(&magic_be, hdr + 0, 4);
memcpy(&payload_be, hdr + 5, 4);
memcpy(&reqid_be, hdr + 9, 4);
uint32_t magic = ntohl(magic_be);
uint8_t type = hdr[4];
uint32_t payloadLen = ntohl(payload_be);
uint32_t reqId = ntohl(reqid_be);
if (magic != KVS_MAGIC_U32 || type != KVS_TYPE_RESP) {
fprintf(stderr, "bad response header: magic=0x%x type=%u\n", magic, type);
return -1;
}
uint8_t *payload = (uint8_t*)malloc(payloadLen);
if (!payload) return -1;
if (payloadLen > 0) {
if (recv_all(fd, payload, payloadLen) < 0) {
perror("recv payload");
free(payload);
return -1;
}
}
// parse response payload:
// | opcount(4) | repeat Cmd |
// Cmd: | status(1) | datalen(4) | data |
const uint8_t *p = payload;
const uint8_t *end = payload + payloadLen;
uint32_t opcount = 0;
if (read_u32(&p, end, &opcount) < 0) {
fprintf(stderr, "resp parse failed\n");
free(payload);
return -1;
}
printf("RESP reqId=%u opcount=%u\n", reqId, opcount);
for (uint32_t i = 0; i < opcount; i++) {
uint8_t status = 0;
uint32_t dlen = 0;
if (read_u8(&p, end, &status) < 0) goto bad;
if (read_u32(&p, end, &dlen) < 0) goto bad;
if (p + dlen > end) goto bad;
printf(" op[%u]: status=%s datalen=%u", i, rsp_status[status], dlen);
if (dlen > 0) {
// printf(" data=\"%.*s\"", (int)dlen, (const char*)p);
printf(" data=\"");
for (int i = 0; i < dlen; i++) {
printf("%02X", (unsigned char)p[i]);
if (i + 1 < dlen)
printf(" ");
}
printf("\"");
}
printf("\n");
p += dlen;
}
free(payload);
return 0;
bad:
fprintf(stderr, "resp parse failed (truncated)\n");
free(payload);
return -1;
}
uint8_t buf[4096];
size_t pkt_len = 0;
uint32_t reqId = 1;
int fd;
int testcase(int op, const char *args[], int argnum){
if (build_req_packet(reqId++, 1, op, argnum, args, buf, sizeof(buf), &pkt_len) < 0) {
fprintf(stderr, "build failed\n");
close(fd);
return 1;
}
if (send_all(fd, buf, pkt_len) < 0) { perror("send SET"); close(fd); return 1; }
if (recv_and_print_resp(fd) < 0) { close(fd); return 1; }
return 0;
}
#define BUF_CAP 65536
int main(int argc, char **argv) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
return 1;
}
const char *ip = argv[1];
int port = atoi(argv[2]);
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) { perror("socket"); return 1; }
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons((uint16_t)port);
if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) {
fprintf(stderr, "bad ip\n");
close(fd);
return 1;
}
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("connect");
close(fd);
return 1;
}
size_t pkt_len = 0;
uint32_t reqId = 1;
// ---- SET key value ----
{
const char *args[] = {"foo", "bar"};
printf("SET ");
for (const unsigned char *p = (const unsigned char *)args[1]; *p; p++) {
printf("%02X ", *p);
}
printf("\n");
testcase(OP_SET, args, 2);
}
// ---- GET key ----
{
const char *args[] = {"foo"};
testcase(OP_GET, args, 1);
}
// ---- SET key ----
{
const char *args[] = {"text", "\r\n\0aaaa\r\n\0"};
printf("SET ");
for (const unsigned char *p = (const unsigned char *)args[1]; *p; p++) {
printf("%02X ", *p);
}
printf("\n");
testcase(OP_SET, args, 2);
}
// ---- GET key ----
{
const char *args[] = {"text"};
testcase(OP_GET, args, 1);
}
close(fd);
return 0;
}