开发利器——C 语言必备实用第三方库

2021-02-10 16:20:49 +08:00
 monkeyNik

本文转载自本人头条号: https://www.toutiao.com/i6926789516594479624/

转载请注明出处,感谢!

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

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

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

它就是——Melon

Github: https://github.com/Water-Melon/Melon

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

数据结构

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

其中:

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

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

算法

Melon 中包含的算法如下:

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

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

其他组件

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

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

因 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:

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

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

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

#include <stdio.h>
#include "mln_core.h"
#include "mln_log.h"
#include "mln_event.h"

char text[1024];

static int global_init(void);
static void worker_process(mln_event_t *ev);
static void print_handler(mln_event_t *ev, void *data);

int main(int argc, char *argv[])
{
    struct mln_core_attr cattr;
    cattr.argc = argc;
    cattr.argv = argv;
    cattr.global_init = global_init;
    cattr.worker_process = worker_process;
    return mln_core_init(&cattr);
}

static int global_init(void)
{
    //global variable init function
    int n = snprintf(text, sizeof(text)-1, "hello world\n");
    text[n] = 0;
    return 0;
}

static void worker_process(mln_event_t *ev)
{
    //we can set event handler here
    //let's set a timer
    mln_event_set_timer(ev, 1000, text, print_handler);
}

static void print_handler(mln_event_t *ev, void *data)
{
    mln_log(debug, "%s\n", (char *)data);
    mln_event_set_timer(ev, 1000, data, print_handler);
}

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

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

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

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

$ sudo vim /usr/local/melon/conf/melon.conf

log_level "none";
//user "root";
daemon off;
core_file_size "unlimited";
//max_nofile 1024;
worker_proc 1;
thread_mode off;
framework off;
log_path "/usr/local/melon/logs/melon.log";
/*
 * Configurations in the 'exec_proc' are the
 * processes which are customized by user.
 *
 * Here is an example to show you how to
 * spawn a program.
 *     keepalive "/tmp/a.out" ["arg1" "arg2" ...]
 * The command in this example is 'keepalive' that
 * indicate master process to supervise this
 * process. If process is killed, master process
 * would restart this program.
 * If you don't want master to restart it, you can
 *     default "/tmp/a.out" ["arg1" "arg2" ...]
 *
 * But you should know that there is another
 * arugment after the last argument you write here.
 * That is the file descriptor which is used to
 * communicate with master process.
 */
exec_proc {
   // keepalive "/tmp/a";
}
thread_exec {
//    restart "hello" "hello" "world";
//    default "haha";
}

我们做如下修改:

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

程序启动后如下:

$ ./hello
Start up worker process No.1
Start up worker process No.2
Start up worker process No.3
02/08/2021 09:34:46 GMT DEBUG: hello.c:print_handler:39: PID:25322 hello world

02/08/2021 09:34:46 GMT DEBUG: hello.c:print_handler:39: PID:25323 hello world

02/08/2021 09:34:46 GMT DEBUG: hello.c:print_handler:39: PID:25324 hello world

02/08/2021 09:34:47 GMT DEBUG: hello.c:print_handler:39: PID:25322 hello world

02/08/2021 09:34:47 GMT DEBUG: hello.c:print_handler:39: PID:25323 hello world

02/08/2021 09:34:47 GMT DEBUG: hello.c:print_handler:39: PID:25324 hello world

...

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

小结

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


Melon 的官方 QQ 群号:756582294

感谢阅读,欢迎各位在评论区留言评论。

5582 次点击
所在节点    推广
27 条回复
hxndg
2021-02-10 16:46:32 +08:00
不同内存模型下的顺序一致性怎么做的?
monkeyNik
2021-02-10 16:55:09 +08:00
@hxndg 兄弟想多了😂,初始化为共享内存的话,分配与释放函数就会按照共享内存管理,否则就是堆内存管理,换句话说,一个池只能是共享内存或者堆内存。
其实想混用也行,把分配释放函数拆分开就行了,池结构本身内部已经区分存放了。
AndyAO
2021-02-10 17:18:29 +08:00
虽然目前还用不到,但感觉好像是很不错的东西,为楼主的奉献精神点个赞 / 顶个帖儿.
hxndg
2021-02-10 17:40:26 +08:00
@monkeyNik 没懂,堆内存和顺序一致性有啥关系?
monkeyNik
2021-02-10 17:43:09 +08:00
@hxndg 😂我错了,我以为你只想问内存池的事。那你指的内存模型是哪些呢?
mintist
2021-02-10 18:01:26 +08:00
还有人记得 C,哈哈哈哈
monkeyNik
2021-02-10 18:13:38 +08:00
@mintist 光复我大 C 哈哈哈~
gaigechunfeng
2021-02-10 19:00:34 +08:00
必须支持,先 make 一下。
现在已经基本脱离 C 语言了。
rim99
2021-02-10 20:06:42 +08:00
赞,不过现在需要写 C/C++的项目越来越少
commoccoom
2021-02-10 20:09:50 +08:00
插眼,刚刚开始学习 C 的路过😂
CismonX
2021-02-10 20:23:32 +08:00
光从 3700 多行的纯文本文档就能看出来楼主很用心👍

可以试试 Texinfo 格式写文档,既可以生成 HTML 在线查看,也可以生成 PDF 用于打印,也可以生成纯文本或者 info 格式便于在终端查看。

当然,从楼主的项目构建风格来看,应该是不太喜欢引入过多依赖,更希望项目能够开箱即用。configure 都是自己手写的,没有用 autoconf 。所以这只作为一个小小的推荐~
jfcai
2021-02-10 21:17:38 +08:00
先收藏,说不定哪天用得上
monkeyNik
2021-02-10 23:46:23 +08:00
@CismonX 多谢兄台的建议,其实之前忙于内部脚本语言的文档和站点建设还有 bug 修复就没完善这部分,这部分确实需要完善一下,目前感觉看着比较乱😂
codehz
2021-02-11 06:34:38 +08:00
1 楼说的应该是可见性问题吧,就是一个线程对共享变量的修改在别的线程角度的可见性,但是楼主这既然没有采用共享内存的设计,就应该不会有可见性的问题(
BingoXuan
2021-02-11 08:22:56 +08:00
兼容 ANSI C 规范不?
monkeyNik
2021-02-11 09:26:25 +08:00
@codehz 其实是支持共享内存的哦,内存池可以分配共享内存。多线程的话就使用 pthread 下的锁或者库内置的自旋锁都行,多进程处理共享内存的话,默认封装的是跨进程的 pthread 读写锁,不过分配与释放函数内不会默认调用加锁解锁,所以使用者可以在外部用其他锁来替代。
monkeyNik
2021-02-11 09:34:53 +08:00
@BingoXuan 严格来说不兼容,里面是用到一些 gnu c 的东西,例如:inline,可变参宏,__attribute__,宏拼接之类的功能。
不过目前这个库在 CentOS MacOS Freebsd 上都可以正常使用,并且 mac 上也支持 x86 和 arm 架构 cpu
silencht
2021-02-11 12:07:25 +08:00
先给个星 mark 一下 O(∩_∩)O
byaiu
2021-02-11 15:52:47 +08:00
atomic 也有 c 库自带的 如果支持新一点的 c11 可能就能直接用了
monkeyNik
2021-02-11 18:27:09 +08:00
感谢各位的意见和建议,积累近 7 年的成果能够被大家支持,是这一年里我最大的收获,我也会继续维护和增加新功能,努力不负所望。
新的一年里,祝大家牛

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/752832

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX