Linux下C语言Socket服务端共享内存键值存储实现相关问题
实现建议
问题1:指令拆分与存储逻辑改造方案
你现有Input.c里的逻辑可以直接迁移到服务端通信流程中,仅需做3处适配调整:
- 把原逻辑中从标准输入
gets()读数据的逻辑,替换为从socket连接句柄cfd读取客户端输入,注意要先去除Telnet传输附带的换行符\r\n/\n,避免strtok拆分指令时携带多余无效字符 - 把原逻辑中局部定义的KeyStorage数组调整为全局存储或共享内存存储:原局部变量仅能在单连接单进程场景下生效,多连接场景下数据会丢失,你可以直接用POSIX共享内存接口
shm_open+mmap实现跨进程共享;如果你的服务端暂时用单进程单线程模型,先改成全局变量也能跑通,后续再迭代为共享内存实现即可 - 新增指令合法性校验逻辑:比如GET/DEL命令不需要第三个value参数,PUT必须同时传key和value,避免参数缺失导致的数组越界访问
问题2:GET/PUT/DEL函数调用时机
你理解的没错,就是在server.c读取到客户端输入之后、返回响应输出之前调用,完整执行顺序为:
- 从socket读取客户端发送的一行指令
- 用strtok拆分指令、校验参数合法性
- 根据指令类型调用对应的get/put/del函数,操作共享内存的keyValueStore
- 把函数执行结果按照要求的格式拼装为响应字符串
- 把响应字符串通过socket写回给客户端
可直接运行的改造后代码示例
整合后的server.c代码
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define BUFSIZE 1024 #define PORT 5678 #define MAX_ARRAY 100 #define MAX_KV 100 // 键值对存储结构,后续可直接替换为共享内存实现 typedef struct Value_ { char key[MAX_ARRAY]; char value[MAX_ARRAY]; } KeyStorage; KeyStorage storageKey[MAX_KV]; int kv_count = 0; // 三个操作函数实现 char* put(char* key, char* value) { // 先查找是否存在已有key,存在则更新 for(int i=0; i<kv_count; i++) { if(strcmp(storageKey[i].key, key) == 0) { strcpy(storageKey[i].value, value); static char res[BUFSIZE]; snprintf(res, BUFSIZE, "PUT:%s:%s\r\n", key, value); return res; } } // 不存在则新增 if(kv_count < MAX_KV) { strcpy(storageKey[kv_count].key, key); strcpy(storageKey[kv_count].value, value); kv_count++; static char res[BUFSIZE]; snprintf(res, BUFSIZE, "PUT:%s:%s\r\n", key, value); return res; } return "PUT:error:storage_full\r\n"; } char* get(char* key) { for(int i=0; i<kv_count; i++) { if(strcmp(storageKey[i].key, key) == 0) { static char res[BUFSIZE]; snprintf(res, BUFSIZE, "GET:%s:%s\r\n", key, storageKey[i].value); return res; } } static char res[BUFSIZE]; snprintf(res, BUFSIZE, "GET:%s:key_nonexistent\r\n", key); return res; } char* del(char* key) { for(int i=0; i<kv_count; i++) { if(strcmp(storageKey[i].key, key) == 0) { // 把最后一个元素挪到当前位置,降低移动开销 if(i != kv_count -1) { memcpy(&storageKey[i], &storageKey[kv_count-1], sizeof(KeyStorage)); } kv_count--; static char res[BUFSIZE]; snprintf(res, BUFSIZE, "DEL:%s:key_deleted\r\n", key); return res; } } static char res[BUFSIZE]; snprintf(res, BUFSIZE, "DEL:%s:key_nonexistent\r\n", key); return res; } int main() { int rfd, cfd; struct sockaddr_in client, server; socklen_t client_len; char in[BUFSIZE]; int bytes_read; rfd = socket(AF_INET, SOCK_STREAM, 0); if (rfd < 0 ){ perror("socket error"); exit(-1); } memset(&server, '\0', sizeof(server)); server.sin_family = AF_INET; server.sin_addr.s_addr = INADDR_ANY; server.sin_port = htons(PORT); int brt = bind(rfd, (struct sockaddr *) &server, sizeof(server)); if (brt < 0 ){ perror("bind error"); exit(-1); } int lrt = listen(rfd, 5); if (lrt < 0 ){ perror("listen error"); exit(-1); } while (1) { client_len = sizeof(client); cfd = accept(rfd, (struct sockaddr *) &client, &client_len); if(cfd <0) { perror("accept error"); continue; } memset(in, 0, BUFSIZE); bytes_read = read(cfd, in, BUFSIZE-1); while (bytes_read > 0) { // 去除Telnet传输的换行符 char *p = strstr(in, "\r\n"); if(p) *p = '\0'; p = strstr(in, "\n"); if(p) *p = '\0'; // 拆分指令 char *argv[3] = {0}; int i=0; char *token = strtok(in, " "); while(token != NULL && i<3) { argv[i++] = token; token = strtok(NULL, " "); } char *resp = "ERROR:invalid_command\r\n"; if(argv[0] != NULL) { if(strcmp(argv[0], "PUT") ==0 && argv[1] != NULL && argv[2] != NULL) { resp = put(argv[1], argv[2]); } else if(strcmp(argv[0], "GET") ==0 && argv[1] != NULL) { resp = get(argv[1]); } else if(strcmp(argv[0], "DEL") ==0 && argv[1] != NULL) { resp = del(argv[1]); } } write(cfd, resp, strlen(resp)); memset(in, 0, BUFSIZE); bytes_read = read(cfd, in, BUFSIZE-1); } close(cfd); } close(rfd); return 0; }
共享内存改造说明
如果需要切换为共享内存存储,仅需要把全局的storageKey数组替换为共享内存映射的地址即可,核心步骤为:
- 服务端启动时调用
shm_open创建共享内存对象,用ftruncate设置大小为MAX_KV * sizeof(KeyStorage) - 调用
mmap把共享内存映射到进程地址空间,替换原来的storageKey数组 - 可以加互斥锁避免多连接同时操作共享内存出现竞态问题
内容的提问来源于stack exchange,提问作者user12595983
相关产品推荐
相关产品推荐

