文章目录
前言
本文将介绍kvstore的网络层设计
一、网络层设计介绍
由于本项目为基础项目,所以在网络这方面并未选择第三方库,比如libevent、muduo和boost等网络库。而是分别基于epoll来实现了reactor网络模型,基于ntyco协程库来实现网络message收发,基于io_uring来实现proactor网络模型。接下来将逐个介绍这三种方式实现的网络层。
二、reactor网络模型
2.1 单Reactor单线程网络模型
本网络模型基本沿用了【高性能网络(2)】采用Reactor网络模型实现HTTP服务器。
我们只需要将收发message的逻辑改为kvstore的即可
#if ENABLE_KVSTORE
typedef int (*msg_handler)(char *msg, int length, char *response);
static msg_handler kvs_handler;
int kvs_request(struct conn *c) {
c->wlength = kvs_handler(c->rbuffer, c->rlength, c->wbuffer);
}
int kvs_response(struct conn *c) {
}
#endif
其中kvs_handler会将客户端的消息进行解析并返回长度,通过send_cb客户端就可以得到服务端的响应结果。在kvs_handler中也就是会包含我们的KV引擎。
2.2 单Reactor多线程网络模型
如果我们需要用采用多线程来处理读写任务等,那么在这些任务中间accept、read、write是串行执行的,以accept之后的文件描述符作为区分 各个线程分别占有。

三、proactor网络模型
3.1 liburing
proactor网络模型通常采用异步io来实现,这里将基本沿用【高性能网络(1)】网络io与io多路复用的实现方法。下面将补充一些在liburing库中有关io_uring的函数。
int io_uring_queue_init_params(unsigned entries, struct io_uring *ring, const struct io_uring_params *p);
// 1)entries:指定 I/O uring 的入口数目,即同时处理的 I/O 事件数目。
// 2)ring:指向 struct io_uring 结构的指针,用于接收初始化后的 I/O uring 环境。
// 3)p:指向 struct io_uring_params 结构的指针,包含了自定义的初始化参数。
struct io_uring_sqe *io_uring_get_sqe(struct io_uring *ring);
// 1)ring:指向 struct io_uring 结构的指针,表示要操作的 I/O uring 环境。
// 该函数返回一个指向 struct io_uring_sqe 结构的指针,该结构表示一个 Submission Queue Entry (SQE)。SQE 包含了要执行的 I/O 操作的详细信息
void io_uring_prep_recv(struct io_uring_sqe *sqe, int fd, struct sockaddr *addr, socklen_t *addrlen, int flags);
// 用于准备执行接收数据的 SQE (Submission Queue Entry)。通过使用 io_uring_prep_recv 函数,你可以设置要接收数据的套接字描述符以及数据缓冲区等参数。
void io_uring_prep_accept(struct io_uring_sqe *sqe, int fd, struct sockaddr *addr, socklen_t *addrlen, int flags);
// 用于准备执行 accept 操作的 SQE (Submission Queue Entry)。通过使用 io_uring_prep_accept 函数,可以设置要接受连接的套接字描述符、新连接的文件描述符和连接地址等参数。
void io_uring_prep_send(struct io_uring_sqe *sqe, int fd, const void *buf, unsigned int len, int flags);
// 用于准备执行发送数据的 SQE (Submission Queue Entry)。通过使用 io_uring_prep_send 函数,你可以设置要发送数据的套接字描述符以及数据缓冲区等参数。
int io_uring_submit(struct io_uring *ring);
// 用于提交 SQE (Submission Queue Entry) 到 I/O uring 环境中进行处理。
int io_uring_wait_cqe(struct io_uring *ring, struct io_uring_cqe **cqe_ptr);
// 用于等待完成队列项(Completion Queue Entry,CQE)的到来。会阻塞等待。
int io_uring_peek_batch_cqe(struct io_uring *ring, struct io_uring_cqe **cqes, unsigned int count);
// 用于批量获取已完成的CQE(Completion Queue Entry)而无需等待.
void io_uring_cq_advance(struct io_uring *ring, unsigned int steps);
// 用于标记完成队列(Completion Queue,CQ)上已经处理的CQE数量。当一个或多个CQE被处理后,需要调用io_uring_cq_advance()函数来更新下一次读取CQE时应该从哪个位置开始。
3.2 问题与思考
3.2.1 事件分发
依赖于sqe的cqe的user_data字段。
__u64 user_data; /* data to be passed back at completion time */
比如在执行accept操作时,我们将自定义的conn_info进行对应修改,当内核处理完成之后由cqe带出。
int set_event_accept(struct io_uring *ring, int sockfd, struct sockaddr *addr,
socklen_t *addrlen, int flags) {
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
struct conn_info accept_info = {
.fd = sockfd,
.event = EVENT_ACCEPT,
};
io_uring_prep_accept(sqe, sockfd, (struct sockaddr*)addr, addrlen, flags);
memcpy(&sqe->user_data, &accept_info, sizeof(struct conn_info));
}
3.2.2 sqe在多线程操作下,是否需要加锁
不需要。io_uring使用了无锁环形队列,通过原子操作来管理队列的头尾指针,可以确保多个线程安全高效并发提交到sqe中。
四、协程实现网络层
这里我们采用ntyco库来实现,对应协程的概念在【高性能网络(3)】采用协程重构网络io。首先安装ntyco
git clone https://github.com/wangbojing/NtyCo.git
cd NtyCo
make
源码:
#include "nty_coroutine.h"
#include <arpa/inet.h>
typedef int (*msg_handler)(char *msg, int length, char *response);
static msg_handler kvs_handler;
void server_reader(void *arg) {
int fd = *(int *)arg;
int ret = 0;
while (1) {
char buf[1024] = {0};
ret = recv(fd, buf, 1024, 0);
if (ret > 0) {
char response[1024] = {0};
int slength = kvs_handler(buf, ret, response);
ret = send(fd, response, slength, 0);
if (ret == -1) {
close(fd);
break;
}
} else if (ret == 0) {
close(fd);
break;
}
}
}
void server(void *arg) {
unsigned short port = *(unsigned short *)arg;
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) return ;
struct sockaddr_in local, remote;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
bind(fd, (struct sockaddr*)&local, sizeof(struct sockaddr_in));
listen(fd, 20);
printf("listen port : %d\n", port);
while (1) {
socklen_t len = sizeof(struct sockaddr_in);
int cli_fd = accept(fd, (struct sockaddr*)&remote, &len);
nty_coroutine *read_co;
nty_coroutine_create(&read_co, server_reader, &cli_fd);
}
}
int ntyco_start(unsigned short port, msg_handler handler) {
//int port = atoi(argv[1]);
kvs_handler = handler;
nty_coroutine *co = NULL;
nty_coroutine_create(&co, server, &port);
nty_schedule_run();
}
采用协程所实现的网络,代码风格与同步的网络io写法差不多却可以实现异步的性能,这是因为在协程库内部有一个调度器,将不同的协程句柄管理起来以实现资源的合理利用。
总结
kv存储中的网络层采用了这三种实现方式,针对不同场景可以随时切换。
参考链接:
https://github.com/0voice
2.10 高性能异步IO机制
高效异步:io_uring 技术解析_io uring
1169

被折叠的 条评论
为什么被折叠?



