Agent 通用库


MiniAgent

这里实际上是一个通用的示例模版,可以通过如下方式进行编译测试。

$ cmake .. -DCMAKE_BUILD_TYPE=Debug -DWITH_EXAMPLES=true -DWITH_UNIT_TESTS=true

常用脚本

简单列举常用的脚本,一般保存在 contrib 目录下。

generate_version.sh

依次从 VERSION 文件、Git-Tag 中读取,其命名格式为 v1.2.3 ,默认是 v0.1.0

package.sh

将源码打包成 tar.bz2 包,同时将源码编译并打包成 RPM 安装包。

./contrib/package.sh MiniAgent 1.2.1-1

如上的示例中,其中 1.2.1 为版本号,而 1 表示编译号,

该脚本同时会调用 generate_version.sh 脚本判断版本号是否匹配,不匹配则直接报错,可以通过 -f 跳过。

测试用例

其中单元测试使用的是 CMake 提供的机制,另外,针对。

常用库

这里的通用库采用 git 的 submodule 方式添加到各个项目中,大致的使用方式如下。

----- 以submodule方式添加库
$ git submodule add git@gogs.cargo.com:cargo/clib-liblog.git libs/liblog

----- 对于所有的submodule拉取最新的代码
$ git submodule foreach --recursive git pull

liblog

一个非常简单的日志库,支持简单的日志打印、级别设置、日志切割等功能,同时针对线程、进程编程进行了简单的优化。

为了尽量简单,并没有支持灵活的日志格式,但是如果需要调整为自己喜欢的格式,可以直接修改 common.c 文件中与日志格式化相关程序,其中 GCC 中可以使用的宏包括了 __FILE__ __FUNCTION__ __LINE__ 等。

注意,在 CMake 中使用 __FILE__ 时默认是文件的全路经,如果要使用相对路径可以参考 CMake 自动编译 中相关介绍。

配置

大部分的配置都是以宏的方式提供,用户可以编译的时候指定。

----- 默认使用MAX缓存,可以当内存不足时自动扩容
#define LOG_USE_AUTOBUFF        1

----- 是否缩减缓存
#define LOG_SHRINK_TIMES        10

----- 日志文件的最大,以及保留日志文件数
#define LOG_FILE_SIZE_MAX       1024 * 1024 * 50
#define LOG_FILE_NUMS_MAX       4

----- 是否使用文件名+行号打印日志
#define LOG_USE_FILENO          0

----- 缓存的最大最小值
#define LOG_BUFFER_MIN          1024
#define LOG_BUFFER_MAX          16 * 1024

API

----- 日志打印,可以将log开头替换为logh
log_fatal(...)
log_error(...)
log_warning(...)
log_notice(...)
log_info(...)
log_debug(...)
log_trace(...)

int log_level_inc(void); /* less logs */
int log_level_dec(void); /* more logs */
int log_set_level(int level);
int log_get_level(const char *level);
const char *log_get_name(const int level);

日志模型

针对不同的场景对应了不同的实现,主要分为如下几类。

liblog-process.a        单进程,在init会打开文件,切割时无需加锁
liblog-multi-process.a  多进程,每次写入时打开文件,原子写入,然后关闭,切割时无加文件锁
liblog-thread.a         多线程,在init时打开文件,每次写入时打开文件,原子写入,然后关闭,切割时需要加线程锁
liblog-stdout.a         直接写入到标准输出,注意不区分标准输出还是标准错误输出

注意,上述的方式实际上是有优先级,也就是 FEATURE_LOG_MACRO FEATURE_LOG_STDOUT FEATURE_LOG_PROCESS FEATURE_LOG_MULTI_PROCESS FEATURE_LOG_THREAD

一般来说,一个程序中会有主进程,为了保证所有库使用相同的日志输出格式,那么可以设置主进程,以及单个进程。

ADD_DEFINITIONS(-DFEATURE_LOG_PROCESS)  # 所有,包括库,优先级低

TARGET_COMPILE_OPTIONS(${PROJECT_NAME}Ctl PRIVATE "-DFEATURE_LOG_STDOUT")  # 单个,优先级高

TODO

  • 日志压缩。

libcron

类似于系统中的 crontab 的周期定义方式。

 .------------------ second       (0~59) , - * /
 |  .--------------- minute       (0~59) , - * /
 |  |  .------------ hour         (0~23) , - * /
 |  |  |  .--------- day of month (1~31) , - * / ?
 |  |  |  |  .------ month        (1~12) , - * / jan,feb,mar,apr ...
 |  |  |  |  |  .--- day of week  (0~7) (Sunday=0/7) , - * / ? sun,mon,tue,wed,thu,fri,sat
 |  |  |  |  |  |
 *  *  *  *  *  * user-name  command to be executed
 0  1  2  3  4  5

其中 crontab 默认精确到分钟,这里的精度是秒,包含了 6 个通过空格分开的字段,其表示的时间段如上所示。

  • * 任意值,如果秒域为 * 表示任一秒都会触发;
  • , 列举,如果秒域为 5,13,20 则表示会在第 5 秒、第 13 秒、第 20 秒会触发;
  • - 范围,如果秒域为 5-9 表示从第 5 秒到第 9 秒都会触发,等价于 5,6,7,8,9
  • / 间隔,如果秒域为 30/13 表示从第 6 秒开始每隔 13 秒触发一次,也就是 39 52 39 52 ...
  • ? 只能用于日期或者星期中,如果设置了其中的一个,另外一个需要设置为 ?

其中 ? 只能用在 Day of MonthDay of Week 两个域,它也匹配域的任意值,但实际不会,因为两者会相互影响。假设,想每月 20 号触发,而不关心具体是星期几,则可以使用 0 0 0 20 * ? ,最后必须是 ? ,否则会直接报错。

