;
node_recv_cb recv_cb;
node_close_cb close_cb;
};
在实现文件中实现接口及回调,注意:即使接口或回调内没有额外的操作,仍然需要实现,例如此处的 log_conn_cb 和 log_connect ,否则上一个插件或下一个插件在日志层调用时会中断:
/* callback */
void log_conn_cb(dul_node_t *ctx, int status) {
zim_log_t *log = (zim_log_t *)ctx;
if (log->pre && log->pre->conn_cb) {
log->pre->conn_cb(log->pre, status);
}
}
/* 省略中间直接回调 */
int log_recv_cb(dul_node_t *ctx, void *params, uv_buf_t *buf, ssize_t size) {
/* 收集接收到的数据 */
recv_data_from_server(buf->base, params, size);
/* 继续向上一层插件回调接收到的数据 */
zim_log_t *log = (zim_log_t *)ctx;
if (log->pre && log->pre->recv_cb) {
log->pre->recv_cb(log->pre, opcode, buf, size);
}
return OK;
}
/* log hanlder */
int log_init(dul_node_t *ctx, map_t params) {
zim_log_t *log = (zim_log_t *)ctx;
bzero(log, sizeof(zim_log_t));
log->init = &log_init;
log->conn = &log_connect;
log->write_data = &log_write;
log->read_data = &log_read;
log->close = &log_close;
log->destroy = &log_destroy;
log->reset = &log_reset;
log->conn_cb = &log_conn_cb;
log->write_cb = &log_write_cb;
log->recv_cb = &log_recv_cb;
log->close_cb = &log_close_cb;
return OK;
}
static void log_connect(dul_node_t *ctx) {
zim_log_t *log = (zim_log_t *)ctx;
if (log->next && log->next->conn) {
log->next->host = log->host;
log->next->port = log->port;
log->next->conn(log->next);
}
}
/* 省略中间直接调用 */
static void log_write(dul_node_t *ctx,
const char *payload,
unsigned long long payload_size,
void *params) {
/* 收集发送数据 */
send_data_to_server(payload, payload_size, params);
/* 继续往下一层插件写入数据 */
zim_log_t *log = (zim_log_t *)ctx;
if (log->next && log->next->write_data) {
log->next->write_data(log->next, payload, payload_size, flags);
}
}
LOADER_ALLOC(zim_log, log);
void (*oper_func[])(dul_node_t **) = {
ws_alloc,
tls_alloc,
uv_alloc,
log_alloc,
};
char const *loaders[] = {
"ws", "tls", "uv", "log"
};
/* 增加日志前 */
char loaders[] = "ws?path=/!tls!uv";
context_init(c, "127.0.0.1", 443, "", "", "", "", NULL, loaders);
/* 增加日志后 */
char loaders[] = "log!ws?path=/!log!tls!uv";
context_init(c, "127.0.0.1", 443, "", "", "", "", NULL, loaders);
我们重新运行程序,就能发现日志功能已经成功的配置上去,能够将接受和发送的数据上报:
总结
回顾一下跨平台长连接组件的设计,我们使用 libuv 和 mbedtls 分别实现 TCP 和 TLS ,参照 WebSocket 协议实现了其握手及数据读写,同时抽象出通信接口及回调,为了和原生层交互,iOS 和 Android 分别采用 runtime 消息发送和 JNI 进行原生方法调用。
但这样的定向设计完全不符合后期可能会有新增协议解析的预期,所以我们进行了插件化改造,其三个核心点是结构体改造
、双向链表
和函数指针
。
我们通过将插件行为抽象出一个结构体,利用双向链表将前后插件绑定在一起,使用函数指针调用具体插件的函数或回调。
这样做的优点是使得插件之间不存在耦合关系,只需保持逻辑顺序上的关系,同时通过修改插件的注册提高了灵活性,使得组件具有可插拔性(冷插拔)。
但在新增组件中我们需要实现所有的接口和回调,如果数量多的话,这还真是一件比较繁琐的事情。