• 基于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]# 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    客户端

    -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]#
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    网络服务端

    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;          //事件轮询的状态结构
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    上面主要关注两个东西: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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    应用分析

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

  • 相关阅读:
    Qt程序打包成一个单独exe的方法
    STM32WL开发之易智联LORA评估板上按键KEY的配置与应用
    docker容器服务器编排利器 Docker Compose应用实战
    Java 8之后的那些新特性(四):网络请求 Java Http Client
    零售数据分析方案:深度剖析人、货、场
    二维码那点事
    Hack The Box-Monitored
    Ghidra反编译报错Can‘t read language spec ***/***/***/mips32be.sla
    在访问一个网页时弹出的浏览器窗口,如何用selenium 网页自动化解决?
    按照C语言程序结构组成数字电路进行计算的计算机
  • 原文地址:https://blog.csdn.net/qq_34462436/article/details/126515468