Linux驱动---按键
一、Input子系统
1.1、简介
在前面的LED驱动文章中,我们知道Linux为了方便GPIO操作设计了GPIO子系统。那么对于键盘、鼠标、触摸屏、游戏控制器这一类的输入设备呢?为了给这一类的输入设备提供统一的接口和管理,Linux设计了Input子系统,设计该系统的主要目的就是将输入设备驱动中的共性部分提取出来,形成一个通用的框架,开发者只需关注差异化的部分。这样,不仅降低了驱动开发的难度,也提高了驱动的通用性和兼容性。
Input子系统为各种输入设备提供了统一的接口,将输入事件转化为统一的事件格式,并通过input接口传递给应用空间程序,应用程序可以通过这些统一的接口来访问和操作输入设备,而不需要关心设备的具体实现。

1.2、Input子系统构成
输入子系统主要由三部分构成:
(1)设备驱动层(struct input_dev):通过获取设备树中硬件的信息,对硬件各寄存器的读写访问和将底层硬件的状态变化转换为标准的输入事件,将相应事件上报。
(2)核心层:用于将设备驱动层和事件处理层进行匹配,由内核完成。
(3)事件处理层(struct input_handler):将核心层生成的输入事件传递给系统的高层应用,并确保这些事件被正确处理。
我们本篇文章属于驱动开发,所以主要整理设备驱动层。
1.3、input_dev结构体
input_dev 是 Linux Input 子系统中用于描述输入设备的核心结构体,它的定义如下:
点击查看代码
struct input_dev {
const char *name; // 设备名称,例如 "Keyboard" 或 "Mouse"
const char *phys; // 设备在系统中的物理路径,例如 "usb-0000:00:14.0-1/input0"
const char *uniq; // 设备的唯一标识符,通常用于匹配特定硬件
struct input_id id; // 包含设备识别信息的结构体(例如供应商ID、产品ID、版本号)
// 属性位图,用于表示设备支持的属性类型
unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
// 事件位图,用于表示设备支持的事件类型
unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
// 键位图,用于表示设备支持的按键类型
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
// 相对位图,用于表示设备支持的相对轴事件。 例如鼠标
unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
// 绝对位图,用于表示设备支持的绝对轴事件,例如触摸屏
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
// 杂项位图,用于表示设备支持的其他事件类型
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
// 指示灯位图,用于表示设备支持的LED灯类型
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
// 声音位图,用于表示设备支持的声音类型
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
// 力反馈位图,用于表示设备支持的力反馈事件
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
// 开关位图,用于表示设备支持的开关类型
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
unsigned int hint_events_per_packet; // 每个数据包中的建议事件数量
unsigned int keycodemax; // 最大按键码数量
unsigned int keycodesize; // 每个按键码的大小
void *keycode; // 指向按键码数据的指针
// 设置按键码的函数指针
int (*setkeycode)(struct input_dev *dev,
const struct input_keymap_entry *ke,
unsigned int *old_keycode);
// 获取按键码的函数指针
int (*getkeycode)(struct input_dev *dev,
struct input_keymap_entry *ke);
struct ff_device *ff; // 力反馈设备的指针
unsigned int repeat_key; // 重复按键
struct timer_list timer; // 用于处理重复按键的定时器
int rep[REP_CNT]; // 用于存储重复延迟和重复率
struct input_mt *mt; // 多点触控相关信息的指针
struct input_absinfo *absinfo; // 绝对轴相关信息的指针
// 当前设备状态的位图(按键、指示灯、声音、开关)
unsigned long key[BITS_TO_LONGS(KEY_CNT)];
unsigned long led[BITS_TO_LONGS(LED_CNT)];
unsigned long snd[BITS_TO_LONGS(SND_CNT)];
unsigned long sw[BITS_TO_LONGS(SW_CNT)];
// 打开设备的函数指针
int (*open)(struct input_dev *dev);
// 关闭设备的函数指针
void (*close)(struct input_dev *dev);
// 刷新设备的函数指针
int (*flush)(struct input_dev *dev, struct file *file);
// 处理事件的函数指针
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);
struct input_handle __rcu *grab; // 用于处理独占设备的指针
spinlock_t event_lock; // 用于保护事件处理的自旋锁
struct mutex mutex; // 设备访问的互斥锁
unsigned int users; // 使用该设备的用户数量
bool going_away; // 标志设备是否正在关闭
struct device dev; // 设备的基础信息
struct list_head h_list; // 处理句柄的链表
struct list_head node; // 设备的链表节点
unsigned int num_vals; // 当前输入值的数量
unsigned int max_vals; // 最大输入值的数量
struct input_value *vals; // 输入值数组的指针
bool devres_managed; // 标志设备资源是否由设备资源管理器管理
};
其中,evbit为事件类型,常见的有以下几种:
EV_KEY 键盘按键事件
EV_REL 鼠标事件
EV_ABS 触摸屏事件
二、输入设备驱动开发流程
2.1、分配和初始化输入设备
输入设备驱动首先需要分配一个input_de结构体,并设置它的基本属性,如设备名称、事件类型、支持的按键等。
struct input_dev *input_device;
input_device = input_allocate_device();
if (!input_device) {
pr_err("Failed to allocate input device\n");
return -ENOMEM;
}
input_device->name = "my_key_device";
input_device->evbit[0] = BIT_MASK(EV_KEY); // 设置支持按键事件
EV_KEY 是一个枚举值,定义在 <linux/input.h> 中,通常值为 1。BIT_MASK(EV_KEY) 展开后相当于 1 << EV_KEY,即 1 << 1,结果是 0x02。因此,最后这行代码相当于:
input_device->evbit[0] = 0x02;
也可以使用 __set_bit 宏来设置位图,它的用法如下:
__set_bit(EV_KEY, input_device->evbit);
2.2、注册设备
设置好设备的属性后,调用input_register_device()函数来注册输入设备,使其可以开始接收并处理事件。
ret = input_register_device(input_device);
if (ret) {
pr_err("Failed to register input device\n");
return ret;
}
2.3、事件上报
输入设备需要在状态发生变化时,通过input_report_key()向Input子系统报告事件。
input_report_key(input_device, KEY_ENTER, 1); // 报告按下事件
input_sync(input_device); // 同步事件
该函数将按键按下的事件报告给系统,用户空间应用程序可以通过evdev等接口读取到这些事件。在驱动中,我们往往需要监听GPIO引脚上按键的状态变化,这通常需要通过硬件中断(IRQ)来触发。按键状态的改变将会触发相应的中断处理函数,在中断处理函数中再通过input_report_key()来报告事件。
2.4、释放和注销设备
在驱动退出时,需释放资源,并通过input_unregister_device()注销输入设备。
input_unregister_device(input_device);
input_free_device(input_device);
三、事件同步与事件队列
在 2.3事件上报 时,我们调用了input_sync函数,为什么要进行该操作呢?这就要聊一下事件同步与事件队列了。
- 事件同步:在报告完事件后,驱动需要调用
input_sync来同步事件,确保事件被正确地传递到Input子系统中。 - 事件队列:输入子系统通过事件队列的方式管理输入事件,驱动程序负责将事件传递到队列中,用户空间程序通过
evdev等接口从队列中读取事件。
四、按键消抖
4.1、按键抖动
按键通常是由两个金属点组成,当按键按下或释放时,这些触点会发生接触或断开。由于物理原因,触点在短时间内可能会发生多次闭合和断开,而不是单次稳定地触发,这种现象称为“抖动”。
如果没有进行去抖动处理,一个按键的按下或释放被多次记录,硬件中断可能会频繁触发,增加系统的处理负担。
通常可以通过软件或硬件方法消除按键抖动。硬件去抖动通常是在按键硬件的设计中加以改进,例如使用RC滤波器、专用的去抖动IC或使用晶振稳定信号,这些方法能在硬件层面消除抖动,无需依赖软件。
接下来,将主要为大家介绍两种软件去抖动的方法。
4.2、延时去抖动
此方法的思路是等待按键接触或断开后的一段时间(例如10~50ms),在检测一次按键状态,已确定状态变化是否稳定。此方法实现简单,但是可能导致响应时间较长,不能非常精准的去抖动。
#define DEBOUNCE_DELAY_MS 20 // 延时 20ms
static irqreturn_t gpio_key_irq(int irq, void *arg)
{
struct keys_desc *key = arg;
static unsigned long last_irq_time = 0;
unsigned long now = jiffies;
// 检查抖动延迟
if (time_after(now, last_irq_time + msecs_to_jiffies(DEBOUNCE_DELAY_MS))) {
int value = gpio_get_value(key->gpio);
if (value == 0) {
input_report_key(input_device, key->key_code, 1); // 按下事件
} else {
input_report_key(input_device, key->key_code, 0); // 释放事件
}
input_sync(input_device);
last_irq_time = now;
}
return IRQ_HANDLED;
}
4.3、轮询去抖动
这种方法是对按键状态进行多次连续检查,只有在按键状态一致时才认为状态已稳定。通常在硬件中断中进行,读取按键状态并检查是否稳定。这种方法可以更可靠地过滤抖动,适合处理快速的按键状态变化。但是它增加了额外的处理复杂度,需要做更多的状态检测和计数。
#define DEBOUNCE_COUNT 5 // 检查连续的 5 次状态
static irqreturn_t gpio_key_irq(int irq, void *arg)
{
struct keys_desc *key = arg;
static int stable_state = -1;
static int count = 0;
int value = gpio_get_value(key->gpio);
if (stable_state == value) {
count++;
if (count > DEBOUNCE_COUNT) {
// 状态稳定,报告事件
if (value == 0) {
input_report_key(input_device, key->key_code, 1); // 按下事件
} else {
input_report_key(input_device, key->key_code, 0); // 释放事件
}
input_sync(input_device);
count = 0;
}
} else {
stable_state = value;
count = 0;
}
return IRQ_HANDLED;
}
五、实现按键驱动
5.1、硬件原理图
下面是按键的原理图,从中我们可以看到该按键连接到了NAND_nCE1这个引脚上,通过设备树的头文件我们可以查到它使用MX6UL_PAD_NAND_CE1_B__GPIO4_IO14这个引脚。如果没有按下按键时,左侧的上拉电阻R25将该GPIO引脚拉成高电平;而一旦按键按下,则该引脚与GND导通变成低电平。由此可见,我们应该将GPIO4_14中断设置成下降沿触发。

