协议定义与实现, 协议支持 批处理、特殊字符如\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:
299
testcase2.c
Normal file
299
testcase2.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user