使用场景:单机上有多个进程运行同一个服务,期望限制某个请求或者其它东西的每段时间运行的次数,就可以使用这个类
#ifndef ACTIVITY_LIMIT_REQUEST_ACTION_H_
#define ACTIVITY_LIMIT_REQUEST_ACTION_H_
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <unistd.h>
#include <errno.h>
#include <sys/shm.h>
#include <sys/time.h>
#include <string>
#include <cmath>
using namespace std;
/** 共享内存结构体 **/
typedef struct shm_data
{
struct timeval tv;
unsigned int uiNumber;
}SRequestLimitStruct;
static const int TIME_SIZE = sizeof(struct timeval);
static const int COUNT_SIZE = sizeof(SRequestLimitStruct) - TIME_SIZE;
class CLimitRequestAction
{
public:
union semun
{
int val; /*value for SETVAL*/
struct semid_ds *buf; /*buffer for IPC_STAT & IPC_SET*/
unsigned short int *array; /*array for GETALL & SETALL*/
struct seminfo *__buf; /*buffer for IPC_INFO*/
};
/* 等待一个二元信号量:阻塞直到信号量的值为正,然后将其减1 */
int binary_semaphore_wait(int semid)
{
struct sembuf sem_b;
sem_b.sem_num = 0;
/* 减一。 */
sem_b.sem_op = -1;
/* 允许撤销操作 */
sem_b.sem_flg = SEM_UNDO;
return semop (semid, &sem_b, 1);
}
/* 对一个二元信号量执行投递操作:将其值加一。 这个操作会立即返回 */
int binary_semaphore_post (int semid)
{
struct sembuf sem_b;
/* 使用(且仅使用)第一个信号量 */
sem_b.sem_num = 0;
/* 加一 */
sem_b.sem_op = 1;
/* 允许撤销操作 */
sem_b.sem_flg = SEM_UNDO; // 程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。
return semop (semid, &sem_b, 1);
}
CLimitRequestAction()
{
bzero(m_szErrMsg, sizeof(m_szErrMsg));
iInitSuccess = 0;
}
~CLimitRequestAction()
{
shmdt((char *)m_pshm);
}
static CLimitRequestAction * Instance()
{
static CLimitRequestAction ms_inst;
return &ms_inst;
}
int init(key_t keyShm, key_t keySem)
{
m_keyShm = keyShm;
m_keySem = keySem;
/** 创建二进制信号量 **/
m_iSemId = semget(m_keySem, 1, 0666 | IPC_CREAT | IPC_EXCL); // 如果key对应的((semflg &IPC_CREAT) &&(semflg &IPC_EXCL))非0, 那么semget返回EEXIST
if (m_iSemId < 0 && errno != EEXIST)
{
snprintf(m_szErrMsg, sizeof(m_szErrMsg)-1, "semget failed. keySem:%u, iSemId:%d, msg:%s", m_keySem, m_iSemId, strerror(errno));
return -1;
}
if(m_iSemId >= 0) // 这个信号量是第一次创建,则初始化
{
/* 初始化信号量 */
semun sem_union;
sem_union.val = 1;
if(semctl(m_iSemId, 0, SETVAL, sem_union) == -1)
{
snprintf(m_szErrMsg, sizeof(m_szErrMsg)-1, "semctl failed. keySem:%u, iSemId:%d, msg:%s", m_keySem, m_iSemId, strerror(errno));
return -2;
}
}
else // 这个信号量已经有了,则直接获得这个信号量值
{
m_iSemId = semget(m_keySem, 1, 0666);
if(m_iSemId < 0)
{
snprintf(m_szErrMsg, sizeof(m_szErrMsg)-1, "semget failed. keySem:%u, iSemId:%d, msg:%s", m_keySem, m_iSemId, strerror(errno));
return -1;
}
}
/** 创建共享内存 **/
m_iShmId = shmget(m_keyShm, sizeof(SRequestLimitStruct), IPC_CREAT|0666); // 如果单独使用IPC_CREAT,shmget()函数要么返回一个已经存在的共享内存的操作符,要么返回一个新建的共享内存的标识符
if(m_iShmId < 0)
{
snprintf(m_szErrMsg, sizeof(m_szErrMsg)-1, "shmget failed. keyShm:%u, iShmid:%d, msg:%s", m_keyShm, m_iShmId, strerror(errno));
return -3;
}
m_pshm = (SRequestLimitStruct *)shmat(m_iShmId, NULL, 0); // 把共享内存区对象映射到调用进程的地址空间
if( (char*)(void*)m_pshm == (char *)-1)
{
snprintf(m_szErrMsg, sizeof(m_szErrMsg)-1, "shmat failed. keyShm:%u, iShmid:%d, msg:%s", m_keyShm, m_iShmId, strerror(errno));
return -4;
}
iInitSuccess = 1;
return 0;
}
/********************************
* 单机上cgi流量限制,通过共享内存和信号量机制实现单机上请求在某段时间内的请求个数限制
* @keySem:信号量key
* @keyShm: 共享内存key
* @uiLimitCount: 限制的请求个数
* @uiInterval: 时间间隔
* 返回值:非0限制,0则根据bIfLimit字段来判断是否限制
********************************/
int RequestNumberLimit(const struct timeval &tvNow, bool &bIfLimit, unsigned int uiLimitCount, unsigned int uiInterval = 1000)
{
bIfLimit = false;
if(binary_semaphore_wait(m_iSemId)) // P操作,失败直接退出进程
{
snprintf(m_szErrMsg, sizeof(m_szErrMsg)-1, "wait semop failed. keySem:%u, iSemId:%d, msg:%s", m_keySem, m_iSemId, strerror(errno));
shmdt((char *)m_pshm);
exit(-1);
}
long long msNow = tvNow.tv_sec*1000 + tvNow.tv_usec/1000;
long long msLastRequestTime = m_pshm->tv.tv_sec * 1000 + m_pshm->tv.tv_usec/1000;
// printf("msNow is %lld, msLastRequestTime is %lld, abs(msNow - msLastRequestTime) is %lld\n", msNow, msLastRequestTime, abs(msNow - msLastRequestTime));
if(abs(msNow - msLastRequestTime) > uiInterval) // 使用绝对值,则可以不用初始化共享内存
{
m_pshm->uiNumber = 0;
m_pshm->tv.tv_sec = tvNow.tv_sec;
m_pshm->tv.tv_usec = tvNow.tv_usec;
}
else
{
if(m_pshm->uiNumber >= uiLimitCount)
{
if(binary_semaphore_post(m_iSemId))
{
snprintf(m_szErrMsg, sizeof(m_szErrMsg)-1, "post semop failed. keySem:%u, iSemId:%d, msg:%s", m_keySem, m_iSemId, strerror(errno));
shmdt((char *)m_pshm);
exit(-1);
}
bIfLimit = true;
return 0;
}
}
++(m_pshm->uiNumber);
if(binary_semaphore_post(m_iSemId))
{
snprintf(m_szErrMsg, sizeof(m_szErrMsg)-1, "post semop failed. keyShm:%u, keySem:%u, iSemId:%d, msg:%s", m_keyShm, m_keySem, m_iSemId, strerror(errno));
shmdt((char *)m_pshm);
exit(-1);
}
return 0;
}
/********************************
* 获取错误信息
********************************/
string GetErrMsg(void)
{
return m_szErrMsg;
}
public:
int iInitSuccess; // 初始化成功与否的标识
private:
key_t m_keyShm;
key_t m_keySem;
int m_iShmId; // 共享内存ID
int m_iSemId; // 信号量ID
SRequestLimitStruct* m_pshm;
char m_szErrMsg[1024];
};示例:
#include "activity_limit_request_action.h"
#include <sys/time.h>
int main(int argc, char *argv[])
{
if(argc < 2)
{
printf("usgae: %s count\n", argv[0]);
return -1;
}
int iCount = atoi(argv[1]);
bool bIfLimit;
for(int i = 0; i < iCount; i++)
{
static bool bInit = false;
if(bInit == false)
{
int iRet = CLimitRequestAction::Instance()->init(0x20131118,0x20131118);
printf("asdadadasdas\n");
if(iRet != 0)
{
printf("init failed.\n");
return -1;
}
bInit = true;
}
struct timeval tvNow;
gettimeofday(&tvNow, NULL);
int iRet = CLimitRequestAction::Instance()->RequestNumberLimit(tvNow, bIfLimit, 50);
printf("pid:%d, i:%d, iRet:%d, bIfLimit:%d, msg:%s\n", getpid(), i, iRet, bIfLimit, CLimitRequestAction::Instance()->GetErrMsg().c_str());
// usleep(5);
}
return 0;
}可同时运行多个进程的副本

本文介绍了一种基于共享内存和信号量机制的限流器实现方案,适用于限制单机上同一服务的请求频率。通过示例代码展示了如何在实际应用中部署此限流器。
1206

被折叠的 条评论
为什么被折叠?



