架构一个完整的Linux物联网设备应用层事件触发机制程序
·
源码树形结构
event_system/ ├── Makefile ├── event_manager.c # 主事件管理器 ├── event_interface.c # 库接口实现 ├── event_interface.h # 公共头文件 ├── S01event.sh # 初始化脚本 └── dependencies/ ├── $(MOLIB_DIR)/ # 基础库 └── $(MOLIB_UPDATER_DIR)/ # 更新库
软件架构分析
1. 组件分离架构
// 架构示意图 +----------------+ 调用 +-------------------+ | 事件管理器 | <---------> | 事件接口库 | | event_manager | | libevent.so | +----------------+ +-------------------+ ↓ ↓ +----------------+ +-------------------+ | 业务逻辑处理 | | API接口层 | | 主事件循环 | | 其他进程通过该库 | | 系统服务 | | 访问事件服务 | +----------------+ +-------------------+
2. 架构合理性分析
有何优点:
1. 模块化设计合理
KEY_MANAGER_SRCS = event_manager.c # 服务进程 LIBKEY_INTERFACE_SRCS = event_interface.c # 接口库
-
服务进程与接口库分离,符合Unix哲学
2. 进程间通信设计
// 推测的通信模式 App Process 1 App Process 2 App Process 3 ↓ ↓ ↓ +-----------------------------------------------+ | 自制libevent.so (共享库) | +-----------------------------------------------+ | IPC机制 (推测) | +-----------------------------------------------+ | event_manager (服务进程) | +-----------------------------------------------+
-
通过共享库提供统一API
-
服务进程集中管理事件
3. 部署结构清晰
INSTALL -m 755 event_manager $(DESTDIR)$(SBINDIR) # 系统服务 INSTALL -m 755 libevent.so $(DESTDIR)$(LIBDIR) # 共享库 INSTALL -m 664 event_interface.h $(DESTDIR)$(INCDIR) # 开发头文件
Makefile:
# 定义编译标志
CFLAGS := -g -fpic -Wall -I. -D_GNU_SOURCE -I$(MOLIB_DIR)/usr/include -I$(MOLIB_UPDATER_DIR)/usr/include
# -g: 生成调试信息
# -fpic: 生成位置无关代码(用于共享库)
# -Wall: 启用所有警告
# -I.: 包含当前目录到头文件搜索路径
# -D_GNU_SOURCE: 定义GNU源宏,启用GNU扩展
# -I$(MOLIB_DIR)/usr/include: 包含MOLIB目录的头文件
# -I$(MOLIB_UPDATER_DIR)/usr/include: 包含MOLIB更新目录的头文件
# 用于回溯调试
CFLAGS += -rdynamic -fasynchronous-unwind-tables
# -rdynamic: 导出所有符号用于回溯
# -fasynchronous-unwind-tables: 生成异步展开表,改进栈回溯
CFLAGS += -Wall -Werror
# -Werror: 将所有警告视为错误
# 定义链接标志
LDFLAGS := -L. -lpthread -L$(MOLIB_DIR)/usr/lib -L$(MOLIB_UPDATER_DIR)/usr/lib
# -L.: 链接当前目录的库
# -lpthread: 链接pthread线程库
# -L$(MOLIB_DIR)/usr/lib: 链接MOLIB目录的库
# -L$(MOLIB_UPDATER_DIR)/usr/lib: 链接MOLIB更新目录的库
# 安装目录配置
DESTDIR = # 目标目录前缀,用于交叉编译或打包
PREFIX = /usr # 安装前缀目录
BINDIR = $(PREFIX)/bin # 二进制文件目录
SBINDIR = $(PREFIX)/sbin # 系统二进制文件目录
LIBDIR = $(PREFIX)/lib # 库文件目录
INCDIR = $(PREFIX)/include # 头文件目录
INITDIR = /etc/init.d/ # 初始化脚本目录
# 安装工具
INSTALL = install
# 源文件定义
KEY_MANAGER_SRCS = event_manager.c # 事件管理器源文件
LIBKEY_INTERFACE_SRCS = event_interface.c # 事件接口库源文件
# 对象文件生成规则
KEY_MANAGER_OBJS = $(patsubst %.c, %.o, $(KEY_MANAGER_SRCS)) # 事件管理器对象文件
LIBKEY_OBJS = $(patsubst %.c, %.o, $(LIBKEY_INTERFACE_SRCS)) # 事件接口库对象文件
# 默认目标
all:event_manager libevent.so
# 构建事件管理器可执行文件
event_manager:$(KEY_MANAGER_OBJS)
$(CC) -o $@ $? $(LDFLAGS)
# $@: 目标文件名(event_manager)
# $?: 所有比目标新的依赖文件
# 构建共享库
libevent.so:$(LIBKEY_OBJS)
$(CC) -shared -o $@ $? $(LDFLAGS)
# -shared: 生成共享库
# $@: 目标文件名(libevent.so)
# 通用编译规则:从.c文件生成.o文件
%.o:%.c
$(CC) $(CFLAGS) -c $^ -o $@
# $^: 所有的依赖文件
# $@: 目标文件
# 安装目标
install:$(TARGET)
# 创建安装目录
$(INSTALL) -d $(DESTDIR)$(LIBDIR) -d $(DESTDIR)$(SBINDIR) -d $(DESTDIR)$(ETCDIR) -d $(DESTDIR)$(INCDIR) -d $(DESTDIR)$(INITDIR)
# 安装事件管理器到系统二进制目录
$(INSTALL) -m 755 event_manager $(DESTDIR)$(SBINDIR)
# 安装初始化脚本
$(INSTALL) -m 755 S01event.sh $(DESTDIR)$(INITDIR)
# 安装共享库
$(INSTALL) -m 755 libevent.so $(DESTDIR)$(LIBDIR)
# 安装头文件
$(INSTALL) -m 664 event_interface.h $(DESTDIR)$(INCDIR)
# 清理构建文件
clean:
-rm -rf $(KEY_MANAGER_OBJS) $(LIBKEY_OBJS) event_manager libevent.so
# -rm: 忽略错误继续执行
# 卸载已安装的文件
uninstall:
rm -rf $(DESTDIR)$(SBINDIR)/event_manager
rm -rf $(DESTDIR)$(LIBDIR)/libevent.so
rm -rf $(DESTDIR)$(INITDIR)/S01event.sh
rm -rf $(DESTDIR)$(INCDIR)/event_interface.h
# 声明伪目标
.PHONY:all clean
event_interface.h
#ifndef __COMMON_H__ // 头文件保护宏,防止重复包含
#define __COMMON_H__
// 标准输入输出函数,用于printf、scanf等
#include <stdio.h>
// 网络地址转换函数,如inet_pton、inet_ntop等
#include <arpa/inet.h>
// Internet地址族定义,如sockaddr_in结构体
#include <netinet/in.h>
// 套接字函数,如socket、bind、connect等
#include <sys/socket.h>
// 基本系统数据类型,如size_t、pid_t等
#include <sys/types.h>
// Unix标准函数,如read、write、close等
#include <unistd.h>
// 标准库函数,如malloc、free、exit等
#include <stdlib.h>
// 字符串处理函数,如strcpy、strlen、memset等
#include <string.h>
// 错误号定义和错误处理函数
#include <errno.h>
/**
* 调试输出宏 - 设计模式分析:空对象模式
* 性能分析:发布版本中定义为空,完全消除调试输出开销
* 功能:在调试版本中输出文件、函数、行号等调试信息
*/
#define DEBUG(x,y...) //(printf("DEBUG [ %s : %s : %d] "x"\n",__FILE__, __func__, __LINE__, ##y))
/**
* 错误输出宏 - 设计模式分析:模板方法模式
* 性能分析:固定格式输出,运行时开销较小但会影响性能关键路径
* 功能:输出错误信息,包含文件名、函数名、行号等上下文
* 翻译:ERROR [ 文件名 : 函数名 : 行号 ] 用户自定义错误信息
*/
#define ERROR(x,y...) (printf("ERROR [ %s : %s : %d] "x"\n", __FILE__, __func__, __LINE__, ##y))
/**
* 服务器注册端口 - 设计模式分析:常量定义模式
* 性能分析:编译时常量,无运行时开销
* 功能:定义客户端注册到服务器时使用的端口号
* 架构分析:用于服务发现或客户端注册机制
*/
#define SERVER_REGISTER_PORT 65430
/**
* 服务器事件端口 - 设计模式分析:常量定义模式
* 性能分析:编译时常量,无运行时开销
* 功能:定义服务器接收和处理事件时使用的端口号
* 架构分析:事件驱动架构,分离注册和事件处理逻辑
*/
#define SERVER_EVENT_PORT 65431
/**
* 服务器地址 - 设计模式分析:配置模式
* 性能分析:字符串常量,编译时确定
* 功能:定义服务器监听的本机回环地址
* 架构分析:当前配置为本地测试,生产环境应支持配置化
*/
#define SERVER_ADDR "127.0.0.1"
#endif // __COMMON_H__ // 头文件保护宏结束
event_interface.c # 库接口实现
#include <unistd.h> // Unix标准函数,提供sleep、close等
#include <pthread.h> // POSIX线程库,用于多线程编程
#include <errno.h> // 错误号定义,用于错误处理
#include <string.h> // 字符串处理函数
#include "event_interface.h" // 事件接口头文件,定义事件结构和函数声明
#include "common.h" // 通用头文件,定义服务器地址和端口
/**
* 连接到TCP服务器 - 设计模式:工厂方法模式
* 性能分析:创建网络连接,耗时操作,应复用连接
* @param ipaddr 服务器IP地址
* @param port 服务器端口号
* @return 成功返回套接字描述符,失败返回-1
*/
static int connect_to_tcp_server(char *ipaddr, int port)
{
int sockfd = -1;
struct sockaddr_in seraddr; // 服务器地址结构
// 创建TCP套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0); // AF_INET:IPv4, SOCK_STREAM:TCP
if (sockfd == -1) {
printf("[EventManager - Error] socket() error for connect_to_tcp_server failed: %s.\n",
strerror(errno)); // 输出套接字创建错误
return -1;
}
// 设置服务器地址信息
seraddr.sin_family = AF_INET; // 地址族:IPv4
seraddr.sin_port = htons(port); // 端口号转网络字节序
seraddr.sin_addr.s_addr = inet_addr(ipaddr); // IP地址转网络字节序
// 连接到服务器
if (-1 == connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr))) {
printf("[EventManager - Error] connect to event manager failed: %s.\n", strerror(errno));
return -1; // 连接失败
}
return sockfd; // 返回连接成功的套接字
}
/**
* 接收事件线程函数 - 设计模式:观察者模式 + 自动重连机制
* 性能分析:持续轮询接收事件,CPU占用需优化,重连机制保证可靠性
* @param arg 事件处理器指针
* @return NULL
*/
static void *recv_event_thread(void *arg)
{
int ret = 0;
tang_event event; // 事件结构体
event_handler *handler = (event_handler *)arg; // 转换参数为事件处理器
while (1) { // 无限循环,持续监听事件
// 接收事件数据
ret = recv(handler->sockfd, &event, sizeof(event), 0); // MSG_WAITALL?
if (ret == sizeof(event)) { // 成功接收到完整事件
if (handler->callback) // 回调函数存在
handler->callback(event, handler->param); // 执行用户回调
} else if (ret == 0){ // 连接断开
printf("[EventManager - Error] event_manager disconnected(event_manager Dead?), Re-connect after 500ms.\n");
usleep(500 * 1000); // 等待500ms后重连
if (handler->sockfd)
close(handler->sockfd); // 关闭旧连接
// 重新连接到注册端口
handler->sockfd = connect_to_tcp_server(SERVER_ADDR, SERVER_REGISTER_PORT);
} else { // 接收错误
printf("[EventManager - Error] recieve data from event_manager failed: %s, Re-try after 500ms.\n",
strerror(errno));
usleep(500 * 1000); // 等待500ms后重试
}
}
return NULL;
}
/**
* 获取事件处理器 - 设计模式:工厂模式 + 线程创建
* 性能分析:内存分配+网络连接+线程创建,初始化成本高,应考虑对象池
* @param callback 事件回调函数
* @param param 用户自定义参数
* @return 成功返回事件处理器指针,失败返回NULL
*/
event_handler *tang_event_handler_get(EventCallback callback, void *param)
{
// 分配事件处理器内存
event_handler *handler = malloc(sizeof(*handler));
if (!handler) {
printf("[EentManager - Error] alloc memory for handler failed: %s.\n",
strerror(errno));
return NULL;
}
// 初始化事件处理器字段
handler->callback = callback; // 设置回调函数
handler->param = param; // 设置用户参数
// 连接到事件管理器的注册端口
handler->sockfd = connect_to_tcp_server(SERVER_ADDR, SERVER_REGISTER_PORT);
if (handler->sockfd < 0) {
printf("[EventManager - Error] Can not connect to event manager.\n");
free(handler); // 连接失败,释放内存
return NULL;
}
pthread_t recv_event_pthread;
// 创建事件接收线程
while (0 != pthread_create(&recv_event_pthread, NULL, recv_event_thread, handler)) {
printf("[EventManager - Error] create thread for listening event failed: %s, Re-try after 500ms.\n",
strerror(errno));
usleep(500 * 1000); // 线程创建失败,等待后重试
}
// 分离线程,线程退出时自动释放资源
pthread_detach(recv_event_pthread); /* FIXME: Never mind error here. may memory leak if occur */
// 注意:pthread_detach可能失败,但这里忽略错误,可能导致内存泄漏
return handler; // 返回创建的事件处理器
}
/**
* 发送事件 - 设计模式:命令模式 + 请求-响应机制
* 性能分析:每次发送都新建连接,性能较差,应考虑连接复用
* @param event 要发送的事件
* @return 成功返回0,失败返回-1
*/
int tang_event_send(tang_event event)
{
int sockfd = -1;
int ret = 0;
char feedback[2] = ""; // 服务器反馈缓冲区
// 连接到事件端口(注意:与注册端口不同)
sockfd = connect_to_tcp_server(SERVER_ADDR, SERVER_EVENT_PORT);
if (sockfd < 0) {
printf("can not connect to event manager, exit..\n");
ret = -1;
goto func_exit; // 使用goto统一清理资源
}
// 发送事件数据
ret = send(sockfd, &event, sizeof(event), 0);
if (ret != sizeof(event)) { // 发送数据量不匹配
printf("send event error: %s.\n", strerror(errno));
ret = -1;
goto func_exit;
}
// 接收服务器反馈
ret = recv(sockfd, feedback, 2, 0);
if (ret == 2) { // 成功接收2字节反馈
if (feedback[0] == '1') { // 服务器返回'1'表示成功
ret = 0;
goto func_exit;
} else { // 其他值表示失败
ret = -1;
goto func_exit;
}
} else { // 接收反馈失败
ret = -1;
goto func_exit;
}
func_exit:
if (sockfd) {
close(sockfd); // 关闭套接字
sockfd = -1;
}
return ret;
}
/**
* 释放事件处理器 - 设计模式:资源管理模式
* 性能分析:简单资源释放操作,无性能瓶颈
* @param handler 要释放的事件处理器
*/
void tang_event_handler_put(event_handler *handler)
{
if (!handler) // 空指针检查
return;
if (handler->sockfd > 0) {
close(handler->sockfd); // 关闭网络连接
handler->sockfd = -1;
}
handler->callback = NULL; // 清空回调指针
free(handler); // 释放内存
}
event_manager.c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <dirent.h>
#include <pthread.h>
#include <linux/input.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <linux/netlink.h>
#include <sys/types.h>
#include "event_interface.h"
#include "common.h"
#define EVENT_FD_SIZE 32 /* 监听文件描述符数组大小 - 设计模式:配置常量模式 */
#define CONN_FD_SIZE 32 /* 连接文件描述符数组大小 - 设计模式:配置常量模式 */
#define UEVENT_MSG_LEN 1024 /* 内核uevent消息最大长度 */
/**
* uevent结构体 - 设计模式:数据转换模式
* 性能分析:存储解析后的uevent信息,避免重复解析字符串
*/
struct uevent {
const char *action; // 动作:add/remove/change
const char *path; // 设备路径
const char *subsystem; // 设备子系统
const char *firmware; // 固件信息
int major; // 主设备号
int minor; // 次设备号
};
// 函数声明
static void openKeyboard();
static void closeKeyboard();
static void eventLoop(void);
// 全局变量定义
static int event_fd[EVENT_FD_SIZE] = {[0 ... EVENT_FD_SIZE-1] = -1}; // 事件监听fd数组,初始化为-1
static int conn_fd[CONN_FD_SIZE] = {[0 ... CONN_FD_SIZE-1] = -1}; // 客户端连接fd数组,初始化为-1
static int device_fd = -1; // uevent设备文件描述符
static int max_fd = -1; // select最大文件描述符
/**
* 启动TCP服务器 - 设计模式:工厂模式
* 性能分析:创建监听socket,系统调用开销,应避免频繁调用
* @param ipaddr 监听IP地址
* @param port 监听端口
* @return 成功返回socket fd,失败返回-1
*/
static int start_tcp_server(char *ipaddr, int port)
{
int sockfd;
int opt = 1; // SO_REUSEADDR选项值
struct sockaddr_in seraddr; // 服务器地址结构
memset(&seraddr, 0, sizeof(struct sockaddr_in)); // 清空地址结构
seraddr.sin_family = AF_INET; // IPv4地址族
seraddr.sin_port = htons(port); // 端口转网络字节序
seraddr.sin_addr.s_addr = inet_addr(ipaddr); // IP地址转网络字节序
//seraddr.sin_addr.s_addr = htonl(INADDR_ANY); // 注释掉的任意地址绑定
// 重试创建socket直到成功
while ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
printf("[EventManager - Error] socket() for processing event hanlder failed: %s,"
"Re-try after 500ms.\n", strerror(errno));
usleep(500 * 1000); // 等待500ms后重试
}
// 设置地址重用选项
if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)))
printf("[EventManager - Warning] set SO_REUSEADDR failed, Ignore it.\n");
// 绑定socket到地址
if (-1 == bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr))) {
printf("[EventManager - Error] bind for event handler socket failed: %s,"
"kernel uevent support is disabled!!!\n", strerror(errno));
close(sockfd);
return -1;
}
return sockfd; // 返回监听socket
}
/**
* 分发事件到所有客户端 - 设计模式:发布-订阅模式
* 性能分析:遍历所有连接发送数据,O(n)复杂度,连接数多时可能成为瓶颈
* @param event 要分发的事件
* @return 总是返回0
*/
static int distribute_event(tang_event event)
{
int i = 0;
// 遍历所有客户端连接
for (i = 0; i < CONN_FD_SIZE; i++) {
if (conn_fd[i] >= 0) // 连接有效
send(conn_fd[i], &event, sizeof(event), 0); /* 忽略发送错误 */
}
return 0;
}
/**
* 打开输入设备事件文件 - 设计模式:资源管理模式
* 性能分析:系统调用open开销,设备发现阶段调用
* @param dev 设备文件路径
* @return 成功返回0,失败返回-1
*/
static int openevent(char *dev)
{
int fd = 0;
int i = 0;
fd = open(dev, O_RDWR); // 以读写方式打开设备文件
if (fd < 0) {
printf("[EventManager - Warning] open %s failed: %s, try next.\n",
dev, strerror(errno));
} else {
// 在事件fd数组中寻找空位
for (i = 0; i < EVENT_FD_SIZE; i++) {
if (event_fd[i] < 0) {
printf("[EventManager - info] new input device added: %s\n", dev);
event_fd[i++] = fd; // 存储fd并增加i
break;
}
}
// 数组已满
if (i == EVENT_FD_SIZE) {
printf("[EventManager - Warning] Too many file opened already, Will not monitor more.\n");
return -1;
}
}
return 0;
}
/**
* 打开所有键盘输入设备 - 设计模式:迭代器模式
* 性能分析:目录遍历和文件打开,初始化时调用一次
*/
static void openKeyboard(void)
{
struct dirent *ptr = NULL;
char eventfile[512] = ""; // 设备文件路径缓冲区
// 打开输入设备目录
DIR *dir = opendir("/dev/input");
if (!dir) {
printf("[EventManager - Warning] open /dev/input failed: %s,"
"key event support is disabled!!!\n", strerror(errno));
return;
}
// 遍历目录项
while (NULL != (ptr = readdir(dir))) {
// 查找event开头的设备文件
if (0 == strncmp(ptr->d_name, "event", 5)) {
memset(eventfile, 0, sizeof(eventfile)); // 清空路径缓冲区
sprintf(eventfile, "/dev/input/%s", ptr->d_name); // 构造完整路径
openevent(eventfile); // 打开设备文件
}
}
closedir(dir); // 关闭目录
return;
}
/**
* 打开内核uevent socket - 设计模式:观察者模式
* 性能分析:创建netlink socket,接收内核热插拔事件
* @return 成功返回0,失败返回-1
*/
static int openUeventSocket(void)
{
int i = 0;
int s = -1;
int ret = 0;
int buffersize = 1024; // 接收缓冲区大小
struct sockaddr_nl addr; // netlink地址结构
memset(&addr, 0, sizeof(addr)); // 清空地址结构
addr.nl_family = AF_NETLINK; // netlink地址族
addr.nl_pid = getpid(); // 当前进程ID作为端口ID
addr.nl_groups = 0xffffffff; // 监听所有多播组
// 重试创建netlink socket直到成功
while ((s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT)) == -1) {
printf("[EventManager - Error] socket() for uevent failed: %s,"
"try after 500ms.\n", strerror(errno));
usleep(500 * 1000); // 等待500ms后重试
}
// 设置接收缓冲区大小
if (-1 == setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize))) {
printf("[EventManager - Error] SO_RCVBUFFORCE failed: %s,"
"kernel uevent support is disabled!!!\n", strerror(errno));
ret = -1;
goto err_exit;
}
// 绑定netlink socket
if (-1 == bind(s, (struct sockaddr *)&addr, sizeof(addr))) {
printf("[EventManager - Error] bind for kernel uevent failed: %s,"
"kernel uevent support is disabled!!!\n", strerror(errno));
ret = -1;
goto err_exit;
}
// 在事件fd数组中存储uevent socket
for (i = 0; i < EVENT_FD_SIZE; i++) {
if (event_fd[i] < 0) {
event_fd[i] = s;
break;
}
}
device_fd = s; // 保存uevent设备fd
return ret;
err_exit:
if (s >= 0)
close(s); // 错误时关闭socket
return ret;
}
/**
* 关闭所有键盘输入设备 - 设计模式:资源清理模式
* 性能分析:关闭所有打开的设备文件,退出时调用
*/
static void closeKeyboard()
{
int i = 0;
// 遍历关闭所有事件文件描述符
for (i = 0; i < EVENT_FD_SIZE; i++) {
if (event_fd[i] > 0) {
close(event_fd[i]);
event_fd[i] = -1; // 重置为无效值
}
}
}
/**
* 处理内核uevent设备事件 - 设计模式:状态机模式 + 过滤器模式
* 性能分析:字符串解析和模式匹配,可能成为性能瓶颈
* @param fd uevent socket文件描述符
* @return 成功返回0,错误返回相应错误码
*/
static int handle_device(int fd)
{
int i = 0;
char msg[UEVENT_MSG_LEN + 2] = {}; // uevent消息缓冲区,+2保证结尾
char cred_msg[CMSG_SPACE(sizeof(struct ucred))] = {}; // 证书消息缓冲区
struct sockaddr_nl snl; // netlink源地址
struct iovec iov = {msg, sizeof(msg)}; // 分散聚集I/O向量
struct msghdr hdr = {&snl, sizeof(snl), &iov, 1, cred_msg, sizeof(cred_msg), 0}; // 消息头
tang_event event; // 事件结构
// 接收uevent消息
ssize_t n = recvmsg(fd, &hdr, 0);
if (n == 0) { /* 对端有序关闭 */
printf("[EventManager - Error] kernel uevent server shutdown,"
"kernel uevent support will disable soon!!!\n");
return EBADFD; // 文件描述符错误
} else if (n < 0 || n >= UEVENT_MSG_LEN) { /* 接收错误 || 接收溢出 */
printf("[EventManager - Error] recvmsg kernel uevent failed: %s,"
"Abandon this time.\n", strerror(errno));
return -1;
}
/* 忽略非内核netlink多播消息 */
if ((snl.nl_groups != 1) || (snl.nl_pid != 0)) {
printf("[EventManager - Error] bad kernel uevent,"
"Abandon this time.\n");
return -1;
}
/* 处理整个字符串:将所有'\0'替换为'\n'便于显示 */
for (i = 0; i < n; i++)
if (*(msg + i) == '\0')
msg[i] = '\n';
// 初始化事件结构
memset(&event, 0, sizeof(event));
event.type = EVENT_MISC;
// 线路输入设备事件处理 (M150平台)
if (strstr(msg, "change@/devices/platform/jz-linein")) {
strcpy(event.event.misc.name, "linein");
if (strstr(msg, "LINEIN_STATE=IN") != NULL)
strcpy(event.event.misc.type, "plugin"); // 插入
else if (strstr(msg, "LINEIN_STATE=OUT"))
strcpy(event.event.misc.type, "plugout"); // 拔出
// 线路输入设备事件处理 (X1000平台)
} else if (strstr(msg, "change@/devices/virtual/switch/linein")) {
strcpy(event.event.misc.name, "linein");
if (strstr(msg, "SWITCH_STATE=1") != NULL)
strcpy(event.event.misc.type, "plugin");
else if (strstr(msg, "SWITCH_STATE=0"))
strcpy(event.event.misc.type, "plugout");
// 耳机设备事件处理
} else if (strstr(msg, "change@/devices/virtual/switch/h2w")) {
strcpy(event.event.misc.name, "headset");
if (strstr(msg, "SWITCH_STATE=1") != NULL)
strcpy(event.event.misc.type, "plugin");
else if (strstr(msg, "SWITCH_STATE=0"))
strcpy(event.event.misc.type, "plugout");
// SPDIF数字音频接口事件
} else if (strstr(msg, "change@/devices/virtual/switch/spdif")) {
strcpy(event.event.misc.name, "spdif");
if (strstr(msg, "SWITCH_STATE=1") != NULL)
strcpy(event.event.misc.type, "plugin");
else if (strstr(msg, "SWITCH_STATE=0"))
strcpy(event.event.misc.type, "plugout");
// TF卡插入事件
} else if (strstr(msg, "add@/devices/platform/jzmmc.1") ||
strstr(msg, "add@/devices/platform/jzmmc_v1.2.0")) {
strcpy(event.event.misc.name, "tfcard");
strcpy(event.event.misc.type, "plugin");
// TF卡拔出事件
} else if (strstr(msg, "remove@/devices/platform/jzmmc.1") ||
strstr(msg, "remove@/devices/platform/jzmmc_v1.2.0")) {
strcpy(event.event.misc.name, "tfcard");
strcpy(event.event.misc.type, "plugout");
// U盘插入事件
} else if (strstr(msg, "add@/devices/platform/jz-dwc2/dwc2/usb1/1-1/1-1:1.0/host") && strstr(msg, "/block/sd")) {
strcpy(event.event.misc.name, "udisk");
strcpy(event.event.misc.type, "plugin");
// U盘拔出事件
} else if (strstr(msg, "remove@/devices/platform/jz-dwc2/dwc2/usb1/1-1/1-1:1.0/host") && strstr(msg, "/block/sd")) {
strcpy(event.event.misc.name, "udisk");
strcpy(event.event.misc.type, "plugout");
// USB设备状态变化事件
} else if (strstr(msg, "change@/devices/virtual/android_usb/android0")) {
strcpy(event.event.misc.name, "usb");
if (strstr(msg, "USB_STATE=CONNECTED"))
strcpy(event.event.misc.type, "connected"); // 已连接
else if (strstr(msg, "USB_STATE=DISCONNECTED"))
strcpy(event.event.misc.type, "disconnected"); // 已断开
else if (strstr(msg, "USB_STATE=CONFIGURED"))
strcpy(event.event.misc.type, "configured"); // 已配置
// USB音频设备事件
} else if (strstr(msg,"change@/devices/platform/jz-dwc2")){
strcpy(event.event.misc.name, "uaudio");
if (strstr(msg, "USB_STATE=CONNECTED")){
strcpy(event.event.misc.type, "connected");
}else if (strstr(msg, "USB_STATE=DISCONNECTED")){
strcpy(event.event.misc.type, "disconnected");
}
// 新输入设备添加事件
} else if (strstr(msg, "add@/devices/virtual/input/")) {
char new_input_dev[32] = {};
char *tmp = NULL;
int num = 0;
tmp = strstr(msg, "DEVNAME=input/event");
if (tmp) {
// 解析事件编号
sscanf(tmp + strlen("DEVNAME=input/event"), "%d", &num);
sprintf(new_input_dev, "/dev/input/event%d", num);
num = 100; // 重试次数
// 等待设备文件创建完成
while (num--) {
if (!access(new_input_dev, 0)) { // 检查文件是否存在
openevent(new_input_dev); // 打开新设备
break;
}
usleep(10 *1000); // 等待10ms
}
if (num <= 0)
printf("[EventManager - Error] %s NOT create after 1 Second\n", new_input_dev);
}
// 电池状态变化事件
} else if (!strncmp(msg, "change@", 7)) {
char *tmp = strrchr(msg, '/'); // 查找最后一个斜杠
if (tmp && !strncmp(tmp, "/battery", 8)) {
strcpy(event.event.misc.name, "battery");
strcpy(event.event.misc.type, "change");
} else {
return 0; // 不支持的change事件
}
// 其他未支持的事件
} else {
#if 0 // 仅调试时启用
printf("Unsupport uevent, Ignore it.\n");
printf("\n\n---------msg--------\n"
"%s"
"\n----------msg-------\n\n", msg);
#endif
return 0; // 忽略不支持的事件
}
// 分发处理好的事件
return distribute_event(event);
}
/**
* 检查按键事件是否有效 - 设计模式:过滤器模式
* 性能分析:简单类型检查,开销很小
* @param event 输入事件
* @return 有效返回true,无效返回false
*/
static bool keyevent_is_valid(struct input_event event)
{
// 检查事件类型:按键、相对坐标、绝对坐标
if (event.type == EV_KEY || event.type == EV_REL || event.type == EV_ABS)
return true;
return false;
}
/**
* 处理键盘输入事件 - 设计模式:适配器模式
* 性能分析:系统调用read开销,事件触发频率决定性能影响
* @param fd 输入设备文件描述符
* @return 成功返回0,失败返回-1
*/
static int handle_key(int fd)
{
int i = 0;
struct tang_event event;
event.type = EVENT_KEY;
// 读取输入事件数据
if (read(fd, &event.event.key.key, sizeof(event.event.key.key)) !=
sizeof(event.event.key.key)) {
// 设备不存在错误
if (errno == ENODEV) {
close(fd); // 关闭无效设备
// 从事件fd数组中移除
for (i = 0; i < EVENT_FD_SIZE; i++) {
if (event_fd[i] == fd) {
event_fd[i] = -1;
break;
}
}
} else {
printf("[EventManager - Error] read keyboard failed: %s,"
"Abandon this time.\n", strerror(errno));
}
return -1;
}
// 检查事件有效性并分发
if (keyevent_is_valid(event.event.key.key)) {
#if 0 // 仅调试时启用
printf("type: %d, code: %d, value: %d.\n",
event.event.key.key.type,
event.event.key.key.code,
event.event.key.key.value);
#endif
return distribute_event(event);
} else {
return 0; // 无效事件忽略
}
}
/**
* 处理文件描述符事件 - 设计模式:策略模式
* 性能分析:简单分支判断,开销很小
* @param fd 就绪的文件描述符
* @return 处理结果
*/
static int handle_fd(int fd)
{
#if 0 // 仅调试时启用
printf("fd %d can be read.\n", fd);
#endif
// 根据fd类型选择处理函数
if (device_fd >= 0 && fd == device_fd)
return handle_device(fd); // uevent设备
else
return handle_key(fd); // 键盘输入设备
}
/**
* 主事件循环 - 设计模式:Reactor模式
* 性能分析:select系统调用,O(n)复杂度,连接数多时考虑epoll
*/
void eventLoop(void)
{
fd_set rfds; // 读文件描述符集合
int i = 0;
int ret = 0;
while (1) { // 无限事件循环
FD_ZERO(&rfds); // 清空描述符集合
// 设置所有有效的事件fd到集合中
for (i = 0; i < EVENT_FD_SIZE; i++) {
if (event_fd[i] >= 0) {
FD_SET(event_fd[i], &rfds);
} else {
break; // 遇到无效fd提前结束
}
}
// 计算最大文件描述符
max_fd = -1;
for (i = 0; i < EVENT_FD_SIZE; i++) {
if (event_fd[i] > 0) {
max_fd = event_fd[i] > max_fd ? event_fd[i] : max_fd;
}
}
#if 0 // 仅调试时启用
printf("max_fd = %d.\n", max_fd);
printf("device_fd = %d.\n", device_fd);
for (i = 0; i < EVENT_FD_SIZE; i++)
if (event_fd[i] > 0)
printf("event[%d] = %d.\n", i, event_fd[i]);
#endif
// 等待事件就绪
ret = select(max_fd + 1, &rfds, NULL, NULL, NULL);
if (ret < 0) {
printf("[EventManager - Error] select() for key_manager failed: %s,"
"Re-try after 500ms.\n", strerror(errno));
usleep(500 * 1000);
continue; // 错误时重试
}
// 处理所有就绪的文件描述符
for (i = 0; i < EVENT_FD_SIZE; i++) {
if (event_fd[i] >= 0 && FD_ISSET(event_fd[i], &rfds))
if (handle_fd(event_fd[i]) == EBADFD) {
// 文件描述符错误,关闭并移除
close(event_fd[i]);
event_fd[i] = -1;
}
}
}
}
/**
* 处理客户端注册请求线程 - 设计模式:生产者-消费者模式
* 性能分析:accept阻塞调用,连接建立开销
* @param data 线程参数(未使用)
* @return NULL
*/
void *process_handler_request(void *data)
{
int i = 0;
int sockfd = -1, connfd = -1;
struct sockaddr_in cliaddr; // 客户端地址
socklen_t addrlen = sizeof(struct sockaddr_in);
// 启动TCP服务器监听注册端口
sockfd = start_tcp_server(SERVER_ADDR, SERVER_REGISTER_PORT);
if (sockfd == -1) {
printf("start tcp server failed, Protocol event support is disabled!!!.\n");
return NULL;
}
// 开始监听
if (-1 == listen(sockfd, 10)) { // backlog=10
printf("[EventManager - Error] listen for handle handler request failed: %s,"
"Protocol event support is disabled!!!.\n", strerror(errno));
close(sockfd);
return NULL;
}
// 接受客户端连接循环
while (1) {
connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &addrlen);
if (-1 == connfd) {
printf("[EventManager - Warning] accept recv protocol connect failed: %s,"
"Ignore this time!!!.\n", strerror(errno));
continue; // 接受错误继续循环
}
// 在连接fd数组中存储新连接
for (i = 0; i < CONN_FD_SIZE; i++) {
if (conn_fd[i] < 0) {
conn_fd[i] = connfd;
break;
}
}
// 连接数达到上限
if (i == CONN_FD_SIZE) {
printf("[EventManager - Warning] Too many connection already, Will not accept new request.\n");
break; // 退出循环
}
}
// 清理所有连接
for (i = 0; i < CONN_FD_SIZE; i++) {
if (conn_fd[i] > 0)
close(conn_fd[i]);
}
close(sockfd); // 关闭监听socket
return NULL;
}
/**
* 处理协议事件线程 - 设计模式:请求-响应模式
* 性能分析:每个事件新建连接,性能较差,应考虑长连接
* @param args 线程参数(未使用)
* @return NULL
*/
static void *process_protocol_event(void *args)
{
int sockfd = -1;
int connfd = -1;
int ret = 0;
struct tang_event event;
struct sockaddr_in cliaddr;
socklen_t addrlen = sizeof(struct sockaddr_in);
// 启动TCP服务器监听事件端口
sockfd = start_tcp_server(SERVER_ADDR, SERVER_EVENT_PORT);
if (-1 == listen(sockfd, 10)) {
printf("[EventManager - Error] listen for recv protocol event failed: %s,"
"Protocol event support is disabled!!!.\n", strerror(errno));
return NULL;
}
// 事件处理循环
while (1) {
connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &addrlen);
if (-1 == connfd) {
printf("[EventManager - Warning] accept recv protocol connect failed: %s,"
"Ignore this time!!!.\n", strerror(errno));
continue;
}
// 读取事件数据
ret = read(connfd, &event, sizeof(event));
if (ret != sizeof(event)) {
if (ret == 0) {
printf("[EventManager - Warning ] connection peer shutdown, close it.\n");
continue;
} else {
printf("[EventManager - Error] recv protocol failed: %s,"
"Abandon this time.\n", strerror(errno));
write(connfd, "0", 2); // 发送失败响应
continue;
}
}
/* 反馈处理结果 */
write(connfd, "1", 2); // 发送成功响应
close(connfd); // 关闭连接
distribute_event(event); // 分发事件
}
return NULL;
}
/**
* 信号描述字符串数组 - 设计模式:查表模式
* 性能分析:编译时常量,无运行时开销
*/
static char *signal_str[] = {
[1] = "SIGHUP", [2] = "SIGINT", [3] = "SIGQUIT", [4] = "SIGILL", [5] = "SIGTRAP",
[6] = "SIGABRT", [7] = "SIGBUS", [8] = "SIGFPE", [9] = "SIGKILL", [10] = "SIGUSR1",
[11] = "SIGSEGV", [12] = "SIGUSR2", [13] = "SIGPIPE", [14] = "SIGALRM", [15] = "SIGTERM",
[16] = "SIGSTKFLT", [17] = "SIGCHLD", [18] = "SIGCONT", [19] = "SIGSTOP", [20] = "SIGTSTP",
[21] = "SIGTTIN", [22] = "SIGTTOU", [23] = "SIGURG", [24] = "SIGXCPU", [25] = "SIGXFSZ",
[26] = "SIGVTALRM", [27] = "SIGPROF", [28] = "SIGWINCH", [29] = "SIGIO", [30] = "SIGPWR",
[31] = "SIGSYS", [34] = "SIGRTMIN", [35] = "SIGRTMIN+1", [36] = "SIGRTMIN+2", [37] = "SIGRTMIN+3",
[38] = "SIGRTMIN+4", [39] = "SIGRTMIN+5", [40] = "SIGRTMIN+6", [41] = "SIGRTMIN+7", [42] = "SIGRTMIN+8",
[43] = "SIGRTMIN+9", [44] = "SIGRTMIN+10", [45] = "SIGRTMIN+11", [46] = "SIGRTMIN+12", [47] = "SIGRTMIN+13",
[48] = "SIGRTMIN+14", [49] = "SIGRTMIN+15", [50] = "SIGRTMAX-14", [51] = "SIGRTMAX-13", [52] = "SIGRTMAX-12",
[53] = "SIGRTMAX-11", [54] = "SIGRTMAX-10", [55] = "SIGRTMAX-9", [56] = "SIGRTMAX-8", [57] = "SIGRTMAX-7",
[58] = "SIGRTMAX-6", [59] = "SIGRTMAX-5", [60] = "SIGRTMAX-4", [61] = "SIGRTMAX-3", [62] = "SIGRTMAX-2",
[63] = "SIGRTMAX-1", [64] = "SIGRTMAX",
};
/**
* 信号处理函数 - 设计模式:回调模式
* 性能分析:信号处理上下文,应避免复杂操作
* @param signo 信号编号
*/
void sig_handler(int signo)
{
char cmd[64] = {};
printf("\n\n[%s: %d] event_manager crashed by signal %s.\n", __func__, __LINE__, signal_str[signo]);
// 处理崩溃信号
if (signo == SIGSEGV || signo == SIGBUS ||
signo == SIGTRAP || signo == SIGABRT) {
sprintf(cmd, "cat /proc/%d/maps", getpid()); // 构造查看进程内存映射命令
printf("Process maps:\n");
system(cmd); // 执行命令显示内存映射
}
closeKeyboard(); // 清理资源
exit(-1); // 退出进程
}
/**
* 主函数 - 设计模式:组合模式
* 性能分析:初始化阶段,一次性设置
* @param argc 参数个数
* @param argv 参数数组
* @return 程序退出码
*/
int main(int argc, char *argv[])
{
pthread_t process_request_thread; // 客户端请求处理线程
pthread_t process_protocol_thread;; // 协议事件处理线程
// 信号处理设置
signal(SIGINT, sig_handler); // 中断信号
signal(SIGUSR1, sig_handler); // 用户定义信号1
signal(SIGUSR2, sig_handler); // 用户定义信号2
signal(SIGTERM, sig_handler); // 终止信号
signal(SIGBUS, sig_handler); // 总线错误
signal(SIGSEGV, sig_handler); // 段错误
signal(SIGABRT, sig_handler); // 中止信号
signal(SIGPIPE, SIG_IGN); // 忽略管道破裂信号
// 初始化输入设备监控
openKeyboard();
openUeventSocket();
// 创建客户端请求处理线程
while (0 != pthread_create(&process_request_thread, NULL, process_handler_request, NULL)) {
printf("[EventManager - Error] create thread for key_manager failed: %s,"
"Re-try after 500ms.\n", strerror(errno));
usleep(500 * 1000);
}
pthread_detach(process_request_thread); /* 忽略分离错误,可能内存泄漏 */
// 创建协议事件处理线程
while (0 != pthread_create(&process_protocol_thread, NULL, process_protocol_event, NULL)) {
printf("[EventManager - Error] create thread for key_manager failed: %s,"
"Re-try after 500ms.\n", strerror(errno));
usleep(500 * 1000);
}
pthread_detach(process_protocol_thread); /* 忽略分离错误,可能内存泄漏 */
// 进入主事件循环
eventLoop();
return 0;
}
这是一个典型的事件驱动架构,适合嵌入式系统和IoT设备的事件管理需求:
事件源层: 键盘输入 → 设备热插拔 → 网络事件 ↓ ↓ ↓ 事件采集层: handle_key() → handle_device() → process_protocol_event() ↓ ↓ ↓ 事件分发层: distribute_event() ↓ 所有注册客户端
Linux物联网设备测试框架架构
整体架构设计
测试框架架构树形结构: Test_Framework/ ├── 核心管理层 (Core Management) │ ├── 测试执行引擎 │ ├── 事件监控器 │ ├── 结果收集器 │ └── 报告生成器 ├── 设备模拟层 (Device Simulation) │ ├── 输入设备模拟 │ ├── 热插拔设备模拟 │ ├── 网络事件模拟 │ └── 协议事件模拟 ├── 事件处理层 (Event Processing) │ ├── 事件分发核心 (您的代码) │ ├── 事件过滤器 │ ├── 事件路由器 │ └── 事件持久化 └── 测试用例层 (Test Cases) ├── 功能测试 ├── 性能测试 ├── 可靠性测试 └── 兼容性测试 对比Linux事件管理树形结构: Linux_Event_Management/ ├── 事件源层 (Event Sources) │ ├── 内核uevent (netlink) │ ├── 输入子系统 (/dev/input/event*) │ └── 网络事件 (TCP sockets) ├── 事件收集层 (Event Collection) │ ├── select/poll/epoll多路复用 │ ├── 设备发现与监控 │ └── 连接管理 ├── 事件处理层 (Event Processing) │ ├── 事件解析与过滤 │ ├── 事件类型识别 │ └── 事件数据转换 └── 事件分发层 (Event Distribution) ├── 客户端注册管理 ├── 事件广播机制 └── 反馈处理
模块交互关系
测试程序 → 事件管理器 → 被测设备 ↓ ↓ ↓ 测试用例 → 事件监听 → 设备状态 ↓ ↓ ↓ 结果验证 → 事件验证 → 状态验证 ↓ ↓ ↓ 报告生成 → 日志记录 → 性能统计
测试主程序逐行注解
/**
* Linux物联网设备测试框架 - 主测试程序
* 设计模式:组合模式 + 观察者模式 + 策略模式
* 架构分析:集成测试框架,验证事件分发机制的正确性和性能
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <sys/time.h>
#include "event_interface.h"
#include "common.h"
// 测试统计结构体 - 设计模式:数据对象模式
typedef struct test_stats {
int total_events; // 总事件数
int key_events; // 按键事件数
int misc_events; // 杂项事件数
int success_count; // 成功事件数
int failure_count; // 失败事件数
long long total_latency; // 总延迟(微秒)
long long max_latency; // 最大延迟(微秒)
long long min_latency; // 最小延迟(微秒)
} test_stats;
// 全局测试状态 - 设计模式:单例模式(简化实现)
static test_stats g_stats = {0};
static volatile int g_test_running = 1; // 测试运行标志
static event_handler *g_event_handler = NULL; // 全局事件处理器
/**
* 获取当前时间戳(微秒) - 设计模式:工具方法模式
* 性能分析:系统调用开销,用于性能测量
* @return 当前时间戳(微秒)
*/
static long long get_timestamp_us() {
struct timeval tv;
gettimeofday(&tv, NULL);
return (long long)tv.tv_sec * 1000000LL + tv.tv_usec;
}
/**
* 事件回调函数 - 设计模式:观察者模式
* 性能分析:事件处理核心路径,应保持高效
* @param event 接收到的事件
* @param param 用户参数(时间戳)
*/
static void test_event_callback(tang_event event, void *param) {
long long receive_time = get_timestamp_us();
long long send_time = *(long long *)param;
long long latency = receive_time - send_time;
// 更新统计信息
g_stats.total_events++;
if (event.type == EVENT_KEY) {
g_stats.key_events++;
printf("[TEST] 收到按键事件: type=%d, code=%d, value=%d\n",
event.event.key.key.type,
event.event.key.key.code,
event.event.key.key.value);
} else if (event.type == EVENT_MISC) {
g_stats.misc_events++;
printf("[TEST] 收到杂项事件: name=%s, type=%s\n",
event.event.misc.name,
event.event.misc.type);
}
// 更新延迟统计
g_stats.total_latency += latency;
if (latency > g_stats.max_latency) g_stats.max_latency = latency;
if (g_stats.min_latency == 0 || latency < g_stats.min_latency)
g_stats.min_latency = latency;
g_stats.success_count++;
printf("[TEST] 事件延迟: %lld us\n", latency);
}
/**
* 模拟按键事件测试 - 设计模式:工厂方法模式
* 性能分析:模拟用户输入,测试事件响应能力
* @param key_code 按键代码
* @param value 按键值(0释放,1按下,2长按)
* @return 成功返回0,失败返回-1
*/
static int test_simulate_key_event(int key_code, int value) {
tang_event event;
long long send_time = get_timestamp_us();
// 构造按键事件
memset(&event, 0, sizeof(event));
event.type = EVENT_KEY;
event.event.key.type = EVENT_KEY;
event.event.key.key.type = EV_KEY; // 按键事件类型
event.event.key.key.code = key_code; // 按键代码
event.event.key.key.value = value; // 按键值
printf("[TEST] 发送按键事件: code=%d, value=%d\n", key_code, value);
// 发送事件并测量延迟
int ret = tang_event_send(event);
if (ret != 0) {
printf("[TEST-ERROR] 发送按键事件失败: %d\n", ret);
g_stats.failure_count++;
return -1;
}
return 0;
}
/**
* 模拟设备热插拔事件测试 - 设计模式:策略模式
* 性能分析:模拟设备状态变化,测试热插拔处理能力
* @param dev_name 设备名称
* @param event_type 事件类型(plugin/plugout)
* @return 成功返回0,失败返回-1
*/
static int test_simulate_device_event(const char *dev_name, const char *event_type) {
tang_event event;
long long send_time = get_timestamp_us();
// 构造设备事件
memset(&event, 0, sizeof(event));
event.type = EVENT_MISC;
strncpy(event.event.misc.name, dev_name, sizeof(event.event.misc.name) - 1);
strncpy(event.event.misc.type, event_type, sizeof(event.event.misc.type) - 1);
printf("[TEST] 发送设备事件: device=%s, action=%s\n", dev_name, event_type);
int ret = tang_event_send(event);
if (ret != 0) {
printf("[TEST-ERROR] 发送设备事件失败: %d\n", ret);
g_stats.failure_count++;
return -1;
}
return 0;
}
/**
* 性能压力测试 - 设计模式:模板方法模式
* 性能分析:高频率事件发送,测试系统承载能力
* @param event_count 事件数量
* @param interval_us 事件间隔(微秒)
*/
static void test_performance_stress(int event_count, int interval_us) {
printf("[TEST] 开始性能压力测试: 事件数=%d, 间隔=%dus\n",
event_count, interval_us);
long long start_time = get_timestamp_us();
for (int i = 0; i < event_count && g_test_running; i++) {
// 交替发送按键和设备事件
if (i % 2 == 0) {
test_simulate_key_event(KEY_A + (i % 10), i % 2);
} else {
const char *devices[] = {"tfcard", "udisk", "linein", "headset"};
const char *actions[] = {"plugin", "plugout"};
test_simulate_device_event(devices[i % 4], actions[i % 2]);
}
usleep(interval_us);
}
long long end_time = get_timestamp_us();
long long total_time = end_time - start_time;
printf("[TEST] 性能测试完成: 总时间=%lld us, 平均延迟=%lld us\n",
total_time, g_stats.total_latency / g_stats.total_events);
}
/**
* 功能正确性测试 - 设计模式:测试用例模式
* 性能分析:验证基本功能,确保系统正确性
*/
static void test_functional_correctness() {
printf("[TEST] === 开始功能正确性测试 ===\n");
// 测试各种按键事件
printf("[TEST] --- 按键事件测试 ---\n");
test_simulate_key_event(KEY_POWER, 1); // 电源键按下
usleep(100000);
test_simulate_key_event(KEY_POWER, 0); // 电源键释放
usleep(100000);
test_simulate_key_event(KEY_VOLUMEUP, 1); // 音量+按下
usleep(100000);
test_simulate_key_event(KEY_VOLUMEDOWN, 1); // 音量-按下
usleep(100000);
// 测试设备热插拔事件
printf("[TEST] --- 设备热插拔测试 ---\n");
test_simulate_device_event("tfcard", "plugin");
usleep(200000);
test_simulate_device_event("tfcard", "plugout");
usleep(200000);
test_simulate_device_event("udisk", "plugin");
usleep(200000);
test_simulate_device_event("udisk", "plugout");
usleep(200000);
test_simulate_device_event("headset", "plugin");
usleep(200000);
test_simulate_device_event("headset", "plugout");
usleep(200000);
printf("[TEST] === 功能正确性测试完成 ===\n");
}
/**
* 可靠性测试 - 设计模式:容错测试模式
* 性能分析:测试系统在异常情况下的表现
*/
static void test_reliability() {
printf("[TEST] === 开始可靠性测试 ===\n");
// 测试快速连续事件
printf("[TEST] --- 快速连续事件测试 ---\n");
for (int i = 0; i < 50 && g_test_running; i++) {
test_simulate_key_event(KEY_MENU + (i % 5), i % 2);
usleep(10000); // 10ms间隔
}
// 测试混合事件流
printf("[TEST] --- 混合事件流测试 ---\n");
for (int i = 0; i < 20 && g_test_running; i++) {
if (i % 3 == 0) {
test_simulate_key_event(KEY_HOME, 1);
} else if (i % 3 == 1) {
test_simulate_device_event("linein", "plugin");
} else {
test_simulate_device_event("spdif", "plugout");
}
usleep(50000); // 50ms间隔
}
printf("[TEST] === 可靠性测试完成 ===\n");
}
/**
* 打印测试统计信息 - 设计模式:报告生成模式
* 性能分析:统计汇总,不影响性能测试
*/
static void print_test_statistics() {
printf("\n[TEST] ==== 测试统计报告 ====\n");
printf("[TEST] 总事件数: %d\n", g_stats.total_events);
printf("[TEST] 按键事件: %d\n", g_stats.key_events);
printf("[TEST] 杂项事件: %d\n", g_stats.misc_events);
printf("[TEST] 成功事件: %d\n", g_stats.success_count);
printf("[TEST] 失败事件: %d\n", g_stats.failure_count);
if (g_stats.total_events > 0) {
printf("[TEST] 平均延迟: %lld us\n",
g_stats.total_latency / g_stats.total_events);
printf("[TEST] 最大延迟: %lld us\n", g_stats.max_latency);
printf("[TEST] 最小延迟: %lld us\n", g_stats.min_latency);
printf("[TEST] 成功率: %.2f%%\n",
(float)g_stats.success_count / g_stats.total_events * 100);
}
printf("[TEST] ==== 统计报告结束 ====\n");
}
/**
* 信号处理函数 - 设计模式:资源清理模式
* 性能分析:优雅退出,确保资源释放
* @param sig 信号编号
*/
static void test_signal_handler(int sig) {
printf("\n[TEST] 收到信号 %d,停止测试...\n", sig);
g_test_running = 0;
// 等待事件处理完成
sleep(1);
// 打印最终统计
print_test_statistics();
// 清理资源
if (g_event_handler) {
tang_event_handler_put(g_event_handler);
g_event_handler = NULL;
}
exit(0);
}
/**
* 测试初始化 - 设计模式:初始化模式
* 性能分析:一次性设置,确保测试环境准备就绪
* @return 成功返回0,失败返回-1
*/
static int test_initialize() {
printf("[TEST] 初始化测试环境...\n");
// 设置信号处理
signal(SIGINT, test_signal_handler);
signal(SIGTERM, test_signal_handler);
// 初始化统计信息
memset(&g_stats, 0, sizeof(g_stats));
g_stats.min_latency = 0;
// 创建事件处理器
long long *timestamp_ptr = malloc(sizeof(long long));
*timestamp_ptr = get_timestamp_us();
g_event_handler = tang_event_handler_get(test_event_callback, timestamp_ptr);
if (!g_event_handler) {
printf("[TEST-ERROR] 创建事件处理器失败\n");
free(timestamp_ptr);
return -1;
}
printf("[TEST] 测试环境初始化完成\n");
return 0;
}
/**
* 测试清理 - 设计模式:资源管理模式
* 性能分析:确保资源正确释放,避免内存泄漏
*/
static void test_cleanup() {
printf("[TEST] 清理测试资源...\n");
if (g_event_handler) {
// 获取回调参数并释放
void *param = g_event_handler->param;
tang_event_handler_put(g_event_handler);
g_event_handler = NULL;
if (param) {
free(param);
}
}
printf("[TEST] 测试资源清理完成\n");
}
/**
* 主测试函数 - 设计模式:组合模式
* 性能分析:测试流程控制,协调各个测试模块
* @return 程序退出码
*/
int main(int argc, char *argv[]) {
printf("=========================================\n");
printf(" Linux物联网设备事件框架测试程序\n");
printf("=========================================\n");
// 初始化测试环境
if (test_initialize() != 0) {
printf("[TEST-ERROR] 测试初始化失败\n");
return -1;
}
// 等待事件管理器启动
printf("[TEST] 等待事件管理器启动...\n");
sleep(2);
// 执行测试套件
printf("[TEST] 开始执行测试套件...\n");
// 1. 功能正确性测试
test_functional_correctness();
if (!g_test_running) goto cleanup;
// 2. 可靠性测试
test_reliability();
if (!g_test_running) goto cleanup;
// 3. 性能压力测试
printf("[TEST] === 开始性能压力测试 ===\n");
test_performance_stress(100, 20000); // 100个事件,20ms间隔
if (!g_test_running) goto cleanup;
// 4. 极限性能测试
printf("[TEST] === 开始极限性能测试 ===\n");
test_performance_stress(50, 1000); // 50个事件,1ms间隔
cleanup:
// 打印最终统计
print_test_statistics();
// 清理资源
test_cleanup();
printf("[TEST] 测试程序退出\n");
return 0;
}
关键测试函数树形结构对比分析
测试框架函数树
test_main ├── 初始化层 │ ├── test_initialize() # 测试环境初始化 │ ├── test_event_callback() # 事件回调处理 │ └── test_signal_handler() # 信号处理 ├── 测试用例层 │ ├── test_functional_correctness() # 功能正确性测试 │ ├── test_reliability() # 可靠性测试 │ └── test_performance_stress() # 性能压力测试 ├── 模拟事件层 │ ├── test_simulate_key_event() # 模拟按键事件 │ └── test_simulate_device_event() # 模拟设备事件 └── 结果分析层 ├── print_test_statistics() # 统计报告生成 └── test_cleanup() # 资源清理
与Linux事件管理树形结构对比
对比关系: 测试框架层 ↔ 事件管理层 ----------- ----------- 测试初始化 ↔ 事件管理器启动 测试用例执行 ↔ 事件监听循环 事件模拟发送 ↔ 事件产生源 事件回调验证 ↔ 事件分发处理 统计结果分析 ↔ 事件日志记录 交互流程: 测试程序 → 发送模拟事件 → 事件管理器 → 分发事件 → 测试程序回调 ↓ ↓ ←-------------- 验证事件 ----------------→ ↓ ↓ 性能统计 ←----------------------------------- 延迟测量
模块管理机制
1. 事件流管理
// 测试事件流 测试用例 → 事件模拟 → 网络发送 → 事件管理器 → 网络接收 → 回调验证 ↓ ↓ ↓ ↓ ↓ ↓ 控制 数据构造 传输协议 核心处理 传输协议 结果验证
2. 资源管理
// 资源生命周期 初始化 → 测试执行 → 结果收集 → 资源释放 ↓ ↓ ↓ ↓ 内存分配 事件发送 统计计算 内存释放 连接建立 事件接收 延迟测量 连接关闭
3. 性能管理
// 性能监控链 事件发送时间戳 → 网络传输 → 事件处理 → 网络传输 → 回调接收时间戳 ↓ ↓ ↓ ↓ ↓ T1 Δ1 Δ2 Δ3 T2 ↓ ↓ 总延迟 = T2 - T1 处理延迟 = Δ2
这个测试框架完整地验证了事件分发机制的各个方面,包括功能正确性、可靠性、性能表现等,为Linux物联网设备程序提供了全面的测试保障。
更多推荐
所有评论(0)