令牌桶算法——信号量实现

头文件定义mytbf.h

#ifndef MYTBF_H__
#define MYTBF_H__

#define MYTBF_MAX 1024
typedef void mytbf_t;

mytbf_t *mytbf_init(int cps, int burst);

int mytbf_fetchtoken(mytbf_t *, int);

int mytbf_returntoken(mytbf_t *, int);

int mytbf_destroy(mytbf_t *);

#endif

令牌桶算法实现部分mytbf.c

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>

#include "mytbf.h"
typedef void (*sighandler_t)(int);

struct mytbf_st
{
    int cps;    // 每秒传输
    int burst;  // 最大限制
    int token;  // 积累传输量
    int pos;    // 记录当前令牌桶在令牌桶数组中的下标
};

struct mytbf_st *mytbf_arr[MYTBF_MAX];
static int inited = 0;
static sighandler_t alrm_handler_save;

static int get_pos(void)
{
    for(int i = 0; i < MYTBF_MAX; i++)
    {
        if(mytbf_arr[i] == NULL)
            return i;
    }
    return -1;
}

static void alrm_handler(int s)
{
    alarm(1);

    for(int i = 0; i < MYTBF_MAX; i++) {
        if(mytbf_arr[i] != NULL)
        {
            mytbf_arr[i]->token += mytbf_arr[i]->cps;
            if(mytbf_arr[i]->token > mytbf_arr[i]->burst)
                mytbf_arr[i]->token = mytbf_arr[i]->burst;
        }
    }
}

static void module_unload(void)
{
    signal(SIGALRM, alrm_handler_save);
    alarm(0);
    for(int i = 0; i < MYTBF_MAX; i++)
        free(mytbf_arr[i]);
}

static void module_load(void)
{
    alrm_handler_save = signal(SIGALRM, alrm_handler);
    alarm(1);   
    atexit(module_unload);
}

mytbf_t *mytbf_init(int cps, int burst)
{
    // 在init函数中使用alarm来定时,同时也要考虑到一个程序中可能创建不止一个令牌桶,如果第二个令牌桶也
    // 调用init,那么会出现两次alarm,程序也会因此执行出错。故应该将alarm设置为只在首次调用时使用。
    if(!inited) {
        module_load();
        inited = 1;
    }


    struct mytbf_st *me = (struct mytbf_st *)malloc(sizeof(struct mytbf_st));
    if(me == NULL) {
        return NULL;
    }
    int pos = get_pos();
    if(pos == -1) {
        return NULL;
    }
    me->cps = cps;
    me->burst = burst;
    me->token = 0;
    me->pos = pos;

    mytbf_arr[pos] = me;

    return me;
}

static int min(const int a, const int b)
{
    return a < b ? a : b;
}

int mytbf_fetchtoken(mytbf_t *ptr, int size)
{
    struct mytbf_st *me = ptr;
    if(size <= 0)
        return -EINVAL;
    
    // 取令牌时,若桶中为空,则等待桶中有令牌再取
    while(me->token <= 0)
        pause();

    int n  = min(me->token, size);
    me->token -= n;
    return n;
}

int mytbf_returntoken(mytbf_t *ptr, int size)
{
    struct mytbf_st *me;

    if(size <= 0) {
        return -EINVAL;
    }

    me->token += size;

    // 令牌桶满则丢弃溢出的部分
    if(me->token > me->burst) {
        me->token = me->burst;
    }

    return size;
}

int mytbf_destroy(mytbf_t *ptr)
{
    struct mytbf_st *me = ptr;
    mytbf_arr[me->pos] = NULL;
    free(me);
    return 0;
}

【注】
使用alarm(N)实现令牌桶或者漏桶时,需要构造“alarm链”,也就是说本次的alarm计时结束并发出SIGALRM信号后,经由singal(SIGALRM, func)捕捉,同时func函数应该设置下一次alarm(N)来继续循环计时。
若将alarm替换为seitimer()函数,就无需再设置下一次alarm,因为seitimer的参数为itimerval类型的指针,这个类型中包括新旧时间,当旧时间结束后,新时间会自动copy给旧时间,达到自动进入下一次计时的目的。
另外,将新时间复制给旧时间的操作是不可分割的原子操作。seitimer计时结果相比alarm更加精准。

seitimer设置执行时间:

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

#include "mytbf.h"
typedef void (*sighandler_t)(int);

struct mytbf_st
{
    int cps;    // 每秒传输
    int burst;  // 最大限制
    int token;  // 积累传输量
    int pos;    // 记录当前令牌桶在令牌桶数组中的下标
};

struct mytbf_st *mytbf_arr[MYTBF_MAX];
static int inited = 0;
static sighandler_t alrm_handler_save;

static int get_pos(void)
{
    for(int i = 0; i < MYTBF_MAX; i++)
    {
        if(mytbf_arr[i] == NULL)
            return i;
    }
    return -1;
}

