简介

Melon 是一个用于简化开发的 C 框架库。 它包括许多数据结构、算法、体系结构和许多其他有用的组件。 您可以根据自己的需求选择一些组件或整个框架。 Melon 通过模块开发支持多进程和多线程模型。

在 Windows 上,框架无法激活,但其他组件仍在工作。

melon GitHub地址:Melon

melon中文帮助文档:中文手册

安装

Windows与UNIX环境的安装并无差异,仅需要先行安装并配置mingwgit bash以及make即可。

执行如下命令安装Melon:

$ git clone https://github.com/Water-Melon/Melon.git
$ ./configure
$ make
$ sudo make install

Melon会同时生成动态库与静态库。对于Linux系统,在使用Melon的动态库时,需要将该库的路径加入到系统配置中:

$ sudo echo "/usr/local/melon/lib/" >> /etc/ld.so.conf
$ sudo ldconfig

默认情况下,UNIX中Melon会被安装在/usr/local/melon下,Windows中会安装于$HOME/libmelon中。

快速入门

Melon的使用并不繁琐,大致步骤可分为:

  1. 在使用前初始化库
  2. 引入对应功能头文件,调用其中函数进行使用
  3. 启动程序前根据需要修改配置文件

组件使用示例

下面看一个使用内存池的例子:

#include <stdio.h>
#include "mln_core.h"
#include "mln_alloc.h"
#include <mln_log.h>int main(int argc, char *argv[])
{char *ptr;mln_alloc_t *pool;struct mln_core_attr cattr;cattr.argc = argc;cattr.argv = argv;cattr.global_init = NULL;cattr.worker_process = NULL;if (mln_core_init(&cattr) < 0) {fprintf(stderr, "Melon init failed.\n");return -1;}pool = mln_alloc_init();ptr = mln_alloc_m(pool, 1024);mln_log(debug, "%X\n", ptr);mln_alloc_free(ptr);mln_alloc_destroy(pool);return 0;
}

其中,mln_core_init就是Melon库的初始化函数,函数参数是一个结构体,该结构体用于传入程序参数全局变量初始化函数以及工作进程处理函数。由于这个例子中并不打算启用多进程框架,也不需要初始化一些全局变量,因此两个函数指针都被置空了。

随后的代码中:

  • mln_alloc_init:用于创建内存池
  • mln_alloc_m: 用于从内存池中分配一块指定大小的内存
  • mln_alloc_free:用于将内存池分配的内存释放回池中
  • mln_alloc_destroy:用于销毁内存池并释放资源

内存池相关的函数及结构定义都在mln_alloc.h中。

mln_log为Melon的日志输出函数,本例中以十六进制输出内存池分配的内存起始地址。

事实上,在GCC编译器的情况下,上面的代码还可以再简化:

#include <stdio.h>
#include "mln_alloc.h"
#include "mln_log.h"
#define MLN_SIMPLE_INIT
#include "mln_core.h"int main(int argc, char *argv[])
{char *ptr;mln_alloc_t *pool;mln_simple_init = 1;pool = mln_alloc_init();ptr = mln_alloc_m(pool, 1024);mln_log(debug, "%X\n", ptr);mln_alloc_free(ptr);mln_alloc_destroy(pool);return 0;
}

这里的简化方式是:将mln_core_init相关内容去掉,然后换成mln_simple_init = 1;。这种方式是利用了GCC的contructor特性做到的。事实上,这个特性使用有个前提:代码不需要使用多进程多线程框架的情况下方可使用,因为无法设置global_initworker_process回调函数进行处理。

随后对代码进行编译,这里以UNIX系统为例:

$ cc -o test test.c -I /usr/local/melon/include/ -L /usr/local/melon/lib/ -lmelon

Windows用户也可以在git bash中执行:

$ gcc -o test test.c -I $HOME/libmelon/include/ -L $HOME/libmelon/lib/ -llibmelon -lWs2_32

此时,还不可以启动这个test程序,因为我们先要检查Melon库的配置文件,确保配置不会使得程序启动多进程或者多线程框架(Windows用户可以忽略此步骤)。

$ vim /usr/local/melon/conf/melon.conflog_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";
}

这里我们需要确保framework这一项为off,因为本例不需要启动框架功能。

此时,我们就可以执行这个例子了。

$ ./test

此时可以看到类似如下输出内容:

03/27/2021 04:36:26 GMT DEBUG: test.c:main:25: PID:24077 1e29950

多进程框架使用示例

框架功能在Windows中,暂时不支持,本例将在UNIX系统中进行演示。

#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 functionint 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 timermln_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);
}

在本例中,我们增加了global_initworker_process的处理函数。global_init用于初始化一个全局的字符数组text。而worker_process则是子进程(或称为工作进程)的处理函数。在工作进程处理函数中,我们使用到了Melon事件模块的定时器函数,用于每1秒中(1000毫秒),调用一次print_handler函数将字符数组text中的内容进行日志输出。

生成可执行程序:

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

接着,依旧是检查配置文件,但这一次我们要确保framework须为onthread_modeoff。这样的配置表明,我们启用Melon的框架功能,但不启用多线程模式,那么Melon就会启用多进程模式。然后,根据需要修改worker_proc的数量,例如:3。

log_level "none";
//user "root";
daemon off;
core_file_size "unlimited";
//max_nofile 1024;
worker_proc 3;
thread_mode off;
framework on;
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
03/27/2021 04:53:44 GMT DEBUG: d.c:print_handler:39: PID:27620 hello world03/27/2021 04:53:44 GMT DEBUG: d.c:print_handler:39: PID:27621 hello world03/27/2021 04:53:44 GMT DEBUG: d.c:print_handler:39: PID:27622 hello world03/27/2021 04:53:45 GMT DEBUG: d.c:print_handler:39: PID:27620 hello world03/27/2021 04:53:45 GMT DEBUG: d.c:print_handler:39: PID:27621 hello world03/27/2021 04:53:45 GMT DEBUG: d.c:print_handler:39: PID:27622 hello world...

到此,快速入门部分就告一段落了,下面我们会针对Melon所提供的每一个功能进行说明并给予示例。

初始化

头文件

#include "mln_core.h"

相关结构

struct mln_core_attr {int                       argc; //一般为main的argcchar                    **argv; //一般为main的argvmln_core_init_t           global_init; //初始化回调函数,一般用于初始化全局变量,该回调会在配置加载完成后被调用mln_core_worker_process_t worker_process; //工作进程处理函数,我们将在多进程框架部分深入
};typedef int (*mln_core_init_t)(void);
typedef void (*mln_core_worker_process_t)(mln_event_t *);

一般情况下,在Melon的各个组件中,被用来作为初始化参数的结构体都以_attr结尾,且不会被typedef定义为某一类型。

函数

mln_core_init

int mln_core_init(struct mln_core_attr *attr);

描述:该函数是Melon库的整体初始化函数,会加载配置,并根据配置启用或停用Melon中框架部分功能,以及其他额外功能。

返回值:成功则返回0,否则返回-1。

举例:

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

配置

由于配置文件的加载是在Melon的初始化函数中被自动加载的,因此多数数据结构及函数是无需开发者关心的,这里仅给出开发者所需要的结构定义和函数声明。

在Melon中,配置被划分为两层,每一层为一个。最外层为main域,在main中允许出现以名称{...}扩住的子域,然而子域中不允许再出现子域。但在程序中,main与子域其实为同级关系,而非包含关系。

每个域内是若干配置项,配置项由配置指令名配置参数组成。

Melon的配置文件melon.conf会被安装在安装路径下的conf子目录中。

头文件

#include "mln_conf.h"

相关结构

typedef struct mln_conf_item_s    mln_conf_item_t;
struct mln_conf_item_s {enum {CONF_NONE = 0,CONF_STR,CONF_CHAR,CONF_BOOL,CONF_INT,CONF_FLOAT} type; //配置项参数类型union {mln_string_t *s;mln_s8_t c;mln_u8_t b;mln_sauto_t i;float f;} val; //配置项参数数据
};

在Melon中,配置参数分为5种类型(忽略NONE),分别为:

  • 字符串:以""扩住的字符集
  • 字符:以''扩住的字符
  • 布尔开关:onoff
  • 整型:十进制整数
  • 浮点型:十进制形式的实数

函数

mln_get_conf

mln_conf_t *mln_get_conf(void);

描述:获取全局配置结构。

返回值:mln_conf_t指针,若为NULL,则表明Melon并未进行初始化。

search

typedef mln_conf_domain_t *(*search_domain) (mln_conf_t *, char *);
typedef mln_conf_cmd_t    *(*search_cmd)    (mln_conf_domain_t *, char *);
typedef mln_conf_item_t   *(*search_item)   (mln_conf_cmd_t *, mln_u32_t);

描述:

在Melon中,所有配置都会被加载进mln_conf_t结构中,随后的获取则是通过这三个search函数进行的。这三个search函数指针依次分别为mln_conf_tmln_conf_domain_t以及mln_conf_cmd_t中的search成员。故在使用时,则是形如:

mln_conf_domain_t *d = conf->search(conf, "main");

这三个search函数分别是:

  • 从全局mln_conf_t中获取某个域结构mln_conf_domain_t
  • 从某个域(mln_conf_domain_t)中获取对应的配置指令项mln_conf_cmd_t
  • 从某个指令(mln_conf_cmd_t)中获取某个参数mln_conf_item_t

其中,最后一个search的第二个参数为参数下标,且下标从1开始而非0

在本篇末尾处将给出使用示例。

返回值:

正常情况下,只要所需配置在配置文件中,且配置被正常初始化,那么返回值则必不为NULL

mln_conf_set_hook

mln_conf_hook_t *mln_conf_set_hook(reload_handler reload, void *data);typedef int (*reload_handler)(void *);

描述:

Melon配置支持重载,重载的方式是设置重载回调函数,且允许设置多个。当执行重载时,新配置加载后,将调用这些回调函数。

回调函数的参数即为mln_conf_set_hook的第二个参数data

返回值:

  • mln_conf_set_hook:成功返回mln_conf_hook_t回调句柄,否则返回NULL
  • 回调函数:成功返回0,否则返回-1

mln_conf_unset_hook

void mln_conf_unset_hook(mln_conf_hook_t *hook);

描述:删除已设置的配置重载回调函数。

返回值:无

mln_conf_get_ncmd

mln_u32_t mln_conf_get_cmdNum(mln_conf_t *cf, char *domain);

描述:获取某个域下配置项的个数。

返回值:配置项个数

mln_conf_get_cmds

void mln_conf_get_cmds(mln_conf_t *cf, char *domain, mln_conf_cmd_t **vector);

描述:获取某个域内的全部配置项,这些配置项将被存放在vector中,vector需要在调用前分配好,配置项个数可以通过mln_conf_get_ncmd预先获取到。

返回值:无

mln_conf_get_narg

mln_u32_t mln_conf_get_argNum(mln_conf_cmd_t *cc);

描述:获取某个配置项的参数个数。

返回值:指令项参数个数

示例

#include <stdio.h>
#include "mln_core.h"static int global_init(void)
{mln_conf_t *cf;mln_conf_domain_t *d;mln_conf_cmd_t *c;mln_conf_item_t *i;cf = mln_get_conf();d = cf->search(cf, "main"); //如果main都不存在,那说明配追初始化有严重问题c = d->search(cf, "daemon"); //这里我们获取daemon配置项if(c == NULL) {mln_log(error, "daemon not exist.\n");return -1;//出错返回}i = c->search(c, 1); //daemon在配置文件中只有一个参数,配置参数的下标从1开始if (i == NULL) {mln_log(error, "Invalid daemon argument.\n");return -1;}if (i->type != CONF_BOOL) { //daemon 参数应该为布尔开关量mln_log(error, "Invalid type of daemon argument.\n");return -1;}mln_log(debug, "%u\n", i->val.b); //输出布尔量的值return 0;
}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 = NULL;return mln_core_init(&cattr);
}