注意,对于 Day of Week 字段来说,在代码中实际使用的范围是 0~6

示例

"0 0 * * * *"                 the top of every hour of every day
"*/10 * * * * *"              every ten seconds
"0 0 8-10 * * *"              8, 9 and 10 o'clock of every day
"0 0/30 8-10 * * *"           8:00, 8:30, 9:00, 9:30 and 10 o'clock every day
"0 0 9-17 * * MON-FRI"        on the hour nine-to-five weekdays
"0 0 0 25 12 ?"               every Christmas Day at midnight


*/10 * * * *     echo "Ten minutes ago." >> /tmp/foo.txt    // 每十分钟执行一次
0 6 * * *        echo "Good morning." >> /tmp/foo.txt       // 每天早上6点
0 */2 * * *      echo "Have a break now." >> /tmp/foo.txt   // 每两个小时
45 4 1,10,22 * * echo "Restart server." >> /tmp/foo.txt     // 每月1、10、22日的4:45
0 23-7/2,8 * * * echo "Have a good dream." >> /tmp/foo.txt  // 晚上11点到早上8点之间每两个小时,早上八点
0 11 4 * 1-3     echo "Just kidding." >> /tmp/foo.txt       // 每月4号和每周的周一到周三的早上11点
45 11 * * 0,6    echo "Have a good lunch." >> /tmp/foo.txt  // 每周六、周日的11点45分
0 9 * * 1-5      echo "Work hard." >> /tmp/foo.txt          // 从周一到周五的9点
2 8-16/3 * * *   echo "Some examples." >> /tmp/foo.txt      // 8:02、11:02、14:02执行

0,30 18-23 * * *    echo "Same." >> /tmp/foo.txt            // 每天18:00到23:00之间每隔30分钟
0-59/30 18-23 * * * echo "Same." >> /tmp/foo.txt            // 同上
*/30 18-23 * * *    echo "Same." >> /tmp/foo.txt            // 同上

在表达式中,每位表示一个时间,例如秒、分、时等。

Tips

如果要测试类似夏令时、冬令时这类的问题,可以通过设置环境变量的方式调整时区,详见 man 3 timegm 中的介绍。

需要测试的场景包括了:夏令时、冬令时、闰年。

libprotocol

这里包含了一些基本的协议,以及常用的 Socket 操作。

简介

一般 Socket 的处理流程分为了发送和接收。

发送流程:

  1. 业务生成数据结构,发送到缓冲队列 (可以是队列、环、优先队列),并通知 Socket 线程;Tips#1
  2. 在 Socket 线程中,进行序列化操作,保存到统一的发送缓冲区中。Tips#3
  3. 然后写入,此时可以通过异步 IO 进行处理;Tips#2

接收流程:

  1. 接收到数据后保存到接收缓冲区,判断是否接收到一个完整的包;
  2. 添加到接收链表中,然后通知工作线程进行处理;
  3. 处理完成后,工作线程向写队列中添加响应报文。

Tips

  1. 默认是在 Socket 线程中进行序列化,如果说性能不满足,那么可以将序列化在发送缓冲队列前进行处理,这时,只需要简单的发送缓冲队列的数据即可。
  2. 这里可能会出现只发送了缓冲队列中一部分数据的情况,需要注意如何进行处理。
  3. 这里的序列化采用的是一个相同的缓冲区,那么可以节省内存使用,而 Tips#1 中的使用方式,实际上是以内存换取效率。

libdown

默认直接覆盖。

PIDFile

这里简单检查 PIDFile 文件,以及 /proc 文件系统中的值,并没有使用 flock 机制。

int pidfile_check(const char *file);
    检查PIDFile对应的进程是否存在,会读取/proc/PID目录,同时校验/proc/PID/comm值。
返回值:
     0 不存在
     1 进程存在
    -1 检查异常,包括各类的异常,例如文件打开异常

int pidfile_update(const char *file);
    更新PIDFile文件值,也就是将当前进程的PID写入到文件中。

void pidfile_destory(const char *file);
    删除PIDFile文件。

也就是说只校验命令,而不校验参数。

其它

子进程处理

在调用子进程时,需要注意如下。

  1. 通过 setpgrp() 设置进程组,在超时或者异常发送信号时,向进程组发送信号。
  2. 使用 prctl(PR_SET_PDEATHSIG, SIGKILL); 确保父进程退出的时候,子进程同样可以接收到信号。
  3. 如果不支持 Clous On Exec ,那么需要在子进程中将相关的文件描述符关闭,注意,为了防止继承系统需要在系统启动时通过 ulimits 进行设置。

正常,如果父进程直接退出,那么子进程会变为僵尸进程,通过 prctl() 可以确保父进程退出时,向子进程发送指定的信号。

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/prctl.h>

int main(void)
{
        int i = 0;
        pid_t pid;

        pid = fork();
        if (pid < 0) {
                fprintf(stderr, "fork failed, %d:%s.\n",
                                errno, strerror(errno));
                return -1;
        } else if (pid == 0) { /* child */
                prctl(PR_SET_PDEATHSIG, SIGKILL);
				/* system("sleep 10000") */
                while (1) {
                    printf("child running...\n");
                    sleep(1);
                }
        }

        for (i = 0; i < 6; i++) {
                printf("father running...\n");
                sleep(1);
        }

        printf("main exit()\n");
        return 0;
}

注意,这里只会涉及到子进程,对于孙子进程实际上是不生效的,例如上述的 system("sleep 10000"); 实际上不会退出。



This Site was built by Jin Yang, generated with Jekyll, and hosted on GitHub Pages
©2013-2019 – Jin Yang