头文件定义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);
}