基于Redis网络模型的简易网络库

ANet

基于Redis网络模型的简易网络库,网络模块代码取自Redis源码。

Redis网络模型介绍

Redis网络模型是一个使用IO多路复用单线程非阻塞的模型。这个模型的优点在于单线程不用考虑加锁,如果在单核环境上可以将效率发挥到最大。

如何启动一个服务器

  • 通过aeCreateEventLoop创建核心aeEventLoop
  • 通过anetTcpServer完成socket() bind() listen()
  • 通过aeCreateFileEventfd注册相应的事件
  • aeMain循环检测每个fd是否有事件发生,如果有就交给相应的读/写处理程序。

附:各个文件介绍

文件作用
ae.c ae.h ae_epoll.c ae_select.cRedis事件处理器的实现(Redis源码)
anet.c anet.hRedis网络库的实现(Redis源码)
buffer.c buffer.h自行实现的buffer
protocol.c protocol.h自行定义协议
define.h一些常量,比如listen的backlog大小
server.c server_test.c自定义的服务端和客户端程序

调试

服务端:

[root@localhost build]# ./server
listen on: 0.0.0.0:12345
accepted ip 127.0.0.1:42864
chat message: No.0 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.1 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.2 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.3 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.4 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.5 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.6 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.7 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.8 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.9 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.10 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.11 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.12 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.13 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.14 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.15 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.16 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.17 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.18 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.19 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.20 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.21 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.22 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.23 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.24 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.25 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.26 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.27 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.28 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.29 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.30 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.31 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.32 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.33 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.34 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.35 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.36 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.37 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.38 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.39 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.40 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.41 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.42 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.43 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.44 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.45 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.46 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.47 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.48 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.49 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.50 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.51 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.52 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.53 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.54 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.55 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.56 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.57 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.58 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
chat message: No.59 Hey, hurley. I say: 1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.
client disconnect, close it.


^C
[root@localhost build]# 

客户端

-rwxr-xr-x 1 root root 59048 524 00:12 server_test
[root@localhost build]# ./server_test 
connect error: creating socket: Connection refused
[root@localhost build]# pwd
/root/zsx/code/redis/ANet-master/build
[root@localhost build]# ./server_test 
send package size: 70
slow send ...
more slow send ...
[root@localhost build]#

网络服务端

Redis单线程模型

通过aeCreateEventLoop创建核心aeEventLoop
在这里插入图片描述

initServer流程:

initServer里面首先创建了一个EventLoop,然后监听Server的IP对应的端口号,假设我们监听的是127.0.0.1:3333这个IP:端口对,我们得到的一个Server Socket句柄,最后通过createFileEvent将我们得到的Server Socket句柄和我们关心的网络事件mask注册到EventLoop上面。

EventLoop的定义

/* State of an event based program 事件状态结构 */
typedef struct aeEventLoop {
    int maxfd;                  /* 当前注册的最大文件描述符 */
    int setsize;                /* 监控的最大文件描述符数 */
    long long timeEventNextId;  /* 定时事件ID */
    time_t lastTime;            /* 最近一次处理事件的时间 */
    aeFileEvent *events;        /* 注册的事件表 数组 */
    aeFiredEvent *fired;        /* 已就绪事件表 */
    aeTimeEvent *timeEventHead; /* 定时事件链表头节点 */
    int stop;                   /* 是否停止循环 事件处理开关 */
    void *apidata;              /* 特定接口的特定数据 保存底层调用的多路复用库的事件状态 */
    aeBeforeSleepProc *beforesleep;     /* 在sleep之前执行的函数 */
} aeEventLoop;          //事件轮询的状态结构

上面主要关注两个东西:events(注册的事件表 数组)和fired(已就绪事件表)。

events用于存放被注册的事件以及相应的句柄,fired用于存放当EventLoop线程从多路复用器轮询到有事件的句柄的时候,EventLoop线程会把它放入fired数组里面,然后处理。

img

事件注册示意图

我用上面的示意图描述createFileEvent做的事情,就是将Server Socket句柄和关心的事件mask以及当事件产生的时候的事件处理器accptHandler生成一个aeFileEvent注册到EventLoop的events的数组里面,当然在这之前会首先将事件注册到多路复用器上,也就是epoll、kqueue等这些组件上。事件注册完之后需要对多路复用器进行轮训,来分离我们关心切发生的事件,那就是最后一步,启动事件轮询器。

接收网络连接

上面的步骤完成了服务端的网络初始化,而且事件轮询器已经开始工作了,事件轮询器做什么事情呢,就是不断轮训多路复用器,看看之前注册的事件有没有发生,如果有发生,则将会将事件分离出来,放入EventLoop的fired数组中,然后处理这些事件。

很显然,上面注册的事件是客户端建立连接这个事件,因此当有两个客户端同时连接Redis服务器的时候,事件轮询器会从多路复用器上面分离出这个事件,同时调用acceptHandler来处理。acceptHandler做的事情主要是accept客户端的连接,创建socket句柄,然后将socket句柄和读事件注册到EventLoop的events数组里面,不一样的是对于客户端的事件处理器是readQueryClient。

preview

accept客户端连接以及注册客户端连接句柄示意图

上面示意图表示了acceptHandler处理客户端连接,得到句柄之后再将这个句柄注册到多路复用器以及EventLoop上的示意图。之后再同样再处理下一个客户端的连接,这些都是串行的。

事件轮训

上面接收客户端这部分其实都发生在事件轮训的主循环里面:

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);
        aeProcessEvents(eventLoop, AE_ALL_EVENTS);
    }
}

Redis会不断的轮训多路复用器,将网络事件分离出来,如果是accept事件,则新接收客户端连接并将其注册到多路复用器以及EventLoop中,如果是查询事件,则通过读取客户端的命令进行相应的处理,这一切都是单线程,顺序的执行的,因此不会发生并发问题。

应用分析

Redis官网对Redis的读写性能测试结果达到10左右,这是非常吸引人的。Redis的单线程的行为主要是对内存的读写,这些操作其实用不了多少时间,因此瓶颈在网络I/O上面,我们一般提供较好的网络环境就可以提升Redis的吞吐量,比如提高网络带宽,除此之外还可以通过合并命令提交批处理请求来代替单条命令一次次请求从而减少网络开销,提高吞吐量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值