RDP协议项目中需要获取键鼠事件回传给server,Linux下键鼠设备为 /dev/input/event*
,直接读取即可。使用非阻塞IO肯定是不优雅的,一定要使用阻塞的IO,但是键鼠设备通常至少有3个,难道要开3个线程吗?epoll是Linux系统IO多路复用的接口,可以同时监视多个文件描述符状态,更详细的资料敲命令man 7 epoll
。
- epoll 相关接口
//所需头文件
#include <sys/epoll.h>
//主要结构体
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events;
epoll_data_t data;
};
//接口函数
int epoll_create (int size);
int epoll_ctl (int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait (int epfd, struct epoll_event *events, int maxevents, int timeout);
int close (int fd);
-
epoll_create ()
:在内核中创建一个 epoll 实例并返回一个文件描述符。参数size
被忽略,但是要大于0。 -
epoll_ctl
:用于添加、删除、修改 被监视的文件描述符。op
为下列值:
EPOLL_CTL_ADD 添加一个文件描述符到epoll实例。
EPOLL_CTL_MOD 改变一个文件描述符的被监视事件。
EPOLL_CTL_DEL 删除一个被监视的文件描述符。
event->events
是要监视的事件,如下:
EPOLLIN 文件描述符可读。
EPOLLOUT 文件描述符可写
EPOLLRDHUP 对方断开连接
EPOLLPRI 带外数据
EPOLLERR 文件描述符发生错误
EPOLLHUP 文件描述符被挂断
EPOLLET 设为边缘触发模式
EPOLLONESHOT 只监视一次
event->data
是用户私有数据,被监视的文件描述符有事件时,该值被原封不动地复制回来。
-
epoll_wait()
:等待文件描述符事件,maxevents
是buffer个数,timeout
是超时时间,单位毫秒。 -
close()
:关闭 epoll 实例。
epoll的几个性质:
1.将epoll自己的文件描述符添加到自己中无效;
2.epoll又可以监视别的epoll文件描述符;
3.被监视的文件描述符被关闭后,也会自动从epoll上移除;
4.epoll上被监视的文件描述符都被关闭后,内核会释放epoll实例。
5.多个epoll实例可以监视相同的文件描述符,并都能收到文件描述符的事件;代码举例:
//==============================================================================
// Copyright (C) 2019 王小康. All rights reserved.
//
// 作者: 王小康
// 描述: epoll用法举例
// 日期: 2019-04-30
//
//==============================================================================
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <dirent.h>
#include <linux/input.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/epoll.h>
static void function_key(short code, short flag){
printf("\e[31m key : %d %d \e[0m\n", code, flag);
}
static void function_move(short x, short y){
printf("\e[32m move : %d %d \e[0m\n", x, y);
}
static void function_wheel(short flag){
printf("\e[33m wheel: %d\e[0m\n", flag);
}
////////////////////////////////////////////////////////////////////////////////
static void readInputEvent(int fd){
struct input_event inputEvent[32];
int length = read(fd, inputEvent, sizeof(inputEvent));
if(length <= 0) return;
length /= sizeof(struct input_event);
int i;
short x = 0, y = 0;
for(i=0; i<length; i++){
if(inputEvent[i].type == EV_KEY){
function_key(inputEvent[i].code, inputEvent[i].value ? 1 : 0);
}
else if(inputEvent[i].type == EV_REL){
if(inputEvent[i].code == REL_X){
x += inputEvent[i].value;
}
else if(inputEvent[i].code == REL_Y){
y += inputEvent[i].value;
}
else if(inputEvent[i].code == REL_WHEEL){
function_wheel(inputEvent[i].value > 0 ? 1 : 0);
}
}
}
if(x || y){
function_move(x, y);
}
}
static int openInputEvent(int epollFd, char *name){
char nameBuffer[32];
snprintf(nameBuffer, 32, "/dev/input/%s", name);
int fd = open(nameBuffer, O_RDWR);
printf("open(%s) = %d\n", nameBuffer, fd);
if(fd >= 0){
struct epoll_event epollEvent;
epollEvent.events = EPOLLIN | EPOLLERR; //监视可读事件和错误事件
epollEvent.data.fd = fd; //文件描述符放入私有数据中
int err = epoll_ctl(epollFd, EPOLL_CTL_ADD, fd, &epollEvent);
if(err < 0){
close(fd);
return 0;
}
else{
return 1;
}
}
else{
return 0;
}
}
int main(int argc, char* argv[]){
//创建epoll实例
int epollFd = epoll_create(8);
if(epollFd < 0) return -1;
//打开目录 /dev/input 下所有 event*,并添加到epoll。
int n = 0;
struct dirent *ent;
DIR *dir = opendir("/dev/input");
if(dir == NULL){
close(epollFd);
return -2;
}
while((ent = readdir(dir))){
if(memcmp(ent->d_name, "event", 5) == 0){
n += openInputEvent(epollFd, ent->d_name);
}
}
closedir(dir);
//检查添加了几个文件描述符到epoll
if(n < 1){
close(epollFd);
return -3;
}
//等待可读的文件描述符,200次后退出。
int err;
struct epoll_event epollEvent;
for(n=0; n<200; n++){
epollEvent.events = 0;
epollEvent.data.fd = 0;
err = epoll_wait(epollFd, &epollEvent, 1, 1000);
if(err < 0) break;
if(err == 0) continue;
if(epollEvent.events & EPOLLIN){
readInputEvent(epollEvent.data.fd);
}
if(epollEvent.events & EPOLLERR){
close(epollEvent.data.fd);
}
}
close(epollFd);
return 0;
}
-
运行效果如图: