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

----- 可以简单查看当前各个模块的状态
$ git submodule

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