static void alrm_handler(int s)
{
    // alarm(1);

    for(int i = 0; i < MYTBF_MAX; i++) {
        if(mytbf_arr[i] != NULL)
        {
            mytbf_arr[i]->token += mytbf_arr[i]->cps;
            if(mytbf_arr[i]->token > mytbf_arr[i]->burst)
                mytbf_arr[i]->token = mytbf_arr[i]->burst;
        }
    }
    // alarm(1);

}

static void module_unload(void)
{
    signal(SIGALRM, alrm_handler_save);
    // alarm(0);
    for(int i = 0; i < MYTBF_MAX; i++)
        free(mytbf_arr[i]);
}

static void module_load(void)
{
    struct itimerval *itm = (struct itimerval *)malloc(sizeof(struct itimerval));
    itm->it_interval.tv_sec = 1;
    itm->it_interval.tv_usec = 0;
    itm->it_value.tv_sec = 1;
    itm->it_value.tv_usec = 0;

    alrm_handler_save = signal(SIGALRM, alrm_handler);
    // alarm(1);   
    setitimer(ITIMER_REAL, itm, NULL);
    atexit(module_unload);
}

mytbf_t *mytbf_init(int cps, int burst)
{
    // 在init函数中使用alarm来定时,同时也要考虑到一个程序中可能创建不止一个令牌桶,如果第二个令牌桶也
    // 调用init,那么会出现两次alarm,程序也会因此执行出错。故应该将alarm设置为只在首次调用时使用。
    if(!inited) {
        module_load();
        inited = 1;
    }


    struct mytbf_st *me = (struct mytbf_st *)malloc(sizeof(struct mytbf_st));
    if(me == NULL) {
        return NULL;
    }
    int pos = get_pos();
    if(pos == -1) {
        return NULL;
    }
    me->cps = cps;
    me->burst = burst;
    me->token = 0;
    me->pos = pos;

    mytbf_arr[pos] = me;

    return me;
}

static int min(const int a, const int b)
{
    return a < b ? a : b;
}

int mytbf_fetchtoken(mytbf_t *ptr, int size)
{
    struct mytbf_st *me = ptr;
    if(size <= 0)
        return -EINVAL;
    
    // 取令牌时,若桶中为空,则等待桶中有令牌再取
    while(me->token <= 0)
        pause();

    int n  = min(me->token, size);
    me->token -= n;
    return n;
}

int mytbf_returntoken(mytbf_t *ptr, int size)
{
    struct mytbf_st *me;

    if(size <= 0) {
        return -EINVAL;
    }

    me->token += size;

    // 令牌桶满则丢弃溢出的部分
    if(me->token > me->burst) {
        me->token = me->burst;
    }

    return size;
}

int mytbf_destroy(mytbf_t *ptr)
{
    struct mytbf_st *me = ptr;
    mytbf_arr[me->pos] = NULL;
    free(me);
    return 0;
}

测试(模拟cat命令,令牌桶控制流量)

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "mytbf.h"
#include "mytbf.c"
#define BUFF_SIZE   10
#define BURST       100
#define SOURCE_FILE "such_file"

static int sc_fd, dest_fd;

int main() 
{
    struct mytbf_st *tbf = mytbf_init(BUFF_SIZE, BURST);
    if(tbf == NULL) {
        fprintf(stderr, "mytbf_init():init failed.\n");
        exit(1);
    }

    int size = 0;
    char buff[BUFF_SIZE];
    do {
        if((sc_fd = open(SOURCE_FILE, O_RDONLY)) < 0) {
            if(errno != EINTR) {
                perror("open()");
                exit(1);                 
            }
        }
    } while(sc_fd < 0);

    // open successfully
    while(1) {
        size = mytbf_fetchtoken(tbf, BUFF_SIZE);
        if(size < 0) {
            fprintf(stderr, "mytbf_fetchtoken() failed: %s\n", strerror(-size));
            exit(1);
        }
        
        int read_ret, write_ret;
        // 频繁被中断打断,例如打断了5秒,那么令牌桶中就积累了me->token+5*CPS的令牌,当成功执行后,可以将
        // 上述令牌个数的字节全数输出
        while((read_ret = read(sc_fd, buff, size)) < 0) {
            if(errno != EINTR) {
                perror("read()");
                break;
            }
        }
        if(read_ret == 0) {
            break;
        }

        if(size - read_ret > 0)
            mytbf_returntoken(tbf, size-read_ret);

        int mark = read_ret;
        while(mark) {
            write_ret = write(1, buff, mark);
            mark -= write_ret;
            if(write_ret < 0) {
                if(errno != EINTR) {
                    perror("write()");
                    exit(1);
                }
                //else continue;
            }
            
            //signal(SIGABRT, func);
        }
    }

    close(sc_fd);
    close(dest_fd);
    exit(0);
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值