对于广大 C 语言开发者来说,缺乏类似 C++ STL 和 Boost 的库会让开发受制于基础库的匮乏,也因此导致了开发效率的骤降。这也使得例如 libevent 这类事件库(基础组件库)一时间大红大紫。

    今天,码哥给大家带来一款基础库,这套库不仅仅提供了常用的数据结构、算法,如红黑树、斐波那契堆、队列、KMP 算法、RSA 算法、各类哈希算法、数据恢复算法等等,还提供了多进程框架、多线程框架、跨平台高性能事件等实用内容。注意:这是一款不依赖第三方的库。

    除此以外,它也是笔者之前文章(Melang 脚本语言)中的核心库。这也就意味着,使用该库,不仅可以快速获得上述内容,还可以让开发者所构建的系统很方便地引入脚本语言的功能。

    它就是——Melon

    UNIX C语言必备实用第三方库:Melon - 图1

    Github 链接

    下面,码哥便带诸位一览这个库的功能。

    Melon 中包含如下数据结构的实现:

    • 双向链表
    • 斐波那契堆
    • 哈希表
    • 队列
    • 红黑树

    其中:

    • 双向链表使用宏实现,可以通过两行宏函数即可完成双向队列插入和删除操作的声明和定义。
    • 斐波那契堆是一个最小堆,在库中的事件功能中用于实现定时器的维护管理,当然,也可以单独使用。

    以上结构几乎均可在其对应名称的头文件中找到数据结构定义以及函数定义。

    一般情况下,数据结构的使用都是函数调用形式,因此也尽可能降低了不同组件间的耦合度。

    Melon 中包含的算法如下:

    • 加密算法:AES、DES、3DES、RC4、RSA
    • 哈希算法:MD5、SHA1、SHA256
    • Base64
    • 大数计算
    • FEC
    • JSON
    • 矩阵运算
    • 里德所罗门编码
    • 正则匹配算法
    • KMP

    如上算法基本都在其各自头文件中可以找到对应的函数声明以及必要的数据结构定义。

    其中,FEC 与里德所罗门编码均属于纠错码,FEC 常用于 RTP 中做数据修复,而里德所罗门编码既可以用于实时语音中丢包恢复,也可以用于冗余阵列(RAID)和其他 UDP 丢包恢复的场景。关于里德所罗门编码,感兴趣的读者可以阅读码哥之前的文章:神奇的数据恢复算法

    前面的都是常规操作,这里才是重头戏。

    Melon 中还包括如下实用组件:

    • 内存池
    • 数据链
    • TCP 封装
    • 事件机制
    • 文件缓存
    • HTTP 处理
    • 脚本语言
    • 词法分析器
    • websocket
    • 多进程框架
    • 多线程框架

    因 Melon 作者 Nginx 中毒较深,所以 Melon 中部分机制与 Nginx 较为相似。

    内存池: 这里内存池不仅支持对从堆中分配的内存进行管理,还支持对共享内存的管理。

    数据链与 TCP 封装: TCP 封装中包含了阻塞与非阻塞下的收发逻辑,并利用数据链结构来存放发送数据与接收数据。

    事件机制: 事件机制中不仅支持 epoll、select,还支持 Kqueue,库在编译前会自行检测平台支持情况。事件包含了:

    • 句柄(文件描述符)事件:读、写、出错事件,以及超时事件(主要用于超时断开链接);
    • 定时事件(与句柄超时是两码事);
    • 信号处理事件:这里的信号处理并非一个信号只能有一个处理事件,而是设置多少个处理函数就会执行多少个;

    文件缓存: 参考 Nginx 文件缓存,避免对同一文件的重复打开浪费文件描述符资源。

    HTTP: 包含了 HTTP 的接收解析和发送,该套接口依赖于数据链结构来进行处理,因此可配合TCP 封装一同使用。

    脚本语言: 内容较多,可另行参考:Melang 脚本语言

    词法分析器: 之所以这个单独算一个功能组件,是因为在 Melon 中,配置文件解析就是使用该词法分析器处理的。仅通过三行 C 代码就可以实现一个最最基础的词法分析器,这也归功于 C 语言宏的强大。

    websocket: 该部分依赖于HTTP组件。

    多进程: 多进程采用一主多从模式,主进程做管理,从进程处理实际业务。主进程与从进程之间由 socketpair 相连,因此从进程异常退出,主进程会立刻拉起一个新的子进程,同时主子进程也可以通过该 socketpair 进行数据通信。除了自身子进程可以管理,也可以通过配置文件配置来拉起其他程序作为自己的子进程来管理,有些类似于 supervisord。

    多线程: 多线程分为两类,一类是常规的线程池,另一类是模块化的线程。后者也是一主多从模型,主与子之间是通过 socketpair 进行通信,而每一个子线程都有其入口函数(类似 main 函数),每一个子线程通常都是处理一类单一事务。

    上面说了那么多,下面就来看一个多进程的例子。

    首先,我们要先安装 Melon:

    1. $ git clone https://github.com/Water-Melon/Melon.git
    2. $ ./configure
    3. $ make
    4. $ sudo make install
    5. $ sudo echo "/usr/local/melon/lib/" >> /etc/ld.so.conf
    6. $ sudo ldconfig

    安装好后,Melon 会被安装在 / usr/local/melon 下。

    接着,我们创建一个名为 hello.c 的源文件来完成我们期望的功能:

    1. #include <stdio.h>
    2. #include "mln_core.h"
    3. #include "mln_log.h"
    4. #include "mln_event.h"
    5. char text[1024];
    6. static int global_init(void);
    7. static void worker_process(mln_event_t *ev);
    8. static void print_handler(mln_event_t *ev, void *data);
    9. int main(int argc, char *argv[]) {
    10. struct mln_core_attr cattr;
    11. cattr.argc = argc;
    12. cattr.argv = argv;
    13. cattr.global_init = global_init;
    14. cattr.worker_process = worker_process;
    15. return mln_core_init(&cattr);
    16. }
    17. static int global_init(void) {
    18. int n = snprintf(text, sizeof(text)-1, "hello world\n");
    19. text[n] = 0;
    20. return 0;
    21. }
    22. static void worker_process(mln_event_t *ev) {
    23. mln_event_set_timer(ev, 1000, text, print_handler);
    24. }
    25. static void print_handler(mln_event_t *ev, void *data) {
    26. mln_log(debug, "%s\n", (char *)data);
    27. mln_event_set_timer(ev, 1000, data, print_handler);
    28. }

    这段代码主要是初始化了一个全局变量,然后给每一个子进程创建了一个定时事件,即每一秒中输出一个 hello world。

    我们先进行编译链接生成可执行程序:

    1. $ gcc -o hello hello.c -I /usr/local/melon/include/ -L /usr/local/melon/lib/ -lmelon

    然后,我们需要先修改 Melon 库的配置文件:

    1. $ sudo vim /usr/local/melon/conf/melon.conf
    2. log_level "none";
    3. daemon off;
    4. core_file_size "unlimited";
    5. worker_proc 1;
    6. thread_mode off;
    7. framework off;
    8. log_path "/usr/local/melon/logs/melon.log";
    9. exec_proc {
    10. }
    11. thread_exec {
    12. }

    我们做如下修改:

    • framework off; —> framework on;
    • worker_proc 1; —> worker_proc 3;

    这样,多进程框架将被启用,且会产生三个子进程。

    程序启动后如下:

    1. $ ./hello
    2. Start up worker process No.1
    3. Start up worker process No.2
    4. Start up worker process No.3
    5. 02/08/2021 09:34:46 GMT DEBUG: hello.c:print_handler:39: PID:25322 hello world
    6. 02/08/2021 09:34:46 GMT DEBUG: hello.c:print_handler:39: PID:25323 hello world
    7. 02/08/2021 09:34:46 GMT DEBUG: hello.c:print_handler:39: PID:25324 hello world
    8. 02/08/2021 09:34:47 GMT DEBUG: hello.c:print_handler:39: PID:25322 hello world
    9. 02/08/2021 09:34:47 GMT DEBUG: hello.c:print_handler:39: PID:25323 hello world
    10. 02/08/2021 09:34:47 GMT DEBUG: hello.c:print_handler:39: PID:25324 hello world
    11. ...

    这时,可以 ps 看一下,一共存在四个 hello 进程,一个为主,其余三个为子进程。

    事实上,Melon 并不会有过多条条框框需要开发者小心谨慎怕踩坑。与 Skynet 类似,Melon 提供的绝大多数内容都可独立使用,而不必一定与多进程多线程框架结合。因此,这也给了使用者极大的自由度。

    https://www.toutiao.com/i6926789516594479624/?wid=1635336661789