在使用日志输出时,请确保Melon配置及文件中日志文件的父目录路径是否存在。

日志

头文件

#include "mln_log.h"

函数

mln_log

enum log_level {none,report,debug,warn,error
};void _mln_sys_log(enum log_level level, const char *file, const char *func, int line, char *msg, ...);#define mln_log(err_lv,msg,...) _mln_sys_log(err_lv, __FILE__, __FUNCTION__, __LINE__, msg, ## __VA_ARGS__)

描述:

日常开发中,经常被用到的是宏mln_log,它会将输出日志的文件、函数、行数都自行附加上。

日志分为5个等级,其级别由上至下依次增加。在配置文件中,有一配置项用于控制输出日志的级别,低于该级别的日志将不会进行输出:

log_level "none";

默认情况下为最低级别none

none与其他级别有所不同,该级别下,所有日志输出的内容完全为msg的内容,而不带有任何前缀信息,如:日期、进程号、文件名、函数名、行号等。

该函数需要在mln_core_init之后或其回调函数中使用,在mln_core_init之前使用将会出错,因为此时日志相关组件尚未被初始化。

字符串

头文件

mln_string.h

主要数据结构

typedef struct {mln_u8ptr_t  data; //数据存放的内存起始地址mln_u64_t    len; //数据字节长度mln_uauto_t  data_ref:1; //data是否是引用mln_uauto_t  pool:1; //本结构是否是由内存池分配mln_uauto_t  ref:30; //本结构所被引用的次数
} mln_string_t;

函数/宏列表

mln_string

mln_string(str)

描述:利用字符串常量str创建一个mln_string_t对象。用于定义mln_string_t变量的同时对其进行初始化。

返回值:mln_string_t类型结构体

举例:

void foo()
{mln_string_t s = mln_string("Hello");
}

mln_string_set

mln_string_set(pstr, s)

描述:用于将s这个字符串赋值给pstr这个mln_string_t指针所指向的结构。此时,data_ref成员会被置1。

返回值:无

举例:

void foo()
{char text[] = "hello";mln_string_t s;mln_string_set(&s, text);
}

mln_string_nset

mln_string_nset(pstr, s, n)

描述:与mln_string_set功能一样,只是pstr所指向的mln_string_t仅记录了s的前n个字节。

返回值:无

举例:

void foo()
{char text[] = "hello world";mln_string_t s;mln_string_nset(&s, text, 5); //利用mln_log的%S进行输出时,仅会输出hello
}

mln_string_ref

mln_string_ref(pstr)

描述:将pstr所指向的mln_string_t结构的ref成员累加1,用于直接引用pstr这个内存结构。在释放内存时,引用计数大于1时是不会实际释放内存的。

返回值:mln_string_t类型指针

void foo(mln_string_t s)
{mln_string_t *ref = mln_string_ref(s); //此时ref与s的内存地址完全相同...
}

mln_string_free

mln_string_free(pstr)

描述:释放ptrs所指向的mln_string_t结构内存,若ref大于1则仅递减引用计数,若data_ref为1,则不释放data成员指向的内存,否则释放data成员内存,随后释放pstr内存。释放时,会根据pool成员判断是释放回内存池,还是返还malloc库。

返回值:无

mln_string_new

mln_string_t *mln_string_new(const char *s);

描述:根据字符串常量s创建字符串结构,此时新字符串结构及其数据部分内存均由malloc库进行分配,并将s的内容拷贝进data成员中。

返回值:成功则返回mln_string_t指针,否则返回NULL

mln_string_pool_new

mln_string_t *mln_string_pool_new(mln_alloc_t *pool, const char *s);

描述:与mln_string_new功能一致,仅内存是由pool所指向的内存池中分配而来。

返回值:成功则返回mln_string_t指针,否则返回NULL

mln_string_dup

mln_string_t *mln_string_dup(mln_string_t *str);

描述:完全复制一份str,其内存均由malloc进行分配。

返回值:成功则返回mln_string_t指针,否则返回NULL

mln_string_pool_dup

mln_string_t *mln_string_pool_dup(mln_alloc_t *pool, mln_string_t *str);

描述:与mln_string_dup功能一致,仅内存是从pool所指向的内存池中分配而来。

返回值:成功则返回mln_string_t指针,否则返回NULL

mln_string_ndup

mln_string_t *mln_string_ndup(mln_string_t *str, mln_s32_t size);

描述:创建一个新字符串对象,并仅复制str中前size个字节数据。

返回值:成功则返回mln_string_t指针,否则返回NULL

mln_string_const_ndup

mln_string_t *mln_string_const_ndup(char *str, mln_s32_t size);

描述:创建一个新字符串对象,并仅复制str中前size个字节数据。

返回值:成功则返回mln_string_t指针,否则返回NULL

mln_string_ref_dup

mln_string_t *mln_string_ref_dup(mln_string_t *str);

描述:创建一个新的字符串结构,但结构中的data成员指向strdata成员所指向的地址,且新结构中data_ref会被置位。

返回值:成功则返回mln_string_t指针,否则返回NULL

mln_string_const_ref_dup

mln_string_t *mln_string_const_ref_dup(char *s);

描述:创建一个新的字符串结构,但结构中的data成员指向s,且新结构中data_ref会被置位。

返回值:成功则返回mln_string_t指针,否则返回NULL

mln_string_strseqcmp

int mln_string_strseqcmp(mln_string_t *s1, mln_string_t *s2);

描述:比较s1s2的数据,如果短的一方刚好与长的一方的前面完全匹配,则长的一方大于短的一方。

返回值:

  • -1 - s1s2
  • 1 - s1s2
  • 0 - 二者相同

举例:

int main(void)
{mln_string_t s1 = mln_string("abcd");mln_string_t s2 = mln_string("abcdefg");printf("%d", mln_string_strseqcmp(&s1, &s2)); //-1return 0;
}

mln_string_strcmp

int mln_string_strcmp(mln_string_t *s1, mln_string_t *s2);

描述:比较s1s2中数据的大小。

返回值:

  • -1 - s1s2
  • 1 - s1s2
  • 0 - 二者相同

mln_string_const_strcmp

int mln_string_const_strcmp(mln_string_t *s1, char *s2);

描述:比较s1所记录的数据与s2的大小。

返回值:

  • -1 - s1s2
  • 1 - s1s2
  • 0 - 二者相同

mln_string_strncmp

int mln_string_strncmp(mln_string_t *s1, mln_string_t *s2, mln_u32_t n);

描述:比较s1s2的前n个字节的大小。

返回值:

  • -1 - s1s2
  • 1 - s1s2
  • 0 - 二者相同

mln_string_const_strncmp

int mln_string_const_strncmp(mln_string_t *s1, char *s2, mln_u32_t n);

描述:比较s1所记录的数据与s2的前n个字节的大小。

返回值:

  • -1 - s1s2
  • 1 - s1s2
  • 0 - 二者相同

mln_string_strcasecmp

int mln_string_strcasecmp(mln_string_t *s1, mln_string_t *s2);

描述:比较s1s2数据的大小,且忽略大小写。

返回值:

  • -1 - s1s2
  • 1 - s1s2
  • 0 - 二者相同

mln_string_const_strcasecmp

int mln_string_const_strcasecmp(mln_string_t *s1, char *s2);

描述:比较s1所记录的数据与s2的大小,且忽略大小写。

返回值:

  • -1 - s1s2
  • 1 - s1s2
  • 0 - 二者相同

mln_string_const_strncasecmp

int mln_string_const_strncasecmp(mln_string_t *s1, char *s2, mln_u32_t n);

描述:比较s1所记录的数据与s2的前n个字节的大小,且忽略大小写。

返回值:

  • -1 - s1s2
  • 1 - s1s2
  • 0 - 二者相同

mln_string_strncasecmp

int mln_string_strncasecmp(mln_string_t *s1, mln_string_t *s2, mln_u32_t n);

描述:比较s1s2所记录数据的前n个字节的大小,且忽略大小写。

返回值:

  • -1 - s1s2
  • 1 - s1s2
  • 0 - 二者相同

mln_string_strstr

char *mln_string_strstr(mln_string_t *text, mln_string_t *pattern);

描述:匹配text所记录的数据中与pattern中数据一样的起始地址。

返回值:若匹配成功,则返回textdata成员所指向地址中的对应地址;否则返回NULL

mln_string_const_strstr

char *mln_string_const_strstr(mln_string_t *text, char *pattern);

描述:匹配text所记录的数据中与pattern一样的起始地址。

返回值:若匹配成功,则返回textdata成员所指向地址中的对应地址;否则返回NULL

mln_string_new_strstr

mln_string_t *mln_string_new_strstr(mln_string_t *text, mln_string_t *pattern);

描述:与mln_string_strstr功能一致,但返回的是由mln_string_t结构包装后的字符串。

返回值:成功则返回mln_string_t指针,否则返回NULL

mln_string_new_const_strstr

mln_string_t *mln_string_new_const_strstr(mln_string_t *text, char *pattern);

描述:与mln_string_const_strstr功能一致,但返回的是由mln_string_t结构包装后的字符串。

返回值:成功则返回mln_string_t指针,否则返回NULL

mln_string_kmp

char *mln_string_kmp(mln_string_t *text, mln_string_t *pattern);

描述:与mln_string_strstr功能一致,但是是由KMP算法实现的。KMP算法适用场景是,text中有较多与pattern前缀相同的字符串的情况。例如: text中包含aaaaaaaaaabcpattern中包含ab,此时,KMP算法性能将高于朴素算法。

返回值:若匹配成功,则返回textdata成员所指向地址中的对应地址;否则返回NULL

mln_string_const_kmp

char *mln_string_const_kmp(mln_string_t *text, char *pattern);

描述:与mln_string_kmp功能一致,但pattern为字符指针类型。

返回值:若匹配成功,则返回textdata成员所指向地址中的对应地址;否则返回NULL

mln_string_new_kmp

mln_string_t *mln_string_new_kmp(mln_string_t *text, mln_string_t *pattern);

描述:与mln_string_kmp功能一致,但返回的是由mln_string_t结构包装后的数据。

返回值:成功则返回mln_string_t指针,失败则返回NULL

mln_string_new_const_kmp

mln_string_t *mln_string_new_const_kmp(mln_string_t *text, char *pattern);

描述:与mln_string_const_kmp功能一致,但返回的是由mln_string_t结构包装后的数据。

返回值:成功则返回mln_string_t指针,失败则返回NULL

mln_string_slice

mln_string_t *mln_string_slice(mln_string_t *s, const char *sep_array/*ended by \0*/);

描述:seq_array是一个字符数组且以0结尾,该数组的每一个字符都是一个分隔标志。函数会扫描s的数据部分,当数据中遇到seq_array中的任意一个字符时都会被进行分割,连续遇到多个时仅分割一次,且分割后,分隔符不会出现在被分割后的字符串中。

返回值:成功则返回mln_string_t数组,否则返回NULL。数组的最后一个元素的len0,且dataNULL

举例:

int main(void)
{mln_string_t s = mln_string("abc-def-=ghi");mln_string_t *str, *arr = mln_string_slice(&s, "-=");for (str = arr; str->data != NULL; ++str) {mln_log(debug, "%S", str);}mln_string_slice_free(arr);return 0;
}

mln_string_slice_free

void mln_string_slice_free(mln_string_t *array);

描述:释放由mln_string_slice函数创建的mln_string_t数组。

返回值:无

mln_string_strcat

mln_string_t *mln_string_strcat(mln_string_t *s1, mln_string_t *s2);

描述:创建一个新的mln_string_t结构,其数据为s1s2依此顺序拼接后的结果。

返回值:成功则返回mln_string_t指针,否则返回NULL

mln_string_pool_strcat

mln_string_t *mln_string_pool_strcat(mln_alloc_t *pool, mln_string_t *s1, mln_string_t *s2);

描述:与mln_string_strcat功能一致,仅新的结构所使用内存由pool指向的内存池分配。