5.2、设备树修改
接下来,我们需要修改DTS文件中关于按键的配置,因为BSP默认已经使能了该设备和Linux内核自带的按键驱动,这里只需将compatible修改成我们自己即将编写的按键驱动“my,keys”即可,别的都不需要修改。
keys {
compatible = "my,keys";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_keys>;
autorepeat;
status = "okay";
key_user {
lable = "key_user";
gpios = <&gpio4 14 GPIO_ACTIVE_LOW>;
linux,code = <KEY_ENTER>;
};
};
... ...
&iomuxc {
pinctrl-names = "default";
... ...
pinctrl_gpio_keys: gpio-keys {
fsl,pins = <
MX6UL_PAD_NAND_CE1_B__GPIO4_IO14 0x17059 /* gpio key */
>;
};
... ...
};
我们的按键使用的是GPIO4_14引脚,并且低电平有效GPIO_ACTIVE_LOW。我们设置该按键的键值linux.code为回车KEY_ENTER,按下该按键即相当于按下了回车。
修改完设备树之后,我们重新编译成.dtb文件,Makefile文件如下:
点击查看代码
ARCH ?= arm
KERNAL_DIR ?= ${HOME}/igkboard-imx6ull/bsp/kernel/linux-imx
CPP_CFLAGS=-Wp,-MD,.x.pre.tmp -nostdinc -undef -D__DTS__ -x assembler-with-cpp
CPP_CFLAGS+= -I ${KERNAL_DIR}/arch/${ARCH}/boot/dts -I ${KERNAL_DIR}/include/
DTC=${KERNAL_DIR}/scripts/dtc/dtc
DTC_FLAGS=-q -@ -I dts -O dtb
DTS_NAME=igkboard-imx6ull
all:
@cpp ${CPP_CFLAGS} ${DTS_NAME}.dts -o .${DTS_NAME}.dts.tmp
${DTC} ${DTC_FLAGS} .${DTS_NAME}.dts.tmp -o ${DTS_NAME}.dtb
@rm -f .*.tmp
decompile:
${DTC} -q -I dtb -O dts ${DTS_NAME}.dtb -o decompile.dts
clean:
rm -f *.dtb decompile.dts
5.3、编写按键驱动代码
接下来我们通过Linux内核定时器实现按键消抖,编写代码如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
#include <linux/jiffies.h>
#include <linux/delay.h>
struct keys_desc {
const char *lable; /* Key name */
unsigned int key_code; /* Key code */
int gpio; /* GPIO number */
unsigned int irq; /* IRQ number */
struct timer_list timer; /* Timer for debounce */
int last_value;/* Last key value */
};
struct key_priv {
int nkeys; /* number of keys */
struct keys_desc *keys; /* keys array */
};
struct input_dev *input_device;
struct key_priv *priv;
/* Timer callback function for debounce */
static void debounce_timer_func(struct timer_list *t)
{
struct keys_desc *key = from_timer(key, t, timer);
int value = gpio_get_value(key->gpio);
if (value != key->last_value) {
key->last_value = value;
if (value == 0) {
input_report_key(input_device, key->key_code, 1); /* Key press event */
} else {
input_report_key(input_device, key->key_code, 0); /* Key release event */
}
input_sync(input_device);
}
}
/* GPIO IRQ handler */
static irqreturn_t gpio_key_irq(int irq, void *arg)
{
struct keys_desc *key = arg;
/* start debounce timer(20ms) to delay event processing */
mod_timer(&key->timer, jiffies + msecs_to_jiffies(20));
return IRQ_HANDLED;
}
static int key_probe(struct platform_device *pdev) {
struct device *dev = &pdev->dev;
struct device_node *np = pdev->dev.of_node;
struct device_node *key_node;
int ret, i=0;
/* allocate memory for private data structure */
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
/* parser the number of keys from the device tree */
priv->nkeys = device_get_child_node_count(dev);
if ( priv->nkeys < 1) {
dev_err(dev, "Failed to read keys gpio from device tree\n");
return -EINVAL;
}
dev_info(dev, "gpio keys driver probe for %d keys from device tree\n", priv->nkeys);
/* allocate memory for all the keys */
priv->keys = devm_kzalloc(dev, priv->nkeys*sizeof(*priv->keys), GFP_KERNEL);
if (!priv->keys )
return -ENOMEM;
/* traval all the keys child node */
for_each_child_of_node(np, key_node) {
/* read lable information */
if (of_property_read_string(key_node, "lable", &priv->keys[i].lable)) {
dev_err(dev, "Failed to read lable from key node\n");
continue;
};
/* read gpio information */
priv->keys[i].gpio = of_get_named_gpio(key_node, "gpios", 0);
if( priv->keys[i].gpio < 0 ) {
dev_err(dev, "Failed to read lable from key node\n");
continue;
}
/* read key code value */
if (of_property_read_u32(key_node, "linux,code", &priv->keys[i].key_code)) {
dev_err(dev, "Failed to read linux,code for key %s\n", priv->keys[i].lable);
continue;
}
/* request gpio for this key */
ret = devm_gpio_request(dev, priv->keys[i].gpio, priv->keys[i].lable);
if (ret) {
dev_err(dev, "Failed to request GPIO for key %s\n", priv->keys[i].lable);
continue;
}
/* request interrupt for this key */
priv->keys[i].irq = gpio_to_irq(priv->keys[i].gpio);
ret = devm_request_irq(dev, priv->keys[i].irq, gpio_key_irq, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, priv->keys[i].lable, &priv->keys[i]);
if (ret) {
dev_err(dev, "Failed to request IRQ for key %s\n", priv->keys[i].lable);
continue;
}
/* initialize debounce timer */
timer_setup(&priv->keys[i].timer, debounce_timer_func, 0);
priv->keys[i].last_value = gpio_get_value(priv->keys[i].gpio);
/* increase to next key */
i++;
}
priv->nkeys = i; /* update valid keys number */
/* alloc input device */
input_device = devm_input_allocate_device(dev);
if (!input_device) {
dev_err(dev, "failed to allocate input device\n");
return -ENOMEM;
}
/* set input deivce information */
input_device->name = "mykeys";
input_device->evbit[0] = BIT_MASK(EV_KEY); /* key event */
for ( i=0; i<priv->nkeys; i++) {
set_bit(priv->keys[i].key_code, input_device->keybit);
}
/* register input device */
ret = input_register_device(input_device);
if (ret) {
pr_err("Failed to register input device\n");
return ret;
}
return 0;
}
static int key_remove(struct platform_device *pdev)
{
input_unregister_device(input_device);
dev_info(&pdev->dev, "gpio keys driver removed.\n");
return 0;
}
static const struct of_device_id key_of_match[] = {
{ .compatible = "my,keys", },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, key_of_match);
static struct platform_driver key_driver = {
.probe = key_probe,
.remove = key_remove,
.driver = {
.name = "keys",
.of_match_table = key_of_match,
},
};
module_platform_driver(key_driver);
MODULE_LICENSE("GPL");
在key_probe()中,我们为每个按键初始化了一个定时器。当GPIO引脚发生变化时,gpio_key_irq函数会被调用,触发定时器的启动。定时器的延时设为50毫秒,到达时间后,定时器回调函数debounce_timer_func()处理按键消抖并报告按键事件。在key_remove中,我们删除了所有按键的定时器,以确保在驱动移除时不会发生定时器回调。
接下来,进行编译,Makefile文件如下:
点击查看代码
ARCH ?= arm
CROSS_COMPILE ?= /opt/gcc-aarch32-10.3-2021.07/bin/arm-none-linux-gnueabihf-
KERNAL_DIR ?= ~/igkboard-imx6ull/bsp/kernel/linux-imx/
PWD :=$(shell pwd)
obj-m += keys.o
modules:
$(MAKE) ARCH=${ARCH} CROSS_COMPILE=${CROSS_COMPILE} -C $(KERNAL_DIR) M=$(PWD) modules
@make clear
clear:
@rm -f *.o *.cmd *.mod *.mod.c
@rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f
@rm -f .*ko.cmd .*.o.cmd .*.o.d
@rm -f *.unsigned
clean:
@rm -f *.ko
make
5.4、按键驱动测试
首先,在开发板上更新我们的设备树文件。
mount /dev/mmcblk1p1 /media/
通过rz、sz或scp将设备树文件下载至/media目录下
sync && reboot
再将按键驱动文件拷贝到开发板上。并通过insmod安装按键驱动,输入设备的设备文件都在/dev/input路径下。
insmod keys.ko
ls /dev/input/
by-path event0 event1
接下来使用 evtest 命令测试我们编写的驱动如下:

