文章目录
-
- 一、核心目标与事件体系构建
-
- [1.1 构建两类核心事件对象](#1.1 构建两类核心事件对象)
- [1.2 适配 Reactor 事件循环](#1.2 适配 Reactor 事件循环)
- [1.3 hiredis 封装规则与适配要点](#1.3 hiredis 封装规则与适配要点)
- [二、Redis 异步连接与实现方案](#二、Redis 异步连接与实现方案)
-
- [2.1 同步与异步连接的核心差异](#2.1 同步与异步连接的核心差异)
- [2.2 异步连接的协议界定](#2.2 异步连接的协议界定)
- [2.3 基于 hiredis + libevent 的实现方案](#2.3 基于 hiredis + libevent 的实现方案)
-
- [2.3.1 核心结构体:redisContext](#2.3.1 核心结构体:redisContext)
- [2.3.2 事件绑定函数:redisLibeventAttach](#2.3.2 事件绑定函数:redisLibeventAttach)
- [2.4 Reactor 处理 Redis 异步连接](#2.4 Reactor 处理 Redis 异步连接)
- [三、Reactor 网络模型与 Redis 驱动的适配细节](#三、Reactor 网络模型与 Redis 驱动的适配细节)
-
- [3.1 Reactor 核心事件结构体定义](#3.1 Reactor 核心事件结构体定义)
- [3.2 Redis 事件与 Reactor 事件的适配设计(C 语言"继承")](#3.2 Redis 事件与 Reactor 事件的适配设计(C 语言“继承”))
- [3.3 Reactor 事件注册与循环的核心代码解析](#3.3 Reactor 事件注册与循环的核心代码解析)
-
- [3.3.1 事件注册函数(add_event)](#3.3.1 事件注册函数(add_event))
- [3.3.2 事件循环函数(eventloop_once)](#3.3.2 事件循环函数(eventloop_once))
- [3.3.3 Redis 事件更新函数(redisEventUpdate)](#3.3.3 Redis 事件更新函数(redisEventUpdate))
- [3.4 hiredis 事件接口与 Reactor 函数的适配](#3.4 hiredis 事件接口与 Reactor 函数的适配)
- [四、自定义 Redis 协议实现](#四、自定义 Redis 协议实现)
-
- [4.1 自定义协议解压缩(解析 Redis 响应)](#4.1 自定义协议解压缩(解析 Redis 响应))
-
- [4.1.1 读取协议行:readline](#4.1.1 读取协议行:readline)
- [4.1.2 核心解析函数:read_sub_response](#4.1.2 核心解析函数:read_sub_response)
- [4.1.3 响应读取入口:read_response](#4.1.3 响应读取入口:read_response)
- [4.2 自定义协议压缩(构造 Redis 请求)](#4.2 自定义协议压缩(构造 Redis 请求))
-
- [4.2.1 写入数组元素个数:write_count](#4.2.1 写入数组元素个数:write_count)
- [4.2.2 写入字符串类型头:write_header](#4.2.2 写入字符串类型头:write_header)
- [4.2.3 写入 Redis 命令:write_command](#4.2.3 写入 Redis 命令:write_command)
- [4.2.4 请求构造入口:RunCommand](#4.2.4 请求构造入口:RunCommand)
一、核心目标与事件体系构建
核心目标是将 Redis 连接管理与 Reactor 事件驱动模式 融合,利用Reactor的高效事件调度来处理Redis连接的事件(如网络IO、命令响应等)。
我们有两种事件对象:hiredis事件对象和Reactor事件对象,需要将它们进行关联。为了避免重复造轮子,我们适配项目已有的 Reactor 事件循环,让Redis的事件能在Reactor中被调度。
hiredis 提供了灵活的接口,允许自行实现IO操作和事件操作接口,我们需要将这些接口与项目的事件处理机制适配。
1.1 构建两类核心事件对象
为让 Redis 事件融入 Reactor 体系,需定义并关联两类事件对象,确保事件能被 Reactor 识别和调度:
- hiredis 事件对象 :由 Redis 客户端库
hiredis原生定义,用于描述 Redis 交互相关的事件,如连接建立、数据读写、命令响应等。 - Reactor 事件对象 :项目中 Reactor 模式使用的事件结构体,需与
hiredis事件对象做关联或适配(如通过指针绑定、字段嵌套),使 Redis 事件能接入 Reactor 的事件循环流程。
1.2 适配 Reactor 事件循环
为了避免重复开发事件循环逻辑,Redis 驱动需要适配项目已有的 Reactor 事件循环。具体通过实现 hiredis 要求的事件操作接口来完成:
- addRead:注册读事件到 Reactor
- delRead:从 Reactor 注销读事件
- addWrite:注册写事件到 Reactor
- delWrite:从 Reactor 注销写事件
- cleanup:清理事件资源
- scheduleTimer:设置超时定时器
1.3 hiredis 封装规则与适配要点
hiredis 对事件与 IO 的封装具有很好的灵活性,主要体现在:
IO 自主实现支持 :hiredis 不强制依赖内部 IO 逻辑,允许使用者根据项目需求自定义 IO 操作(如连接建立、数据收发),只需适配其提供的接口即可。
事件操作接口适配 :hiredis 提供统一的事件操作接口(如事件注册、删除、触发、定时器调度),适配时需将这些接口与项目 Reactor 的事件接口对齐(如把 hiredis 的 addRead 映射到 Reactor 的 add_event),让 Reactor 接管 hiredis 的事件生命周期。
多库/多平台兼容 :不同网络库(如 libevent、libuv)、不同操作系统对事件操作的接口定义(函数签名、参数格式)存在差异,适配时需针对这些差异做兼容处理,确保 Redis 驱动在不同环境下都能与事件系统协同工作。
二、Redis 异步连接与实现方案
Redis 连接分为同步与异步两种模式,异步模式基于非阻塞 IO 实现,是适配 Reactor 模式的核心方向,以下详细说明其原理、实现与选择依据。
2.1 同步与异步连接的核心差异
Redis 同步连接采用阻塞IO,当发送命令后,当前线程会阻塞直至Redis返回结果。优点是代码书写是同步的,业务逻辑不会被割裂,易于理解和维护。缺点是阻塞当前线程,在高并发场景下效率较低;通常需要通过多线程池来提升处理能力。
Redis 异步连接采用非阻塞 IO 实现,其优点是不会阻塞当前线程,即使 Redis 未返回结果,仍可继续向 Redis 发送其他命令,并发效率更高;但缺点是代码需通过回调函数实现,易导致业务逻辑割裂,该问题可通过协程(如 openresty、skynet)解决。此外,异步连接配合 Redis 6.0 及以上版本的 IO 多线程(需大量并发请求场景)、异步连接池,能进一步提升应用层的数据访问性能。
2.2 异步连接的协议界定
实现 Redis 协议的第一步是明确数据包的界定方式,主要有两种:
- 长度 + 二进制流:先传输数据的长度信息,再传输对应的二进制数据,通过长度确定数据包边界,避免粘包问题。
- 二进制流 + 特殊分隔符 :通过特定字符(如
\r\n)作为数据包的结束标识,解析时通过识别分隔符确定数据包边界,Redis 原生协议即采用此方式。
2.3 基于 hiredis + libevent 的实现方案
hiredis 可与 libevent(轻量级事件库)结合实现 Redis 异步连接,核心依赖 hiredis 的异步上下文结构体与事件绑定函数,以下为关键代码与解析:
2.3.1 核心结构体:redisContext
redisContext 是 hiredis 中描述 Redis 连接上下文的核心结构体,存储连接的 IO 句柄、错误信息、读写缓冲区、协议解析器等关键信息,部分字段含义如下:
c
typedef struct redisContext {
const redisContextFuncs *funcs; // 函数表,存储事件操作函数指针
int err; // 错误标志,0 表示无错误
char errstr[128]; // 错误信息字符串
redisFD fd; // 连接的文件描述符(socket)
char *obuf; // 写缓冲区,存储待发送给 Redis 的数据
redisReader *reader; // 协议解析器,处理 Redis 响应数据
enum redisConnectionType connection_type; // 连接类型(TCP/UNIX 域套接字)
void *privdata; // 用户自定义私有数据,hiredis 不直接使用
void (*free_privdata)(void *); // 私有数据的释放回调函数
} redisContext;
2.3.2 事件绑定函数:redisLibeventAttach
该函数用于将 hiredis 的异步上下文(redisAsyncContext)与 libevent 的事件底座(event_base)绑定,实现 hiredis 事件与 libevent 事件循环的对接,关键逻辑如下:
c
static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
redisContext *c = &(ac->c);
redisLibeventEvents *e; // 存储 hiredis 与 libevent 事件关联的容器
// 若已绑定事件底座,直接返回错误
if (ac->ev.data != NULL) return REDIS_ERR;
// 分配事件容器内存
e = (redisLibeventEvents*)hi_calloc(1, sizeof(*e));
if (e == NULL) return REDIS_ERR;
e->context = ac; // 绑定 hiredis 异步上下文
e->base = base; // 绑定 libevent 事件底座
// 替换 hiredis 的事件操作接口,让 libevent 接管事件
ac->ev.addRead = redisLibeventAddRead; // 注册读事件
ac->ev.delRead = redisLibeventDelRead; // 注销读事件
ac->ev.addWrite = redisLibeventAddWrite; // 注册写事件
ac->ev.delWrite = redisLibeventDelWrite; // 注销写事件
ac->ev.cleanup = redisLibeventCleanup; // 事件清理
ac->ev.scheduleTimer = redisLibeventSetTimeout; // 定时器调度
ac->ev.data = e; // 存储事件容器,供后续操作使用
// 创建 libevent 事件(监听读/写事件),绑定事件处理函数 redisLibeventHandler
e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e);
return REDIS_OK;
}
2.4 Reactor 处理 Redis 异步连接
eactor 模式通过事件驱动机制处理 Redis 连接的建立、数据读写等过程,具体步骤如下:
- 创建非阻塞 Socket 并发起连接 :先创建 Socket 并设置为非阻塞模式,调用
connect后会立刻返回(无论连接是否成功),避免线程阻塞。 - 通过写事件确认连接建立 :连接建立前,Reactor 需为该 Socket 注册写事件;当写事件触发时,说明 Redis 连接已建立(底层三次握手完成)。
- 注销写事件并注册读事件 :连接建立后必须注销写事件。
原因是:- 连接建立前,epoll 检测的是三次握手的同步包;
- 建立后,epoll 会转为检测 Socket 的发送缓冲区
- 如果不注销,写事件会因发送缓冲区非满而持续触发,导致资源浪费。
- 注销后注册读事件,用于监听 Redis 的响应数据。
- 数据发送与事件调整:向 Redis 发送命令时,若发送失败或数据未发送完整(如发送缓冲区已满),需重新注册写事件;待下次写事件触发(发送缓冲区有空间)时,继续发送剩余数据。
- 响应数据处理 :当 Redis 返回响应数据时,读事件触发,Reactor 调用对应的读事件处理函数,通过
hiredis的协议解析器解析数据,完成业务逻辑。
三、Reactor 网络模型与 Redis 驱动的适配细节
Reactor 网络模型是 Redis 异步驱动的核心底座,需通过事件结构体设计、接口适配、事件操作函数实现,确保 Redis 事件能被 Reactor 正确调度。
3.1 Reactor 核心事件结构体定义
Reactor 模式通过 event_t 结构体描述一个事件,包含事件关联的文件描述符、Reactor 实例、缓冲区、回调函数等,是 Reactor 识别事件的基础,定义如下:
c
struct event_s {
int fd; // 事件关联的文件描述符(如 Redis 连接的 Socket)
reactor_t *r; // 关联的 Reactor 实例
buffer_t *in; // 读缓冲区,存储从 Socket 读取的数据
buffer_t *out; // 写缓冲区,存储待写入 Socket 的数据
event_callback_fn read_fn; // 读事件回调函数(事件触发时执行)
event_callback_fn write_fn; // 写事件回调函数
error_callback_fn error_fn; // 错误事件回调函数
};
3.2 Redis 事件与 Reactor 事件的适配设计(C 语言"继承")
Reactor 仅能识别 event_t 结构体,而 Redis 驱动需关联 hiredis 的异步上下文(redisAsyncContext),因此通过 结构体字段嵌套 实现"继承"效果(C 语言中,结构体起始地址相同可强转),定义 redis_event_t 结构体如下:
c
typedef struct {
event_t e; // 嵌套 Reactor 事件结构体,作为起始字段
int mask; // 事件掩码(如 EPOLLIN、EPOLLOUT)
redisAsyncContext *ctx; // 关联的 hiredis 异步上下文
} redis_event_t;
适配逻辑:创建 redis_event_t 实例后,取其 e 字段(event_t 类型)注册到 Reactor;当 Reactor 触发事件并返回 event_t* 时,可将其强转为 redis_event_t*,从而访问 ctx 字段操作 Redis 连接,实现 Reactor 事件与 Redis 上下文的绑定。
3.3 Reactor 事件注册与循环的核心代码解析
Reactor 通过 epoll 实现事件检测,核心包含事件注册(add_event)、事件循环(eventloop_once)、事件更新(redisEventUpdate)等函数,确保 Redis 事件能被正确调度。
3.3.1 事件注册函数(add_event)
该函数将 event_t 注册到 Reactor 的 epoll 实例,关联事件掩码(如读/写事件),代码如下:
c
int add_event(reactor_t *R, int events, event_t *e) {
struct epoll_event ev;
ev.events = events; // 设置事件类型(如 EPOLLIN | EPOLLOUT)
ev.data.ptr = e; // 绑定事件结构体,供 epoll_wait 返回时使用
// 调用 epoll_ctl 注册事件,EPOLL_CTL_ADD 表示新增事件
if (epoll_ctl(R->epfd, EPOLL_CTL_ADD, e->fd, &ev) == -1) {
printf("add event err fd = %d\n", e->fd);
return 1;
}
return 0;
}
3.3.2 事件循环函数(eventloop_once)
该函数是 Reactor 的核心,通过 epoll_wait 等待事件触发,再根据事件类型调用对应的回调函数,代码如下:
c
void eventloop_once(reactor_t * r, int timeout) {
// 等待事件触发,timeout 为超时时间(毫秒),返回触发的事件数
int n = epoll_wait(r->epfd, r->fire, MAX_EVENT_NUM, timeout);
for (int i = 0; i < n; i++) {
struct epoll_event *e = &r->fire[i];
int mask = e->events;
// 若触发错误或挂断事件,同时标记读/写事件(确保后续处理)
if (e->events & EPOLLERR) mask |= EPOLLIN | EPOLLOUT;
if (e->events & EPOLLHUP) mask |= EPOLLIN | EPOLLOUT;
// 将 epoll 返回的事件数据强转为 event_t*
event_t *et = (event_t*) e->data.ptr;
// 触发读事件,调用读回调函数
if (mask & EPOLLIN && et->read_fn) {
et->read_fn(et->fd, EPOLLIN, et);
}
// 触发写事件,调用写回调函数;若无回调,直接从缓冲区写数据
if (mask & EPOLLOUT) {
if (et->write_fn) {
et->write_fn(et->fd, EPOLLOUT, et);
} else {
uint8_t * buf = buffer_write_atmost(evbuf_out(et));
event_buffer_write(et, buf, buffer_len(evbuf_out(et)));
}
}
}
}
3.3.3 Redis 事件更新函数(redisEventUpdate)
该函数通过位运算调整 redis_event_t 的事件掩码,动态注册/注销 Reactor 事件(如添加读事件、删除写事件),代码逻辑如下:
c
static void redisEventUpdate(void *privdata, int flag, int remove) {
redis_event_t *re = (redis_event_t *)privdata; // 强转为 Redis 事件结构体
reactor_t *r = re->e.r;
int prevMask = re->mask; // 调整前的事件掩码
int enable = 0;
// 处理事件注销:从掩码中移除目标事件(如 EPOLLOUT)
if (remove) {
if ((re->mask & flag) == 0) return; // 若未注册该事件,直接返回
re->mask &= ~flag;
enable = 0;
}
// 处理事件注册:向掩码中添加目标事件(如 EPOLLIN)
else {
if (re->mask & flag) return; // 若已注册该事件,直接返回
re->mask |= flag;
enable = 1;
}
int fd = re->ctx->c.fd;
// 若事件掩码为空,删除该事件(从 epoll 中移除)
if (re->mask == 0) {
del_event(r, &re->e);
}
// 若之前无事件,新增事件(注册到 epoll)
else if (prevMask == 0) {
add_event(r, re->mask, &re->e);
}
// 若已有事件,更新事件状态(启用/禁用读/写事件)
else {
if (flag & EPOLLIN) {
enable_event(r, &re->e, enable, 0); // 更新读事件
} else if (flag & EPOLLOUT) {
enable_event(r, &re->e, 0, enable); // 更新写事件
}
}
}
3.4 hiredis 事件接口与 Reactor 函数的适配
hiredis 的异步上下文(redisAsyncContext)通过 ev 字段存储事件操作函数,适配时需将这些函数映射到 Reactor 的事件操作(add_event、del_event 等),核心适配代码如下:
c
static int redisAttach(reactor_t *r, redisAsyncContext *ac) {
redisContext *c = &(ac->c);
redis_event_t *re;
// 若已绑定 Reactor,直接返回错误
if (ac->ev.data != NULL) return REDIS_ERR;
// 分配 Redis 事件结构体内存
re = (redis_event_t*)hi_malloc(sizeof(*re));
if (re == NULL) return REDIS_ERR;
// 绑定 hiredis 上下文与 Reactor 实例
re->ctx = ac;
re->e.fd = c->fd;
re->e.r = r;
re->e.in = NULL; // 不使用 Reactor 缓冲区,复用 hiredis 自身缓冲区
re->e.out = NULL;
re->mask = 0;
// 将 hiredis 事件接口映射到 Reactor 操作(关键适配步骤)
ac->ev.addRead = redisAddRead; // 读事件注册 → 调用 redisEventUpdate 实现
ac->ev.delRead = redisDelRead; // 读事件注销 → 调用 redisEventUpdate 实现
ac->ev.addWrite = redisAddWrite; // 写事件注册 → 调用 redisEventUpdate 实现
ac->ev.delWrite = redisDelWrite; // 写事件注销 → 调用 redisEventUpdate 实现
ac->ev.cleanup = redisCleanup; // 事件清理 → 释放 redis_event_t 内存
ac->ev.data = re; // 存储 Redis 事件结构体,供后续操作使用
return REDIS_OK;
}
适配后,hiredis 触发的读/写事件操作,会间接调用 Reactor 的 add_event、del_event 等函数,实现 Redis 事件与 Reactor 循环的深度整合。
四、自定义 Redis 协议实现
当需要让 Redis 驱动与项目自定义数据结构(如类似 Lua Table 的 SVar)契合时,可跳过 hiredis 的协议解析,自行实现 Redis 协议的解压缩(解析响应)与压缩(构造请求),确保数据格式与项目兼容。

4.1 自定义协议解压缩(解析 Redis 响应)
Redis 响应协议以不同字符开头标识数据类型(如 $ 表示二进制安全字符串、* 表示数组),read_sub_response 函数通过递归解析不同类型的响应,将结果存入项目自定义的 SVar 类型,核心代码与逻辑如下:
4.1.1 读取协议行:readline
从缓冲区中读取以 \r\n 结尾的一行数据,确定协议行的长度,代码如下:
c
static bool readline(u_char *start, u_char *last, int &pos) {
// 遍历缓冲区,查找 '\r' 且下一个字符为 '\n' 的位置
for (pos = 0; start+pos <= last-1; pos++) {
if (start[pos] == '\r' && start[pos+1] == '\n') {
pos--; // pos 指向 '\r' 前的最后一个有效字符
return true;
}
}
return false; // 未找到完整的 '\r\n',数据不完整
}
4.1.2 核心解析函数:read_sub_response
递归解析 Redis 响应的不同类型,返回解析状态(-2 错误、-1 数据不完整、0 成功、1 错误信息),代码逻辑如下:
c
static int read_sub_response(u_char *start, u_char *last, SVar &s, int &usz) {
int pos = 0;
// 先读取一行协议头,若读取失败(数据不完整),返回 -1
if (!readline(start, last, pos)) return -1;
u_char *tail = start + pos + 1; // 指向 '\r' 的位置
u_char ch = start[0]; // 协议类型标识(如 $、+、-、:、*)
// usz 累计已解析的字节数(协议头长度:pos+2+1 = 有效字符数 + '\r\n' 长度)
usz += pos + 2 + 1;
switch (ch) {
// 类型 1:$ 表示二进制安全字符串(如普通响应数据)
case '$': {
// 读取字符串长度(协议头中 '#' 后的数字)
string str(start+1, tail);
int len = atoi(str.c_str());
if (len < 0) return 0; // len <0 表示 nil(空值)
// 若缓冲区剩余长度不足字符串长度 + '\r\n',返回数据不完整
if (tail + 2 + len > last) return -1;
// 将字符串数据存入 SVar
s = string(tail+2, tail+2+len);
usz += len + 2; // 累计字符串长度 + '\r\n' 长度
return 0;
}
// 类型 2:+ 表示简单字符串(如 "OK")
case '+': {
s = string(start+1, tail); // 直接读取协议头后的字符串
return 0;
}
// 类型 3:- 表示错误信息(如 "ERR wrong number of arguments")
case '-': {
s = string(start+1, tail); // 读取错误信息存入 SVar
return 1; // 返回 1 标识为错误信息
}
// 类型 4:: 表示整数(如计数、状态码)
case ':': {
string str(start+1, tail); // 读取数字字符串并转为浮点数(兼容整数)
s = atof(str.c_str());
return 0;
}
// 类型 5:* 表示数组(如命令返回的多元素结果)
case '*': {
// 读取数组元素个数
string str(start+1, tail);
int n = atoi(str.c_str());
if (n == 0) return 0; // n=0 表示空数组
if (n < 0) return 0; // n<0 表示超时或空值
int ok = 0;
u_char *pt = tail + 2; // 指向数组第一个元素的起始位置
for (int i = 0; i < n; i++) {
if (pt > last) return -1; // 缓冲区不足,返回数据不完整
int sz = 0;
SVar t; // 存储单个元素
// 递归解析数组中的每个元素
int ret = read_sub_response(pt, last, t, sz);
if (ret < 0) return -1; // 解析失败,返回错误
s.Insert(t); // 将元素插入 SVar 数组
usz += sz; // 累计元素解析的字节数
pt += sz; // 移动指针到下一个元素
if (ret == 1) ok = 1; // 若有元素是错误信息,标记 ok=1
}
return ok;
}
}
return -2; // 未知协议类型,返回解析错误
}
4.1.3 响应读取入口:read_response
从项目自定义的句柄(SHandle)缓冲区中读取数据,调用 read_sub_response 解析,代码如下:
c
static int read_response(SHandle *pHandle, SVar &s, int &size) {
int len = pHandle->GetCurBufSize(); // 获取缓冲区当前数据长度
u_char *start = pHandle->m_pBuffer; // 缓冲区起始地址
u_char *last = pHandle->m_pBuffer + len; // 缓冲区末尾地址
// 调用核心解析函数,返回解析状态
return read_sub_response(start, last, s, size);
}
4.2 自定义协议压缩(构造 Redis 请求)
构造 Redis 请求需遵循 Redis 协议格式(数组类型为主,如 *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n),通过 write_count、write_header、write_command 等函数拼接请求字符串,核心代码如下:
4.2.1 写入数组元素个数:write_count
向请求字符串中写入数组类型标识(*)和元素个数,代码如下:
c
static void write_count(string &req, size_t n) {
char chv[16] = {0};
_itoa(n, chv, 10); // 将元素个数转为字符串
req.append("*"); // 写入数组标识
req.append(chv); // 写入元素个数
}
4.2.2 写入字符串类型头:write_header
向请求字符串中写入字符串类型标识($)、字符串长度和 \r\n,代码如下:
c
static void write_header(string &req, size_t n) {
char chv[16] = {0};
_itoa(n, chv, 10); // 将字符串长度转为字符串
req.append("\r\n$"); // 写入换行和字符串标识
req.append(chv); // 写入字符串长度
req.append("\r\n"); // 写入换行
}
4.2.3 写入 Redis 命令:write_command
向请求字符串中写入 Redis 命令(如 SET),并拼接对应的字符串头,代码如下:
c
static void write_command(string &req, const char *cmd) {
int n = strlen(cmd); // 获取命令长度
write_header(req, n); // 写入字符串头(长度信息)
req.append(cmd); // 写入命令内容
}
4.2.4 请求构造入口:RunCommand
根据 Redis 命令(如 SET)和参数列表(如 key、value),构造完整请求并发送,代码如下:
c
void SRedisClient::RunCommand(const char* cmd, vector<string> ¶ms) {
string req; // 存储完整请求字符串
size_t nsize = params.size(); // 参数个数
// 写入数组标识和总元素个数(命令 + 参数个数)
write_count(req, nsize + 1);
// 写入 Redis 命令(如 SET)
write_command(req, cmd);
// 遍历参数列表,写入每个参数
for (size_t i = 0; i < params.size(); i++) {
size_t n = params[i].size(); // 参数长度
write_header(req, n); // 写入参数的字符串头
req.append(params[i]); // 写入参数内容
}
req.append("\r\n"); // 补充请求末尾的换行
Send(req); // 发送请求到 Redis 服务器
}