返回值:成功则返回mln_string_t指针,否则返回NULL

双向链表

头文件

#include "mln_defs.h"

函数/宏

MLN_CHAIN_FUNC_DECLARE

 MLN_CHAIN_FUNC_DECLARE(prefix,type,ret_attr,func_attr);

描述:本宏用于对双向链表的添加操作和删除操作函数进行声明,其中:

  • prefix:为两个函数名的前缀,这是为了允许在一个源文件内为多个双向链表进行函数声明。
  • type:链表节点的类型
  • ret_attr:两个函数的操作域类型和返回值类型
  • func_attr:对函数参数的约束(仅限于Linux中),若无则留空即可

MLN_CHAIN_FUNC_DEFINE

MLN_CHAIN_FUNC_DEFINE(prefix,type,ret_attr,prev_ptr,next_ptr);ret_attr prefix##_chain_add(type **head, type **tail, type *node);
ret_attr prefix##_chain_del(type **head, type **tail, type *node);

描述:本宏用于定义双向链表的添加和删除操作函数,其中:

  • prefix:为两个函数名的前缀,这是为了允许在一个源文件内为多个双向链表进行函数声明。
  • type:链表节点的类型
  • ret_attr:两个函数的操作域类型和返回值类型
  • prev_ptr:链表节点中指向前一节点的指针名
  • next_ptr:链表节点中指向后一节点的指针名

chain_addchain_del分别为添加和删除节点函数,两个函数的参数为:

  • head:二级指针,用于在操作函数内对头指针自动修改
  • tail:二级指针,用于在操作函数内对尾指针自动修改
  • node:被加入的节点指针,其前后指向的指针可能会被修改

示例

#include <stdio.h>
#include <stdlib.h>
#include "mln_defs.h"typedef struct chain_s {int             val;struct chain_s *prev;struct chain_s *next;
} chain_t;MLN_CHAIN_FUNC_DECLARE(test, chain_t, static inline void, );
MLN_CHAIN_FUNC_DEFINE(test, chain_t, static inline void, prev, next);int main(void)
{int i;chain_t *head = NULL, *tail = NULL, *c;for (i = 0; i < 10; ++i) {c = (chain_t *)malloc(sizeof(chain_t));if (c == NULL) {fprintf(stderr, "malloc failed.\n");return -1;}c->val = i;c->prev = c->next = NULL;test_chain_add(&head, &tail, c);}for (c = head; c != NULL; c = c->next) {printf("%d\n", c->val);}return 0;
}

头文件

#include "mln_stack.h"

函数/宏

mln_stack_init

mln_stack_t *mln_stack_init(struct mln_stack_attr *attr);struct mln_stack_attr {stack_free               free_handler;//栈节点数据释放函数stack_copy               copy_handler;//栈节点数据复制函数mln_u32_t                cache:1;//是否缓存栈节点结构
};typedef void (*stack_free)(void *);
typedef void *(*stack_copy)(void *, void *);

描述:

初始化栈结构。

free_handler:是入栈数据的释放函数,由于入栈数据可能为自定义数据结构,因此若需释放,可对此进行设置否则置NULL

copy_handler:复制栈节点数据。

cache:是否缓存全部栈节点结构内存以提升效率(非用户数据)。

stack_free的参数为用户自定义数据的数据结构指针。

stack_copy的参数分别为:被复制的栈节点数据的数据结构指针 和 mln_stack_dup函数的第二个参数(即用户自定义数据),这个回调函数仅在mln_stack_dup函数中被调用。

返回值:成功则返回栈指针,否则为NULL

mln_stack_destroy

void mln_stack_destroy(mln_stack_t *st);

描述:销毁栈结构,并释放栈节点内数据资源。

返回值:无

mln_stack_push

int mln_stack_push(mln_stack_t *st, void *data);

描述:将数据data压入栈st中。

返回值:成功返回0,否则返回-1

mln_stack_pop

void *mln_stack_pop(mln_stack_t *st);

描述:将栈st的栈顶元素数据弹出。

返回值:若栈内无元素则为NULL,否则为栈节点内的数据指针

mln_stack_empty

mln_stack_empty(s)

描述:判断栈是否为空。

返回值:空为非0,否则为0

mln_stack_top

mln_stack_top(st)

描述:获取栈顶元素数据。

返回值:若栈st为空则返回NULL,否则为栈顶节点内的数据指针

mln_stack_dup

mln_stack_t *mln_stack_dup(mln_stack_t *st, void *udata);

描述:完全复制栈studata为用户提供的额外数据。

返回值:若成功则返回新栈指针,否则返回NULL

mln_stack_scan_all

int mln_stack_scan_all(mln_stack_t *st, stack_scan scanner, void *data);typedef int (*stack_scan)(void *, void *);

描述:

从栈顶向栈底遍历栈st的每一个栈内元素数据。scanner为数据访问函数,data为遍历时的额外用户数据。

stack_scan有两个参数,分别为:栈节点内数据指针 和 data参数。

返回值:

  • mln_stack_scan_all:全部遍历完则返回0,否则返回-1
  • stack_scan:若想中断遍历则返回小于0的值,否则返回值大于等于0

队列

头文件

#include "mln_queue.h"

函数/宏

mln_queue_init

mln_queue_t *mln_queue_init(struct mln_queue_attr *attr);struct mln_queue_attr {mln_uauto_t            qlen; //队列长度queue_free             free_handler; //队列节点数据的释放函数
};
typedef void (*queue_free)(void *);

描述:创建队列。

本队列为固定长度队列,因此attr.qlen就是队列的长度。free_handler为释放函数,用于释放队列内每个成员中的数据。若不需要释放则置NULL即可。

释放函数的参数即为队列每个成员的数据结构指针。

返回值:成功则返回mln_queue_t类型的队列指针,失败则返回NULL

mln_queue_destroy

void mln_queue_destroy(mln_queue_t *q);

描述:销毁队列。

队列销毁时,会根据free_handler的设置而自动释放队列成员的数据。

返回值:无

mln_queue_append

int mln_queue_append(mln_queue_t *q, void *data);

描述:将数据data追加进队列q的末尾。

返回值:若队列已满则返回-1,成功返回0

mln_queue_get

void *mln_queue_get(mln_queue_t *q);

描述:获取队首成员的数据。

返回值:成功则返回数据指针,若队列为空则返回NULL

mln_queue_remove

void mln_queue_remove(mln_queue_t *q);

描述:删除队首元素,但不释放资源。

返回值:无

mln_queue_search

void *mln_queue_search(mln_queue_t *q, mln_uauto_t index);

描述:查找并返回从队列q队首开始的第index成员的数据,下标从0开始。

返回值:成功则返回数据指针,否则为NULL

mln_queue_free_index

void mln_queue_free_index(mln_queue_t *q, mln_uauto_t index);

描述:释放队列q内指定下标index的成员,并根据free_handler释放其数据。

返回值:无

mln_queue_scan_all

int mln_queue_scan_all(mln_queue_t *q, queue_scan scan_handler, void *udata);typedef int (*queue_scan)(void *, void *);

描述:遍历每一个队列成员。

udata为辅助遍历的自定义结构指针,若不需要可置NULL

scan_handler的两个参数分别为:成员数据udata

返回值:遍历完成返回0,被中断则返回-1

mln_queue_empty

mln_queue_empty(q)

描述:判断队列q是否为空队列。

返回值:空则为非0,否则为0

mln_queue_full

mln_queue_full(q)

描述:判断队列是否已满。

返回值:满则为非0,否则为0

mln_queue_length

mln_queue_length(q)

描述:获取队列q的总长度。

返回值:无符号整型长度值

mln_queue_element

mln_queue_element(q)

描述:获取队列q中当前的成员数量。

返回值:无符号整型数量值

示例

#include <stdio.h>
#include <stdlib.h>
#include "mln_core.h"
#include "mln_log.h"
#include "mln_queue.h"int main(int argc, char *argv[])
{int i = 10;mln_queue_t *q;struct mln_queue_attr qattr;struct mln_core_attr cattr;cattr.argc = argc;cattr.argv = argv;cattr.global_init = NULL;cattr.worker_process = NULL;if (mln_core_init(&cattr) < 0) {fprintf(stderr, "init failed\n");return -1;}qattr.qlen = 10;qattr.free_handler = NULL;q = mln_queue_init(&qattr);if (q == NULL) {mln_log(error, "queue init failed.\n");return -1;}mln_queue_append(q, &i);mln_log(debug, "%d\n", *(int *)mln_queue_get(q));mln_queue_destroy(q);return 0;
}

内存池

Melon中,内存池分为两类:

  • 堆内存
  • 共享内存

其中,共享内存内存池只允许主子进程之间共享数据(兄弟进程之间也共享)。即使用时,由主进程创建共享内存内存池,然后创建子进程。

头文件

#include "mln_alloc.h"

函数

mln_alloc_init

mln_alloc_t *mln_alloc_init(void);

描述:创建堆内存内存池。

返回值:成功则返回内存池结构指针,否则返回NULL

mln_alloc_shm_init

mln_alloc_t *mln_alloc_shm_init(mln_size_t size);

描述:创建共享内存内存池。本池建立时需要给出池大小size(单位字节),一旦创建完毕后则后续无法再扩大。

返回值:成功则返回内存池结构指针,否则返回NULL

mln_alloc_destroy

void mln_alloc_destroy(mln_alloc_t *pool);

描述:销毁内存池。销毁操作会将内存池中管理的所有内存进行统一释放。

返回值:无

mln_alloc_m

void *mln_alloc_m(mln_alloc_t *pool, mln_size_t size);

描述:从内存池pool中分配一个size大小的内存。如果内存池是共享内存内存池,则会从共享内存中进行分配,否则从堆内存中进行分配。

返回值:成功则返回内存起始地址,否则返回NULL

mln_alloc_c

void *mln_alloc_c(mln_alloc_t *pool, mln_size_t size);

描述:从内存池pool中分配一个size大小的内存,且该内存会被清零。

返回值:成功则返回内存起始地址,否则返回NULL

mln_alloc_re

void *mln_alloc_re(mln_alloc_t *pool, void *ptr, mln_size_t size);

描述:从内存池pool中分配一个size大小的内存,并将ptr指向的内存中的数据拷贝到新的内存中。

ptr必须为内存池分配的内存起始地址。若size0ptr指向的内存会被释放。

返回值:成功则返回内存起始地址,否则返回NULL

mln_alloc_free

void mln_alloc_free(void *ptr);

描述:释放ptr指向的内存。注意ptr必须为分配函数返回的地址,而不可以是分配的内存中某一个位置。

返回值:无

mln_alloc_shm_rdlock

int mln_alloc_shm_rdlock(mln_alloc_t *pool);

描述:读锁定。本函数会等待直到锁资源可用,并将之锁定。

本函数及后续锁相关函数均用于共享内存内存池。

出于对读多写少的场景考虑,给共享内存配备的是读写锁,而非互斥锁。

返回值:成功返回0,否则返回非0

mln_alloc_shm_tryrdlock

int mln_alloc_shm_tryrdlock(mln_alloc_t *pool);

描述:尝试读锁定。本函数不会挂起等待锁资源可用。

返回值:成功返回0,否则返回非0

mln_alloc_shm_wrlock

int mln_alloc_shm_wrlock(mln_alloc_t *pool);

描述:写锁定。本函数会等待直到锁资源可用,并将之锁定。

返回值:成功返回0,否则返回非0

mln_alloc_shm_trywrlock

int mln_alloc_shm_trywrlock(mln_alloc_t *pool);

描述:尝试写锁定。本函数不会挂起等待锁资源可用。

返回值:成功返回0,否则返回非0

mln_alloc_shm_unlock

int mln_alloc_shm_unlock(mln_alloc_t *pool);

描述:解除锁定。

返回值:成功返回0,否则返回非0

示例