Linux驱动---按键的更多相关文章
- Linux驱动之定时器在按键去抖中的应用
机械按键在按下的过程中会出现抖动的情况,如下图,这样就会导致本来按下一次按键的过程会出现多次中断,导致判断出错.在按键驱动程序中我们可以这么做: 在按键驱动程序中我们可以这么做来取消按键抖动的影响:当 ...
- Linux驱动之按键驱动编写(中断方式)
在Linux驱动之按键驱动编写(查询方式)已经写了一个查询方式的按键驱动,但是查询方式太占用CPU,接下来利用中断方式编写一个驱动程序,使得CPU占有率降低,在按键空闲时调用read系统调用的进程可以 ...
- Linux驱动之按键驱动编写(查询方式)
在Linux驱动之LED驱动编写已经详细介绍了一个驱动的编写过程,接着来写一个按键驱动程序,主要是在file_operations结构中添加了一个read函数.还是分以下几步说明 1.查看原理图,确定 ...
- linux驱动程序设计的硬件基础,王明学learn
linux驱动程序设计的硬件基础(一) 本章讲总结学习linux设备程序设计的硬件基础. 一.处理器 1.1通用处理器 通用处理器(GPP)并不针对特定的应用领域进行体系结构和指令集的优化,它们具有一 ...
- Linux驱动设计—— 中断与时钟
中断和时钟技术可以提升驱动程序的效率 中断 中断在Linux中的实现 通常情况下,一个驱动程序只需要申请中断,并添加中断处理函数就可以了,中断的到达和中断函数的调用都是内核实现框架完成的.所以程序员只 ...
- Linux驱动之触摸屏程序编写
本篇博客分以下几部分讲解 1.介绍电阻式触摸屏的原理 2.介绍触摸屏驱动的框架(输入子系统) 3.介绍程序用到的结构体 4.介绍程序用到的函数 5.编写程序 6.测试程序 1.介绍电阻式触摸屏的原理 ...
- zynq linux驱动之PL-PS中断【转】
转自:https://blog.csdn.net/h244259402/article/details/83993524 PC:Windows 10 虚拟机:ubuntu 16.04 vivado:2 ...
- Linux驱动之一个简单的输入子系统程序编写
的在Linux驱动之输入子系统简析已经分析过了输入子系统的构成,它是由设备层.核心层.事件层共同组成的.其中核心层提供一些设备层与事件层公用的函数,比如说注册函数.反注册函数.事件到来的处理函数等等: ...
- Linux驱动之poll机制的理解与简单使用
之前在Linux驱动之按键驱动编写(中断方式)中编写的驱动程序,如果没有按键按下.read函数是永远没有返回值的,现在想要做到即使没有按键按下,在一定时间之后也会有返回值.要做到这种功能,可以使用po ...
- linux驱动之中断方式获取键值
linux驱动之中断方式获取键值 ----------------------------------------------------------------------------------- ...
随机推荐
- Blazor 组件库 BootstrapBlazor 中Markdown组件介绍
组件介绍 Markdown组件是tui.editor的封装,所以所有内容均基于tui.editor. 默认状态下样子如下所示: 其代码如下: <Markdown Language="@ ...
- 【FAQ】HarmonyOS SDK 闭源开放能力 —Push Kit(6)
1.问题描述: 推送通知到手机,怎么配置拉起应用指定的页面? 解决方案: 1.如果点击通知栏打开默认Ability的话, actionType可以设置为0, 同时可以在.clickAction.dat ...
- 开源 - Ideal库 - Excel帮助类,TableHelper实现(二)
书接上回,我们今天开始实现对象集合与DataTable的相互转换. 01.接口设计 上文中已经详细讲解了整体设计思路以及大致设计了需要哪些方法.下面我们先针对上文设计思想确定对外提供的接口.具体接口如 ...
- 【返回值】定义泛型JSON
/** * 定义统一的Json结构 * 由于封装的Json数据的类型不确定,所以在定义统一的json结构时,我们需要用到泛型. * 统一的json结构中属性包括:数据.状态码.提示信息即可. * 构造 ...
- SFE人才需要具备哪些能力
SFE(销售队伍效力)人才在企业中扮演着至关重要的角色,他们需要具备一系列的能力来确保销售队伍的高效运作和业绩提升.关于SFE的角色和能力,可以从业务理解.数据洞察.向上管理以及效率提升等几个方面来通 ...
- VLC web(http)控制 (4) 服务器文件获取
通过链接 http://127.0.0.1:8080/requests/browse.xml?uri=file%3A%2F%2F~ 可以获取服务器默认目录所有文件. 其中file%3A%2F%2F~是 ...
- VS Code 变身小霸王游戏机!
在韩老师的<Visual Studio Code 权威指南>一书中,我向大家推荐了许多好用的插件,其中也不乏许多摸鱼插件,刷知乎.炒股票.看电影.听音乐.追番.看小说,一应俱全. 今天,就 ...
- 2.mysql授权认证
权限系统介绍 ● 什么是权限系统 权限系统是授予来自某个主机的某个用户可以查询.插入.修改.删除等数据库操作的权限 不能明确的指定拒接某个用户的连接 权限控制(授权与收回)的执行语句包括 create ...
- Qt开源作品39-日志输出增强版V2022
一.前言 之前已经开源过基础版本,近期根据客户需求和自己的项目需求,提炼出通用需求部分,对整个日志重定向输出类重新规划和重写代码. 用Qt这个一站式超大型GUI超市做开发已经十二年了,陆陆续续开发过至 ...
- 网页端IM通信技术快速入门:短轮询、长轮询、SSE、WebSocket
本文来自"糊糊糊糊糊了"的分享,原题<实时消息推送整理>,有优化和改动. 1.写在前面 对Web端即时通讯技术熟悉的开发者来说,我们回顾网页端IM的底层通信技术,从短轮 ...