#include <stdio.h>
#include <stdlib.h>
#include "mln_core.h"
#include "mln_log.h"
#include "mln_alloc.h"int main(int argc, char *argv[])
{char *p;mln_alloc_t *pool;struct mln_core_attr cattr;cattr.argc = argc;cattr.argv = argv;cattr.global_init = NULL;cattr.worker_process = NULL;if (mln_core_init(&cattr) < 0) {fprintf(stderr, "init failed\n");return -1;}pool = mln_alloc_init();if (pool == NULL) {mln_log(error, "pool init failed\n");return -1;}p = (char *)mln_alloc_m(pool, 6);if (p == NULL) {mln_log(error, "alloc failed\n");return -1;}memcpy(p, "hello", 5);p[5] = 0;mln_log(debug, "%s\n", p);mln_alloc_free(p);return 0;
}

线程池

在Melon中支持两种多线程模式,线程池是其中一种,另一种请参见后续的多线程框架文章。

注意:在每个进程中仅允许存在一个线程池。

头文件

#include "mln_thread_pool.h"

函数

mln_thread_pool_run

int mln_thread_pool_run(struct mln_thread_pool_attr *tpattr);struct mln_thread_pool_attr {void                              *main_data;mln_thread_process                 child_process_handler;mln_thread_process                 main_process_handler;mln_thread_dataFree                free_handler;mln_u64_t                          cond_timeout; /*ms*/mln_u32_t                          max;mln_u32_t                          concurrency;
};
typedef int  (*mln_thread_process)(void *);
typedef void (*mln_thread_dataFree)(void *);

描述:创建并运行内存池。

线程池由主线程进行管理和做一部分处理后下发任务,子线程组则接受任务进行处理。

初始状态下,是不存在子线程的,当有任务需要下发时会自动创建子线程。当任务处理完后,子线程会延迟释放,避免频繁分配释放资源。

其中参数结构体的每个成员含义如下:

  • main_data 为主线程的用户自定义数据。
  • child_process_handler 每个子线程的处理函数,该函数有一个参数为主线程下发任务时给出的数据结构指针,返回值为0表示处理正常,非0表示处理异常,异常时会有日志输出。
  • main_process_handler 主线程的处理函数,该函数有一个参数为main_data,返回值为0表示处理正常,非0表示处理异常,异常时会有日志输出。一般情况下,主线程处理函数不应随意自行返回,一旦返回代表线程池处理结束,线程池会被销毁
  • free_handler 为资源释放函数。其资源为主线程下发给子线程的数据结构指针所指向的内容。
  • cond_timeout为闲置子线程回收定时器,单位为毫秒。当子线程无任务处理,且等待时间超过该定时器时长后,会自行退出。
  • max线程池允许的最大子线程数量。
  • concurrency用于pthread_setconcurrency设置并行级别参考值,但部分系统并为实现该功能,因此不应该过多依赖该值。在Linux下,该值设为零表示交由本系统实现自行确定并行度。

返回值:本函数返回值与主线程处理函数的返回值保持一致

mln_thread_pool_addResource

int mln_thread_pool_addResource(void *data);

描述:将资源data放入到资源池中。本函数仅应由主线程调用,用于主线程向子线程下发任务所用。

返回值:成功则返回0,否则返回非0

mln_thread_quit

void mln_thread_quit(void);

描述:本函数用于告知线程池,关闭并销毁线程池。

返回值:无

mln_thread_ResourceInfo

void mln_thread_ResourceInfo(struct mln_thread_pool_info *info);struct mln_thread_pool_info {mln_u32_t                          max_num;mln_u32_t                          idle_num;mln_u32_t                          cur_num;mln_size_t                         res_num;
};

描述:获取当前线程池信息。信息会写入参数结构体中,结构体每个参数含义如下:

  • max_num:线程池最大子线程数量
  • idle_num:当前闲置子线程数量
  • cur_num:当前子线程数量(包含闲置和工作中的子线程)
  • res_num:当前尚未被处理的资源数量

返回值:无

示例

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "mln_core.h"
#include "mln_thread_pool.h"
#include "mln_log.h"static int main_process_handler(void *data);
static int child_process_handler(void *data);
static void free_handler(void *data);int main(int argc, char *argv[])
{struct mln_core_attr cattr;struct mln_thread_pool_attr tpattr;cattr.argc = argc;cattr.argv = argv;cattr.global_init = NULL;cattr.worker_process = NULL;if (mln_core_init(&cattr) < 0) {return -1;}tpattr.dataForMain = NULL;tpattr.child_process_handler = child_process_handler;tpattr.main_process_handler = main_process_handler;tpattr.free_handler = free_handler;tpattr.condTimeout = 10;tpattr.max = 10;tpattr.concurrency = 10;return mln_thread_pool_run(&tpattr);
}static int child_process_handler(void *data)
{mln_log(none, "%s\n", (char *)data);return 0;
}static int main_process_handler(void *data)
{int n;char *text;while (1) {if ((text = (char *)malloc(16)) == NULL) {return -1;}n = snprintf(text, 15, "hello world");text[n] = 0;mln_thread_pool_addResource(text);usleep(1000);}
}static void free_handler(void *data)
{free(data);
}

JSON

头文件

#include "mln_json.h"

函数/宏

mln_json_new

mln_json_t *mln_json_new(void);

描述:新建json节点,用于生成json字符串之用。

返回值:成功则返回mln_json_t指针,否则返回NULL

mln_json_parse

mln_json_t *mln_json_parse(mln_string_t *jstr);

描述:将JSON字符串jstr解析成数据结构。

返回值:成功则返回mln_json_t指针,否则返回NULL

mln_json_free

void mln_json_free(void *json);

描述:释放mln_json_t类型的json节点内存。

返回值:无

mln_json_dump

void mln_json_dump(mln_json_t *j, int n_space, char *prefix);

描述:将json节点j的详细信息输出到标准输出。n_space表示当前缩进空格数,prefix为输出内容的前缀。

返回值:无

mln_json_generate

mln_string_t *mln_json_generate(mln_json_t *j);

描述:由mln_json_t节点结构生成JSON字符串。

返回值:成功返回mln_string_t字符串指针,否则返回NULL

mln_json_search_value

mln_json_t *mln_json_search_value(mln_json_t *j, mln_string_t *key);

描述:从节点j中搜索key为key的value内容。此时,j必须为对象类型(有key: value对的字典)。

返回值:成功则返回mln_json_t类型的value,否则返回NULL

mln_json_search_element

mln_json_t *mln_json_search_element(mln_json_t *j, mln_uauto_t index);

描述:从节点j中搜索下标为index的元素内容。此时,j必须为数组类型。

返回值:成功则返回mln_json_t类型的元素节点,否则返回NULL

mln_json_get_array_length

mln_uauto_t mln_json_get_array_length(mln_json_t *j);

描述:获取数组的长度。此时j必须为数组类型。

返回值:数组长度

mln_json_update_obj

int mln_json_update_obj(mln_json_t *j, mln_json_t *key, mln_json_t *val);

描述:将keyval对添加到j JSON节点中。此时,j需为对象类型。若key已经存在,则将原本value替换为val

返回值:成功则返回0,否则返回-1

mln_json_add_element

int mln_json_add_element(mln_json_t *j, mln_json_t *value);

描述:将value加入到数组类型的JSON结构j中。

返回值:成功则返回0,否则返回-1

mln_json_update_element

int mln_json_update_element(mln_json_t *j, mln_json_t *value, mln_uauto_t index);

描述:将value更新到数组类型JSON结构j的下标为index的位置上。

返回值:成功则返回0,否则返回-1

mln_json_reset

void mln_json_reset(mln_json_t *j);

描述:重置JSON节点j数据结构,将其内存进行释放。

返回值:无

mln_json_remove_object

mln_json_t *mln_json_remove_object(mln_json_t *j, mln_string_t *key);

描述:将key值为key的键值对从对象类型的JSON结构j中删除,并将相应value返回。

返回值:存在则返回对应value部分的JSON节点,否则返回NULL

mln_json_remove_element

mln_json_t *mln_json_remove_element(mln_json_t *j, mln_uauto_t index);

描述:将下标为index的元素从数组类型JSON节点上删除并返回。

返回值:存在则返回元素指针,否则返回NULL

is_type

M_JSON_IS_OBJECT(json)
M_JSON_IS_ARRAY(json)
M_JSON_IS_STRING(json)
M_JSON_IS_NUMBER(json)
M_JSON_IS_TRUE(json)
M_JSON_IS_FALSE(json)
M_JSON_IS_NULL(json)
M_JSON_IS_NONE(json)

描述:判断mln_json_t结构的json类型,依次分别为:对象、数组、字符串、数字、布尔真、布尔假、NULL、无类型。

返回值:满足条件返回非0,否则返回0

set_type

M_JSON_SET_TYPE_NONE(json)
M_JSON_SET_TYPE_OBJECT(json)
M_JSON_SET_TYPE_ARRAY(json)
M_JSON_SET_TYPE_STRING(json)
M_JSON_SET_TYPE_NUMBER(json)
M_JSON_SET_TYPE_TRUE(json)
M_JSON_SET_TYPE_FALSE(json)
M_JSON_SET_TYPE_NULL(json)

描述:给mln_json_t类型的json节点设置类型,依次分别为:无类型、对象、数组、字符串、数字、布尔真、布尔假、NULL。

返回值:无

get_data

M_JSON_GET_DATA_OBJECT(json)
M_JSON_GET_DATA_ARRAY(json)
M_JSON_GET_DATA_STRING(json)
M_JSON_GET_DATA_NUMBER(json)
M_JSON_GET_DATA_TRUE(json)
M_JSON_GET_DATA_FALSE(json)
M_JSON_GET_DATA_NULL(json)

描述:获取mln_json_t类型的json节点中对应类型的数据部分。类型依次为:对象、数组、字符串、数字、布尔真、布尔假、NULL。

返回值:

  • 对象类型为mln_hash_t类型指针
  • 数组类型为mln_rbtree_t类型指针
  • 字符串类型为mln_string_t类型指针
  • 数字类型为double类型值
  • 布尔真为mln_u8_t类型值
  • 布尔假为mln_u8_t类型值
  • NULL类型为mln_u8ptr_t类型的NULL值

set_data

M_JSON_SET_DATA_STRING(json,str)
M_JSON_SET_DATA_NUMBER(json,num)
M_JSON_SET_DATA_TRUE(json)
M_JSON_SET_DATA_FALSE(json)
M_JSON_SET_DATA_NULL(json)

描述:给不同类型的JSON节点json设置数据值。对象和数组类型分别使用哈希表和红黑树函数进行操作,其余类型用上述宏进行设置。

注意:这里设置的字符串必须是从内存池或堆中分配的内存,栈中内存会出现段错误,因为赋值时不会在宏内自动复制一份而是直接使用。

返回值:无

M_JSON_SET_INDEX

M_JSON_SET_INDEX(json,i)

描述:设置mln_json_t类型节点json的下标为index。该宏用于生成JSON字符串中数组的部分。

返回值:无

示例

#include <stdio.h>
#include <stdlib.h>
#include "mln_core.h"
#include "mln_log.h"
#include "mln_string.h"
#include "mln_json.h"int main(int argc, char *argv[])
{mln_json_t *j = NULL, *key = NULL, *val = NULL;mln_string_t s1 = mln_string("name");mln_string_t s2 = mln_string("Tom");mln_string_t *res;struct mln_core_attr cattr;cattr.argc = argc;cattr.argv = argv;cattr.global_init = NULL;cattr.worker_process = NULL;if (mln_core_init(&cattr) < 0) {fprintf(stderr, "init failed\n");return -1;}key = mln_json_new();if (key == NULL) {mln_log(error, "init key failed\n");goto err;}M_JSON_SET_TYPE_STRING(key);M_JSON_SET_DATA_STRING(key, mln_string_dup(&s1));//注意,一定是要自行分配内存,不可直接使用栈中内存val = mln_json_new();if (val == NULL) {mln_log(error, "init val failed\n");goto err;}M_JSON_SET_TYPE_STRING(val);M_JSON_SET_DATA_STRING(val, mln_string_dup(&s2));//注意,一定是要自行分配内存,不可直接使用栈中内存j = mln_json_new();if (j == NULL) {mln_log(error, "init object failed\n");goto err;}if (mln_json_update_obj(j, key, val) < 0) {mln_log(error, "update object failed\n");goto err;}key = val = NULL;res = mln_json_generate(j);mln_json_free(j);if (res == NULL) {mln_log(error, "generate failed\n");goto err;}mln_log(debug, "%S\n", res);j = mln_json_parse(res);mln_string_free(res);mln_json_dump(j, 0, NULL);mln_json_free(j);return 0;err:if (j != NULL) mln_json_free(j);if (key != NULL) mln_json_free(key);if (val != NULL) mln_json_free(val);return -1;
}

HTTP

头文件

#include "mln_http.h"

函数/宏

mln_http_init

mln_http_t *mln_http_init(mln_tcp_conn_t *connection, void *data, mln_http_handler body_handler);typedef int (*mln_http_handler)(mln_http_t *, mln_chain_t **, mln_chain_t **);

描述:创建并初始化mln_http_t结构。connection是TCP结构,内含TCP套接字。data为体处理函数的用户自定义数据部分,用于辅助请求或响应体的处理。body_handler是体处理函数,该函数会在每次调用mln_http_parsemln_http_generate函数时被调用。体处理函数有三个参数,分别为:http结构,用于解析或生成HTTP报文的双向链表的头和尾节点。

返回值:成功则返回mln_http_t结构指针,否则返回NULL

mln_http_destroy

void mln_http_destroy(mln_http_t *http);

描述:销毁http结构并释放资源。

返回值:无

mln_http_reset

void mln_http_reset(mln_http_t *http);

描述:重置http结构,但不会将结构释放,可用于下一次处理。

返回值:无

mln_http_parse

int mln_http_parse(mln_http_t *http, mln_chain_t **in);

描述:用于解析HTTP报文,并将解析的结果写入http中。

返回值:

  • M_HTTP_RET_DONE 解析完成
  • M_HTTP_RET_OK 解析未完成但未出错,继续传入新的数据使解析完成
  • M_HTTP_RET_ERROR 解析失败

mln_http_generate

int mln_http_generate(mln_http_t *http, mln_chain_t **out_head, mln_chain_t **out_tail);

描述:将http中HTTP相关信息生成HTTP报文。报文可能不会一次性生成完全,因此可以多次调用。已生成的报文将会存放在out_headout_tail指定的双向链表中。

返回值:

  • M_HTTP_RET_DONE 生成完成
  • M_HTTP_RET_OK 生成未完成但未出错
  • M_HTTP_RET_ERROR 生成失败

mln_http_set_field

int mln_http_set_field(mln_http_t *http, mln_string_t *key, mln_string_t *val);

描述:设置HTTP头字段。若头字段key存在,则会将val替换原有值。

返回值:

  • M_HTTP_RET_OK 处理成功
  • M_HTTP_RET_ERROR处理失败

mln_http_get_field

mln_string_t *mln_http_get_field(mln_http_t *http, mln_string_t *key);

描述:获取HTTP头字段中键为key的值。

返回值:成功则返回值字符串结构指针,否则返回NULL

mln_http_field_iterator

mln_string_t *mln_http_field_iterator(mln_http_t *http, mln_string_t *key);

描述:每次返回一个键为key的头字段值(即假设存在多个相同键名的头字段)。

返回值:成功则返回值字符串结构指针,否则返回NULL

mln_http_drop_field

void mln_http_drop_field(mln_http_t *http, mln_string_t *key);

描述:移除头字段key及其值。

返回值:无

mln_http_dump

void mln_http_dump(mln_http_t *http);

描述:将HTTP信息输出到标准输出,用于调试之用。

返回值:无

mln_http_get_connection

mln_http_get_connection(h)

描述:获取类型为mln_http_th中TCP链接结构。

返回值:mln_tcp_conn_t类型指针

mln_http_set_connection

mln_http_set_connection(h,c)

描述:将mln_http_t类型的h中TCP链接结构设置为mln_tcp_conn_t类型的c

返回值:无

mln_http_get_pool

mln_http_get_pool(h)

描述:获取类型为mln_http_th中内存池结构。

返回值:mln_alloc_t类型指针

mln_http_set_pool

mln_http_set_pool(h,p)

描述:将mln_http_t类型的h中内存池设置为mln_alloc_t类型的p

返回值:无

mln_http_get_data

mln_http_get_data(h)

描述:获取类型为mln_http_th中辅助体处理函数的用户自定义数据。

返回值:用户自定义数据指针

mln_http_set_data

mln_http_set_data(h,d)

描述:将mln_http_t类型的h中辅助体处理函数的用户自定义数据设置为d

返回值:无

mln_http_get_uri

mln_http_get_uri(h)

描述:获取类型为mln_http_th中URI字符串。

返回值:mln_string_t类型指针

mln_http_set_uri

mln_http_set_uri(h,u)

描述:将mln_http_t类型的h中URI设置为mln_string_t类型指针的u

返回值:无

mln_http_get_args

mln_http_get_args(h)

描述:获取类型为mln_http_th中参数字符串。

返回值:mln_string_t类型指针

mln_http_set_args

mln_http_set_args(h,a)

描述:将mln_http_t类型的h中参数设置为mln_string_t类型指针的a

返回值:无

mln_http_get_status

mln_http_get_status(h)

描述:获取类型为mln_http_th中响应状态字,例如200 400等。

返回值:整型状态字

mln_http_set_status

mln_http_set_status(h,s)

描述:将mln_http_t类型的h中响应状态字设置为整型的s

返回值:无

mln_http_get_method

mln_http_get_method(h)

描述:获取类型为mln_http_th中方法字段

返回值:

  • M_HTTP_GET
  • M_HTTP_POST
  • M_HTTP_HEAD
  • M_HTTP_PUT
  • M_HTTP_DELETE
  • M_HTTP_TRACE
  • M_HTTP_CONNECT
  • M_HTTP_OPTIONS

mln_http_set_method

mln_http_set_method(h,m)

描述:将mln_http_t类型的h中请求方法设置为mm的可用值参考mln_http_get_method的返回值部分。

返回值:无

mln_http_get_version

mln_http_get_version(h)

描述:获取类型为mln_http_th中HTTP版本

返回值:

  • M_HTTP_VERSION_1_0 HTTP 1.0
  • M_HTTP_VERSION_1_1 HTTP 1.1

mln_http_set_version

mln_http_set_version(h,v)

描述:将mln_http_t类型的h中的HTTP版本号为vv的取值参考mln_http_get_version的返回值。

返回值:无

mln_http_get_type

mln_http_get_type(h)

描述:获取类型为mln_http_th中HTTP类型,即请求还是响应。

返回值:

  • M_HTTP_UNKNOWN未知类型
  • M_HTTP_REQUEST请求
  • M_HTTP_RESPONSE响应

mln_http_set_type

mln_http_set_type(h,t)

描述:将mln_http_t类型的h中报文类型设置为tt的取值参考mln_http_get_type的返回值。

返回值:无

mln_http_get_handler

mln_http_get_handler(h)

描述:获取类型为mln_http_th中体处理函数指针。

返回值:类型为mln_http_handler的函数指针

mln_http_set_handler

mln_http_set_handler(h,hlr)

描述:将mln_http_t类型的h中提处理函数设置为mln_http_handler类型的hlr

返回值:无

mln_http_get_response_msg

mln_http_get_response_msg(h)

描述:获取类型为mln_http_th中响应信息,即类似:Bad Request 或 Internal Server Error等字符串。

返回值:mln_string_t类型指针

mln_http_set_response_msg

mln_http_set_response_msg(h,m)

描述:将mln_http_t类型的h中响应信息设置为mln_string_t类型指针的m

返回值:无

mln_http_get_error

mln_http_get_error(h)#define M_HTTP_CONTINUE                        100
#define M_HTTP_SWITCHING_PROTOCOLS             101
#define M_HTTP_PROCESSING                      102
#define M_HTTP_OK                              200
#define M_HTTP_CREATED                         201
#define M_HTTP_ACCEPTED                        202
#define M_HTTP_NON_AUTHORITATIVE_INFORMATION   203
#define M_HTTP_NO_CONTENT                      204
#define M_HTTP_RESET_CONTENT                   205
#define M_HTTP_PARTIAL_CONTENT                 206
#define M_HTTP_MULTI_STATUS                    207
#define M_HTTP_MULTIPLE_CHOICES                300
#define M_HTTP_MOVED_PERMANENTLY               301
#define M_HTTP_MOVED_TEMPORARILY               302
#define M_HTTP_SEE_OTHER                       303
#define M_HTTP_NOT_MODIFIED                    304
#define M_HTTP_USE_PROXY                       305
#define M_HTTP_SWITCH_PROXY                    306
#define M_HTTP_TEMPORARY_REDIRECT              307
#define M_HTTP_BAD_REQUEST                     400
#define M_HTTP_UNAUTHORIZED                    401
#define M_HTTP_PAYMENT_REQUIRED                402
#define M_HTTP_FORBIDDEN                       403
#define M_HTTP_NOT_FOUND                       404
#define M_HTTP_METHOD_NOT_ALLOWED              405
#define M_HTTP_NOT_ACCEPTABLE                  406
#define M_HTTP_PROXY_AUTHENTICATION_REQUIRED   407
#define M_HTTP_REQUEST_TIMEOUT                 408
#define M_HTTP_CONFLICT                        409
#define M_HTTP_GONE                            410
#define M_HTTP_LENGTH_REQUIRED                 411
#define M_HTTP_PRECONDITION_FAILED             412
#define M_HTTP_REQUEST_ENTITY_TOO_LARGE        413
#define M_HTTP_REQUEST_URI_TOO_LARGE           414
#define M_HTTP_UNSUPPORTED_MEDIA_TYPE          415
#define M_HTTP_REQUESTED_RANGE_NOT_SATISFIABLE 416
#define M_HTTP_EXPECTATION_FAILED              417
#define M_HTTP_TOO_MANY_CONNECTIONS            421
#define M_HTTP_UNPROCESSABLE_ENTITY            422
#define M_HTTP_LOCKED                          423
#define M_HTTP_FAILED_DEPENDENCY               424
#define M_HTTP_UNORDERED_COLLECTION            425
#define M_HTTP_UPGRADE_REQUIRED                426
#define M_HTTP_RETRY_WITH                      449
#define M_HTTP_INTERNAL_SERVER_ERROR           500
#define M_HTTP_NOT_IMPLEMENTED                 501
#define M_HTTP_BAD_GATEWAY                     502
#define M_HTTP_SERVICE_UNAVAILABLE             503
#define M_HTTP_GATEWAY_TIMEOUT                 504
#define M_HTTP_VERSION_NOT_SUPPORTED           505
#define M_HTTP_VARIANT_ALSO_NEGOTIATES         506
#define M_HTTP_INSUFFICIENT_STORAGE            507
#define M_HTTP_BANDWIDTH_LIMIT_EXCEEDED        509
#define M_HTTP_NOT_EXTENDED                    510
#define M_HTTP_UNPARSEABLE_RESPONSE_HEADERS    600

描述:获取类型为mln_http_th中错误信息。

返回值:宏定义的错误值

mln_http_set_error

mln_http_set_error(h,e)

描述:将mln_http_t类型的h中错误信息设置为ee的取值参见mln_http_get_error中的宏定义。

返回值:无

mln_http_get_header

mln_http_get_header(h)

描述:获取类型为mln_http_th中头字段结构。

返回值:mln_hash_t类型结构

示例

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include "mln_core.h"
#include "mln_log.h"
#include "mln_http.h"
#include "mln_file.h"static void mln_accept(mln_event_t *ev, int fd, void *data);
static int mln_http_recv_body_handler(mln_http_t *http, mln_chain_t **in, mln_chain_t **nil);
static void mln_recv(mln_event_t *ev, int fd, void *data);
static void mln_quit(mln_event_t *ev, int fd, void *data);
static void mln_send(mln_event_t *ev, int fd, void *data);
static int mln_http_send_body_handler(mln_http_t *http, mln_chain_t **body_head, mln_chain_t **body_tail);static void worker_process(mln_event_t *ev)
{mln_u16_t port = 1234;mln_s8_t ip[] = "0.0.0.0";struct sockaddr_in addr;int val = 1;int listenfd = socket(AF_INET, SOCK_STREAM, 0);if (listenfd < 0) {mln_log(error, "listen socket error\n");return;}if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {mln_log(error, "setsockopt error\n");close(listenfd);return;}addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(ip);if (bind(listenfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {mln_log(error, "bind error\n");close(listenfd);return;}if (listen(listenfd, 511) < 0) {mln_log(error, "listen error\n");close(listenfd);return;}if (mln_event_set_fd(ev, \listenfd, \M_EV_RECV|M_EV_NONBLOCK, \M_EV_UNLIMITED, \NULL, \mln_accept) < 0){mln_log(error, "listen sock set event error\n");close(listenfd);return;}
}static void mln_accept(mln_event_t *ev, int fd, void *data)
{mln_tcp_conn_t *connection;mln_http_t *http;int connfd;socklen_t len;struct sockaddr_in addr;while (1) {memset(&addr, 0, sizeof(addr));len = sizeof(addr);connfd = accept(fd, (struct sockaddr *)&addr, &len);if (connfd < 0) {if (errno == EAGAIN) break;if (errno == EINTR) continue;perror("accept");exit(1);}connection = (mln_tcp_conn_t *)malloc(sizeof(mln_tcp_conn_t));if (connection == NULL) {fprintf(stderr, "3No memory.\n");close(connfd);continue;}if (mln_tcp_conn_init(connection, connfd) < 0) {fprintf(stderr, "4No memory.\n");close(connfd);free(connection);continue;}http = mln_http_init(connection, NULL, mln_http_recv_body_handler);if (http == NULL) {fprintf(stderr, "5No memory.\n");mln_tcp_conn_destroy(connection);free(connection);close(connfd);continue;}if (mln_event_set_fd(ev, \connfd, \M_EV_RECV|M_EV_NONBLOCK, \M_EV_UNLIMITED, \http, \mln_recv) < 0){fprintf(stderr, "6No memory.\n");mln_http_destroy(http);mln_tcp_conn_destroy(connection);free(connection);close(connfd);continue;}}
}static void mln_quit(mln_event_t *ev, int fd, void *data)
{mln_http_t *http = (mln_http_t *)data;mln_tcp_conn_t *connection = mln_http_get_connection(http);mln_event_set_fd(ev, fd, M_EV_CLR, M_EV_UNLIMITED, NULL, NULL);mln_http_destroy(http);mln_tcp_conn_destroy(connection);free(connection);close(fd);
}static void mln_recv(mln_event_t *ev, int fd, void *data)
{mln_http_t *http = (mln_http_t *)data;mln_tcp_conn_t *connection = mln_http_get_connection(http);int ret, rc;mln_chain_t *c;while (1) {ret = mln_tcp_conn_recv(connection, M_C_TYPE_MEMORY);if (ret == M_C_FINISH) {continue;} else if (ret == M_C_NOTYET) {c = mln_tcp_conn_remove(connection, M_C_RECV);if (c != NULL) {rc = mln_http_parse(http, &c);if (c != NULL) {mln_tcp_conn_append_chain(connection, c, NULL, M_C_RECV);}if (rc == M_HTTP_RET_OK) {return;} else if (rc == M_HTTP_RET_DONE) {mln_send(ev, fd, data);} else {fprintf(stderr, "Http parse error. error_code:%u\n", mln_http_get_error(http));mln_quit(ev, fd, data);return;}}break;} else if (ret == M_C_CLOSED) {c = mln_tcp_conn_remove(connection, M_C_RECV);if (c != NULL) {rc = mln_http_parse(http, &c);if (c != NULL) {mln_tcp_conn_append_chain(connection, c, NULL, M_C_RECV);}if (rc == M_HTTP_RET_ERROR) {fprintf(stderr, "Http parse error. error_code:%u\n", mln_http_get_error(http));}}mln_quit(ev, fd, data);return;} else if (ret == M_C_ERROR) {mln_quit(ev, fd, data);return;}}
}static int mln_http_recv_body_handler(mln_http_t *http, mln_chain_t **in, mln_chain_t **nil)
{mln_u32_t method = mln_http_get_method(http);if (method == M_HTTP_GET)return M_HTTP_RET_DONE;mln_http_set_error(http, M_HTTP_NOT_IMPLEMENTED);return M_HTTP_RET_ERROR;
}static void mln_send(mln_event_t *ev, int fd, void *data)
{mln_http_t *http = (mln_http_t *)data;mln_tcp_conn_t *connection = mln_http_get_connection(http);mln_chain_t *c = mln_tcp_conn_get_head(connection, M_C_SEND);int ret;if (c == NULL) {mln_http_reset(http);mln_http_set_status(http, M_HTTP_OK);mln_http_set_version(http, M_HTTP_VERSION_1_0);mln_http_set_type(http, M_HTTP_RESPONSE);mln_http_set_handler(http, mln_http_send_body_handler);mln_chain_t *body_head = NULL, *body_tail = NULL;if (mln_http_generate(http, &body_head, &body_tail) == M_HTTP_RET_ERROR) {fprintf(stderr, "mln_http_generate() failed. %u\n", mln_http_get_error(http));mln_quit(ev, fd, data);return;}mln_tcp_conn_append_chain(connection, body_head, body_tail, M_C_SEND);}while ((c = mln_tcp_conn_get_head(connection, M_C_SEND)) != NULL) {ret = mln_tcp_conn_send(connection);if (ret == M_C_FINISH) {mln_quit(ev, fd, data);break;} else if (ret == M_C_NOTYET) {mln_chain_pool_release_all(mln_tcp_conn_remove(connection, M_C_SENT));mln_event_set_fd(ev, fd, M_EV_SEND|M_EV_APPEND|M_EV_NONBLOCK, M_EV_UNLIMITED, data, mln_send);return;} else if (ret == M_C_ERROR) {mln_quit(ev, fd, data);return;} else {fprintf(stderr, "Shouldn't be here.\n");abort();}}
}static int mln_http_send_body_handler(mln_http_t *http, mln_chain_t **body_head, mln_chain_t **body_tail)
{mln_u8ptr_t buf;mln_alloc_t *pool = mln_http_get_pool(http);mln_string_t cttype_key = mln_string("Content-Type");mln_string_t cttype_val = mln_string("text/html");buf = mln_alloc_m(pool, 5);if (buf == NULL) {mln_http_set_error(http, M_HTTP_INTERNAL_SERVER_ERROR);return M_HTTP_RET_ERROR;}memcpy(buf, "hello", 5);if (mln_http_set_field(http, &cttype_key, &cttype_val) == M_HTTP_RET_ERROR) {mln_http_set_error(http, M_HTTP_INTERNAL_SERVER_ERROR);return M_HTTP_RET_ERROR;}mln_string_t ctlen_key = mln_string("Content-Length");mln_string_t ctlen_val = mln_string("5");if (mln_http_set_field(http, &ctlen_key, &ctlen_val) == M_HTTP_RET_ERROR) {mln_http_set_error(http, M_HTTP_INTERNAL_SERVER_ERROR);return M_HTTP_RET_ERROR;}mln_chain_t *c = mln_chain_new(pool);if (c == NULL) {mln_http_set_error(http, M_HTTP_INTERNAL_SERVER_ERROR);return M_HTTP_RET_ERROR;}mln_buf_t *b = mln_buf_new(pool);if (b == NULL) {mln_chain_pool_release(c);mln_http_set_error(http, M_HTTP_INTERNAL_SERVER_ERROR);return M_HTTP_RET_ERROR;}c->buf = b;b->left_pos = b->pos = b->start = buf;b->last = b->end = buf + 5;b->in_memory = 1;b->last_buf = 1;b->last_in_chain = 1;if (*body_head == NULL) {*body_head = *body_tail = c;} else {(*body_tail)->next = c;*body_tail = c;}return M_HTTP_RET_DONE;
}int main(int argc, char *argv[])
{struct mln_core_attr cattr;cattr.argc = argc;cattr.argv = argv;cattr.global_init = NULL;cattr.worker_process = worker_process;return mln_core_init(&cattr);
}

矩阵运算

头文件

#include "mln_matrix.h"

相关结构

typedef struct {                 mln_size_t  row;//矩阵的行数mln_size_t  col;//矩阵的列数double     *data;//一个一维数组,包含了矩阵内所有元素,按行一次排列mln_u32_t   is_ref:1;//标识data是否为外部引用,该标记用于释放矩阵结构时忽略对data的释放
} mln_matrix_t;

函数

mln_matrix_new

mln_matrix_t *mln_matrix_new(mln_size_t row, mln_size_t col, double *data, mln_u32_t is_ref);

描述:创建一个rowcol列,数据为data的矩阵。若is_ref0则表示矩阵结构会完全复制一个data在其中,否则直接引用data

返回值:成功则返回矩阵结构指针,否则返回NULL

mln_matrix_free

void mln_matrix_free(mln_matrix_t *matrix);

描述:释放矩阵结构内存。

返回值:无

mln_matrix_mul

mln_matrix_t *mln_matrix_mul(mln_matrix_t *m1, mln_matrix_t *m2);

描述:矩阵乘法。

返回值:成功则返回结果矩阵指针,否则返回NULL

mln_matrix_inverse

mln_matrix_t *mln_matrix_inverse(mln_matrix_t *matrix);

描述:矩阵求逆。注意:矩阵求逆要求是该矩阵为方阵。

返回值:成功则返回结果矩阵指针,否则返回NULL

mln_matrix_dump

void mln_matrix_dump(mln_matrix_t *matrix);

描述:将矩阵的信息输出到标准输出中。仅用于调试。

返回值:无

示例

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include "mln_core.h"
#include "mln_log.h"
#include "mln_matrix.h"int main(int argc, char *argv[])
{mln_matrix_t *a, *b;double data[] = {1, 1, 1, 1, 2, 4, 2, 8, 64};struct mln_core_attr cattr;cattr.argc = argc;cattr.argv = argv;cattr.global_init = NULL;cattr.worker_process = NULL;if (mln_core_init(&cattr) < 0) {fprintf(stderr, "init failed\n");return -1;}a = mln_matrix_new(3, 3, data, 1);if (a == NULL) {mln_log(error, "init matrix failed\n");return -1;}mln_matrix_dump(a);b = mln_matrix_inverse(a);mln_matrix_free(a);if (b == NULL) {mln_log(error, "inverse failed: %s\n", strerror(errno));return -1;}mln_matrix_dump(b);mln_matrix_free(b);return 0;
}

大数计算

头文件

#include "mln_bignum.h"

相关结构

typedef struct {mln_u32_t tag;//正负数标记 mln_u32_t length;//当前大数数值所使用到的data的元素个数mln_u64_t data[M_BIGNUM_SIZE];//大数数值
} mln_bignum_t;#define M_BIGNUM_POSITIVE 0 //正数
#define M_BIGNUM_NEGATIVE 1 //负数#define M_BIGNUM_SIZE     257

Melon中大数的实现是定长的,即大数是有上限的,目前支持到最大2048位。

函数/宏

mln_bignum_init

mln_bignum_t *mln_bignum_init(void);

描述:创建并初始化大数结构mln_bignum_t,该结构由malloc分配而来。

返回值:成功则返回大数结构指针,否则返回NULL

mln_bignum_pool_init

mln_bignum_t *mln_bignum_pool_init(mln_alloc_t *pool);

描述:创建并初始化大数结构mln_bignum_t,该结构由pool指定的内存池分配而来。

返回值:成功则返回大数结构指针,否则返回NULL

mln_bignum_free

void mln_bignum_free(mln_bignum_t *bn);

描述:释放大数结构bn,该结构应由mln_bignum_init分配而来。

返回值:无

mln_bignum_pool_free

void mln_bignum_pool_free(mln_bignum_t *bn);

描述:释放大数结构bn,该结构应由mln_bignum_pool_init分配而来。

返回值:无

mln_bignum_dup

mln_bignum_t *mln_bignum_dup(mln_bignum_t *bn);

描述:完全复制一份大数结构bn,复制品由malloc分配内存。

返回值:成功则返回大数结构指针,否则返回NULL

mln_bignum_pool_dup

mln_bignum_t *mln_bignum_pool_dup(mln_alloc_t *pool, mln_bignum_t *bn);

描述:完全复制一份大数结构bn,复制品由pool指定的内存池上分配内存。

返回值:成功则返回大数结构指针,否则返回NULL

mln_bignum_assign

int mln_bignum_assign(mln_bignum_t *bn, mln_s8ptr_t sval, mln_u32_t len);

描述:将svallen表示的字符串形式的大数赋值给大数结构bn

返回值:成功则返回0,否则返回-1

mln_bignum_add

void mln_bignum_add(mln_bignum_t *dest, mln_bignum_t *src);

描述:大数加法,计算结果会放入dest中。

返回值:无

mln_bignum_sub

void mln_bignum_sub(mln_bignum_t *dest, mln_bignum_t *src);

描述:大数减法,计算结果会放入dest中。

返回值:无

mln_bignum_mul

void mln_bignum_mul(mln_bignum_t *dest, mln_bignum_t *src);

描述:大数乘法,计算结果会放入dest中。

返回值:无

mln_bignum_div

int mln_bignum_div(mln_bignum_t *dest, mln_bignum_t *src, mln_bignum_t *quotient);

描述:大数除法,商会放入dest中,若quotient不为空,则余数将放入其中。

返回值:成功则返回0,否则返回-1

mln_bignum_pwr

int mln_bignum_pwr(mln_bignum_t *dest, mln_bignum_t *exponent, mln_bignum_t *mod);

描述:大数幂运算,计算destexponent次方。若mod不为空,则结果会对mod取模。最终计算结果会放入dest中。

返回值:成功则返回0,否则返回-1

mln_bignum_compare

int mln_bignum_compare(mln_bignum_t *bn1, mln_bignum_t *bn2);

描述:带符号的比较大数大小值。

返回值:

  • 1 - bn1 > bn2
  • -1 -bn1 < bn2
  • 0 - bn1 = bn2

mln_bignum_abs_compare

int mln_bignum_abs_compare(mln_bignum_t *bn1, mln_bignum_t *bn2);

描述:绝对值比大小。

返回值:

  • 1 - bn1 > bn2
  • -1 -bn1 < bn2
  • 0 - bn1 = bn2

mln_bignum_bit_test

int mln_bignum_bit_test(mln_bignum_t *bn, mln_u32_t index);

描述:检测index指定的大数bn中该比特是否为1。

返回值:为1则返回1,否则返回0

mln_bignum_left_shift

void mln_bignum_left_shift(mln_bignum_t *bn, mln_u32_t n);

描述:将大数bn左移n位。

返回值:无

mln_bignum_right_shift

void mln_bignum_right_shift(mln_bignum_t *bn, mln_u32_t n);

描述:将大数bn右移n位。

返回值:无

mln_bignum_prime

int mln_bignum_prime(mln_bignum_t *res, mln_u32_t bitwidth);

描述:计算一个bitwidth位的大素数,并将结果写入res中。

返回值:成功则返回0,否则返回-1

mln_bignum_extend_eulid

int mln_bignum_extend_eulid(mln_bignum_t *a, mln_bignum_t *b, mln_bignum_t *x, mln_bignum_t *y);

描述:大数版本的扩展欧几里得算法。

返回值:成功则返回0,否则返回-1

mln_bignum_i2osp

int mln_bignum_i2osp(mln_bignum_t *n, mln_u8ptr_t buf, mln_size_t len);

描述:将n的二进制值写入buflen指代的内存中。

返回值:成功则返回0,否则返回-1

mln_bignum_os2ip

int mln_bignum_os2ip(mln_bignum_t *n, mln_u8ptr_t buf, mln_size_t len);

描述:将buflen指代的内存中的二进制值赋予n。与mln_bignum_i2osp是相反操作。

返回值:成功则返回0,否则返回-1

mln_bignum_i2s

int mln_bignum_i2s(mln_bignum_t *n, mln_u8ptr_t buf, mln_size_t len);

描述:与mln_bignum_i2osp功能相同,推荐使用mln_bignum_i2osp。

返回值:成功则返回0,否则返回-1

mln_bignum_s2i

int mln_bignum_s2i(mln_bignum_t *n, mln_u8ptr_t buf, mln_size_t len);

描述:与mln_bignum_os2ip功能相同,推荐使用mln_bignum_os2ip

返回值:成功则返回0,否则返回-1

mln_bignum_positive

mln_bignum_positive(pbn)

描述:将mln_bignum_t *的大数pbn设置为正数。

返回值:无

mln_bignum_negative

mln_bignum_negative(pbn)

描述:将mln_bignum_t *的大数pbn设置为负数。

返回值:无

mln_bignum_is_positive

mln_bignum_is_positive(pbn)

描述:判断mln_bignum_t *的大数pbn是否为为正数。

返回值:为正则返回非0,否则返回0

mln_bignum_is_negative

mln_bignum_is_negative(pbn)

描述:判断mln_bignum_t *的大数pbn是否为为负数。

返回值:为负则返回非0,否则返回0

mln_bignum_get_length

mln_bignum_get_length(pbn)

描述:获取mln_bignum_t *的大数pbn的值占data多少个数组元素。

返回值:元素个数

mln_bignum_zero

mln_bignum_zero()

描述:返回一个值为0的大数。

返回值:大数值为0的常量

示例

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include "mln_core.h"
#include "mln_log.h"
#include "mln_bignum.h"int main(int argc, char *argv[])
{mln_bignum_t *n1 = NULL, *n2 = NULL;struct mln_core_attr cattr;cattr.argc = argc;cattr.argv = argv;cattr.global_init = NULL;cattr.worker_process = NULL;if (mln_core_init(&cattr) < 0) {fprintf(stderr, "init failed\n");return -1;}n1 = mln_bignum_init();n2 = mln_bignum_init();if (n1 == NULL || n2 == NULL) {mln_log(error, "init bignum failed\n");goto err;}if (mln_bignum_assign(n1, "10", 2) < 0) {mln_log(error, "assign failed\n");goto err;}if (mln_bignum_assign(n2, "20", 2) < 0) {mln_log(error, "assign failed\n");goto err;}mln_bignum_add(n1, n2);mln_bignum_dump(n1);err:if (n1 != NULL) mln_bignum_free(n1);if (n2 != NULL) mln_bignum_free(n2);return 0;
}

多线程框架

注意:Windows下目前不支持本功能。

多线程框架与前面介绍的线程池不同,是一种模块化线程。模块化线程是指,每一个线程都是一个独立的代码模块,都有各自对应的入口函数(类似于每一个 C 语言程序有一个 main 函数一样)。

模块要存放于 Melon/threads/目录下。在现有的Melon代码中,包含了两个示例模块——haha 和 hello (名字起得有点随意)。下面,我们以这两个模块为例说明模块化线程的开发和使用流程。

开发流程

这里有几点注意事项:

  1. 模块的名字:模块的名字将被用于两个地方,一个是配置文件中,一个是模块入口函数名。前者将在使用流程中说明,后者我们马上将以 haha 为例进行说明。
  2. 模块的参数:参数是在配置文件中给出的,这一点我们在使用流程中将会说明。但是需要注意一点,最后一个参数并不是配置文件中给出的,而是框架自动追加的,是主线程与该线程模块通信的 socketpair 套接字。
//haha 模块int haha_main(int argc, char **argv)
{int fd = atoi(argv[argc-1]);mln_thread_msg_t msg;int nfds;fd_set rdset;for (;;) {FD_ZERO(&rdset);FD_SET(fd, &rdset);nfds = select(fd+1, &rdset, NULL, NULL, NULL);if (nfds < 0) {if (errno == EINTR) continue;mln_log(error, "select error. %s\n", strerror(errno));return -1;}memset(&msg, 0, sizeof(msg));
#if defined(WINNT)int n = recv(fd, (char *)&msg, sizeof(msg), 0);
#elseint n = recv(fd, &msg, sizeof(msg), 0);
#endifif (n != sizeof(msg)) {mln_log(debug, "recv error. n=%d. %s\n", n, strerror(errno));return -1;}mln_log(debug, "!!!src:%S auto:%l char:%c\n", msg.src, msg.sauto, msg.c);mln_thread_clearMsg(&msg);}return 0;
}

可以看到,在这个例子中,模块的入口函数名为haha_main。对于每一个线程模块来说,他们的入口函数就是他们模块的名称(即文件名)+下划线+main组成的。

这个例子也很简单,就是利用 select 持续关注主线程消息,当从主线程接收到消息后,就进行日志输出,然后释放资源。

与之功能对应的就是 hello 这个模块:

//hello 模块
#include <assert.h>static void hello_cleanup(void *data)
{mln_log(debug, "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
}int hello_main(int argc, char **argv)
{mln_thread_cleanup_set(hello_cleanup, NULL);int i;for (i = 0; i < 1; ++i)  {int fd = atoi(argv[argc-1]);mln_thread_msg_t msg;memset(&msg, 0, sizeof(msg));msg.dest = mln_string_new("haha");assert(msg.dest);msg.sauto = 9736;msg.c = 'N';msg.type = ITC_REQUEST;msg.need_clear = 1;
#if defined(WINNT)int n = send(fd, (char *)&msg, sizeof(msg), 0);
#elseint n = send(fd, &msg, sizeof(msg), 0);
#endifif (n != sizeof(msg)) {mln_log(debug, "send error. n=%d. %s\n", n, strerror(errno));mln_string_free(msg.dest);return -1;}}usleep(100000);return 0;
}

这个模块的功能也很简单,就是向主线程发送消息,而消息的接收方是 haha 模块,即主线程是一个中转站,它将 hello 模块的消息转发给 haha 模块。

在 hello 这个模块中,调用了mln_thread_cleanup_set函数,这个函数的作用是:在从当前线程模块的入口函数返回至上层函数后,将会被调用,用于清理自定义资源。

每一个线程模块的清理函数只能被设置一个,多次设置会被覆盖,清理函数是线程独立的,因此不会出现覆盖其他线程处理函数的情况(当然,你也可以故意这样来构造,比如传一个处理函数指针给别的模块,然后那个模块再进行设置)。

使用流程

使用流程遵循如下步骤:

  1. 编写框架启动器
  2. 编译链接生成可执行程序
  3. 修改配置文件
  4. 启动程序

我们逐个步骤进行操作。

我们先编写启动器:

//launcher.c#include "mln_core.h"int main(int argc, char *argv[])
{struct mln_core_attr cattr;cattr.argc = argc;cattr.argv = argv;cattr.global_init = NULL;cattr.worker_process = NULL;return mln_core_init(&cattr);
}

这里,我们不初始化任何全局变量,也不需要工作进程,因此都置空即可。

$ cc -o launcher launcher.c -I /usr/local/melon/include/ -L /usr/local/melon/lib/ -lmelon -lpthread

生成名为 launcher 的可执行程序。

此时,我们的线程尚不能执行,我们需要修改配置文件:

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";
}

上面是默认配置文件,我们要进行如下修改:

  • thread_mode off; -> thread_mode on;
  • framework off; -> framework on;
  • thread_exec配置块中的两项注释去掉

这里,需要额外说明一下:

thread_exec配置块专门用于模块化线程之用,其内部每一个配置项均为线程模块。

以 hello 为例:

restart "hello" "hello" "world";

restart或者default是指令,restart表示线程退出主函数后,再次启动线程。而default则表示一旦退出便不再启动。其后的hello字符串就是模块的名称,其余则为模块参数,即入口函数的argcargv的部分。而与主线程通信的套接字则不必写在此处,而是线程启动后进入入口函数前自动添加的。

现在,就来启动程序吧。

$ ./launcherStart up worker process No.1
Start thread 'hello'
Start thread 'haha'
02/14/2021 04:07:48 GMT DEBUG: ./src/mln_thread_module.c:haha_main:42: PID:9309 !!!src:hello auto:9736 char:N
02/14/2021 04:07:49 GMT DEBUG: ./src/mln_thread_module.c:hello_cleanup:53: PID:9309 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
02/14/2021 04:07:49 GMT REPORT: PID:9309 Thread 'hello' return 0.
02/14/2021 04:07:49 GMT REPORT: PID:9309 Child thread 'hello' exit.
02/14/2021 04:07:49 GMT REPORT: PID:9309 child thread pthread_join's exit code: 0
02/14/2021 04:07:49 GMT DEBUG: ./src/mln_thread_module.c:haha_main:42: PID:9309 !!!src:hello auto:9736 char:N
02/14/2021 04:07:49 GMT DEBUG: ./src/mln_thread_module.c:hello_cleanup:53: PID:9309 @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
02/14/2021 04:07:49 GMT REPORT: PID:9309 Thread 'hello' return 0.
02/14/2021 04:07:49 GMT REPORT: PID:9309 Child thread 'hello' exit.
02/14/2021 04:07:49 GMT REPORT: PID:9309 child thread pthread_join's exit code: 0
...

可以看到,事实上 Melon 中会启动工作进程来拉起其子线程,而工作进程数量由worker_proc配置项控制,如果多于一个,则每个工作进程都会拉起一组haha和hello线程。此外,我们也看到,hello线程退出后,清理函数被调用。

IPC模块开发

注意:Windows下暂时不支持本功能。

Melon支持多进程框架,那么自然主子进程之间会涉及通信问题,这部分就由IPC进行处理。

Melon提供了一种简单的IPC扩展方式,它不需要对现有源文件进行修改即可完成(但需要重新编译)。为此,在Melon目录中有一个专门的目录提供给这个扩展之用——ipc_handlers

在这个目录下的文件,每一个即为一个消息以及消息的处理函数。文件的命名是有规范的:处理函数前缀.消息类型名。而处理函数又分为:主进程处理函数 和 子进程处理函数,他们的名字分别为:处理函数前缀_master处理函数前缀_worker。消息类型会在configure时自动加入到mln_ipc.h头文件中,用户自己的代码直接使用即可。

处理函数的函数原型如下:

void prefix_master(mln_event_t *ev, void *f_ptr, void *buf, mln_u32_t len, void **udata_ptr);

void prefix_worker(mln_event_t *ev, void *f_ptr, void *buf, mln_u32_t len, void **udata_ptr);

第一个参数为与本消息相关的事件结构。第二个参数是一个mln_fork_t类型的指针,我们可以通过这里个指针获取一些和子进程相关的信息,例如通信的链接结构。第三个参数为主/子进程本次收到的消息内容。第四个参数为消息的长度。第五个参数是一个可自定义的用户数据。我们可以在本次调用中对其进行赋值,而在下次调用时它会被传递回来继续使用,框架不会对其进行初始化和释放操作。

关于mln_fork_t类型:

struct mln_fork_s {struct mln_fork_s       *prev;struct mln_fork_s       *next;mln_s8ptr_t             *args; //子进程参数mln_tcp_conn_t           conn; //通信的链接结构,socketpair被当作tcp使用pid_t                    pid; //子进程的pidmln_u32_t                n_args;//参数个数mln_u32_t                state;//子进程状态mln_u32_t                msg_len;//消息长度mln_u32_t                msg_type;//消息类型mln_size_t               error_bytes;//错误消息类型数据大小void                    *msg_content;//消息内容enum proc_exec_type      etype;//子进程是需要被替换执行映像的(exec)还是不需要的enum proc_state_type     stype;//子进程退出后是否需要被重新拉起
};

由于Melon是一主多从模式,因此需要一个方法能够遍历子进程列表,并对其下发消息,因此提供了一个函数用于遍历所有子进程结构:

int mln_fork_scan_all(mln_event_t *ev, scan_handler handler, void *data);typedef int (*scan_handler)(mln_event_t *, mln_fork_t *, void *);
  • ev为主进程消息相关的事件处理结构。
  • handler为每个子进程节点的处理函数,函数有三个参数,分别为:消息相关的事件处理结构、子进程的mln_fork_t结构以及用户自定义数据(即mln_fork_scan_all的第三个参数)。
  • data用户自定义数据。

Melon中还提供了其他函数:

  • mln_fork_get_master_connection

    mln_tcp_conn_t *mln_fork_get_master_connection(void);
    

    用于在子进程中获取与主进程通信的TCP链接结构(socketpair被当作TCP处理)。

  • mln_ipc_master_send_prepare

    int mln_ipc_master_send_prepare(mln_event_t *ev, mln_u32_t type, void *buf, mln_size_t len, mln_fork_t *f_child);
    

    用于主进程将长度为len类行为type的消息buf发送给f_child指定的子进程。

  • mln_ipc_worker_send_prepare

    int mln_ipc_worker_send_prepare(mln_event_t *ev, mln_u32_t type, void *msg, mln_size_t len);
    

    用于子进程将长度为len类行为type的消息buf发送给主进程。

目前,在ipc_handlers中有一个已经定义了的消息,用于配置热重载的,极其简单,用户可以在阅读完本文后与之对照。

多进程模型

注意:Windows下目前不支持本功能。

Melon开发之初便是要支持多进程模型的,这一点也主要源于Nginx以及以往用户态网络程序开发经历。因此,Melon中的多进程也延续了类似Nginx的异步事件模式。

下面我们来使用Melon来完成一个多进程例子。这个例子虽然简单,但用户会发现,事实上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 functionint 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 timermln_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.conflog_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";
}

我们做如下修改:

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

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

最后,程序启动后如下:

$ ./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 world02/08/2021 09:34:46 GMT DEBUG: hello.c:print_handler:39: PID:25323 hello world02/08/2021 09:34:46 GMT DEBUG: hello.c:print_handler:39: PID:25324 hello world02/08/2021 09:34:47 GMT DEBUG: hello.c:print_handler:39: PID:25322 hello world02/08/2021 09:34:47 GMT DEBUG: hello.c:print_handler:39: PID:25323 hello world02/08/2021 09:34:47 GMT DEBUG: hello.c:print_handler:39: PID:25324 hello world...

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

到此,我们的例子已经看完。我们可以看到,在全局的初始化函数中,我们可以自由的对全局变量进行处理,甚至可以在其中改写当前框架的配置以强制框架按照我们的期望进行初始化。而在工作进程处理函数中,我们可以自由的编写我们的程序逻辑,而不会担心会有其他繁杂的逻辑会对此产生干扰。

【C语言开源库】C语言必备实用第三方库Melon(包括数据结构算法)相关推荐

  1. 20个必不可少的Python库也是基本的第三方库(转载)

    20个必不可少的Python库也是基本的第三方库 读者您好.今天我将介绍20个属于我常用工具的Python库,我相信你看完之后也会觉得离不开它们.他们是: Requests.Kenneth Reitz ...

  2. python怎么导入第三方库完整教程_Python 安装第三方库教程

    Python是一门优雅的语言,简洁的语法,强大的功能.包含丰富的第三方库,那么问题来了,如何安装这些第三方库(包)呢? 我的电脑:Windows 764位 Python IDE软件:PyCharm 2 ...

  3. python升级第三方库_python一键升级所有第三方库

    import pip from subprocess import call for dist in pip.get_installed_distributions(): call("pip ...

  4. python语言的第三方库_常用的Python第三方库

    今天公司停电,没上班.跑上来更新个博客,跟大家分享一下我常用的几个第三方 Python 库.Python 语言之所以能够如此流行,除了本身内置许多程序库来保障快速开发之外,目不睱接的第三方库也是一大主 ...

  5. golang优秀的第三方库mysql_golang比较优秀的第三方库收集-Go语言中文社区

    golang有许多优秀的第三方库,有时要使用时忘记库名又要去github找半天,还是写下来做个笔记并且逐步添加.以下的库基本都实际使用过. log star最多的log项目,我现在基本都使用这个项目, ...

  6. python语言中、用来安装第三方库的命令_python安装第三方库的方法

    python安装第三方库的方法 发布时间:2020-09-03 11:22:15 来源:亿速云 阅读:76 作者:小新 小编给大家分享一下python安装第三方库的方法,相信大部分人都还不怎么了解,因 ...

  7. python库_计算机二级教程 Python语言程序设计,第10章python第三方库概览

    第十章学习知识导图 考点主要是: 1) 第三方库的获取和安装 2) 脚本程序转变为可执行程序的第三方库 : PyInstaller库(必选) 3) 第三方库: jieba库(必选).wordcloud ...

  8. python必备第三方库-20个必不可少的Python库也是基本的第三方库

    传智播客博学谷 微信号:boxuegu- get最新最全的IT技能 免费领取各种视频资料 今天将介绍20个常用工具的Python库,我相信你看完之后也会觉得离不开它们.他们是: Requests.Ke ...

  9. 【数据结构与算法】多种语言(VB、C、C#、JavaScript)系列数据结构算法经典案例教程合集目录

    文章目录 1. 专栏简介 2. 专栏地址 3. 专栏目录 1. 专栏简介 2. 专栏地址 「 刘一哥与GIS的故事 」之<数据结构与算法> 3. 专栏目录 [经典回放]多种语言系列数据结构 ...

  10. 20个必不可少的Python库也是基本的第三方库

    引用: https://www.cnblogs.com/jiangchunsheng/p/9275881.html 读者您好.今天我将介绍20个属于我常用工具的Python库,我相信你看完之后也会觉得 ...

最新文章

  1. 软件疑难杂症解决办法汇总
  2. Matlab生成Xilinx Rom IP CORE的初始化内容coe文件
  3. 免费下载 | 超全算法题精解,一本能“在线”编程的面试宝典
  4. signature=8cc1e8491a741a9dc954b549013b75e5,基于小波的SAR影像纹理分析
  5. 无法加载安装安装程序:Wbemupgd.dll
  6. 闲鱼账号被封怎么办?解封看这里!
  7. c语言实现 windows socket_C语言实现Socket简单通信
  8. nft文件传输_Nancy之文件上传与下载
  9. 推荐系统-Task04前后端基础及交互
  10. 路由重发布Route Redistribution
  11. 基于荔枝派Lichee Nano(全志f1c100s)的u-boot移植(一)
  12. 有电流平衡能力的6组白光LED驱动器MAX8790
  13. 360极速浏览器的兼容模式
  14. 43.自动获取汉子笔画
  15. python_pdf常规使用
  16. 【荐】女性新手必看的停车入位技巧图解
  17. 【黑苹果装机实践】从硬件选择到系统安装
  18. C++实现演讲比赛流程管理系统
  19. JS对浏览器地址的操作
  20. T100 genero report (GR) 凭证报表开发流程

热门文章

  1. Docker网络及资源管理
  2. 4k纸是几厘米乘几厘米_4k纸有多大?长宽各几厘米?
  3. unity游戏开发之谷歌上架准备
  4. 峰值帧+时序信息 跨数据集微表情识别
  5. 苹果手机邮箱imap服务器没有响应,Apple手机的内置电子邮件地址突然显示imap.gmail.com没有响应,无法发送电子邮件...
  6. 老年人-傻妞机器人安装及使用教程
  7. Json Schema 是什么?
  8. android京东源码下载,京东商城APP - 源码下载|通讯/手机编程|android开发|源代码 - 源码中国...
  9. 企业大数据价值你只发掘了1%!
  10. FOFA常用搜索语法