AWTK 嵌入式Linux平台实现多点触控缩放旋转以及触点丢点问题解决
前言
最近涉及海图的功能交互,多点触摸又开始找麻烦。
在PC/Web平台awtk是通过底层的sdl2库来实现多点触摸,但是在嵌入式Linux平台,可能是考虑到性能原因,awtk并没有采用sdl库来做事件处理,而是自己实现一个awtk-linux-fb来做适配,多点触摸的相关逻辑必须自己适配。
去年11月的时候,匆忙赶工,自己适配了一份tslib的代码,思路是循环线程内读取触点数据后,直接调用awtk自带的一个multi_gesture.inc文件去计算距离和旋转角度,在应用层注册EVT_MULTI_GESTURE事件。
static ret_t tslib_dispatch_one_event(run_info_t* info) {#ifdef MT_TOUCH_ENABLEint ret = -1;if (info->ts != NULL) {ret = ts_read_mt(info->ts, info->samp_mt, info->max_slots, info->read_samples);}uint8_t event_number = 0, down_number = 0;...int max_slots = info->max_slots;// note: down, up only trigger once(ET), move will be keep trigger(LT)for (int i = 0; i < max_slots; i++) {// printf(YELLOW "sample %d - %ld.%06ld -" RESET " (slot %d) %6d %6d %6d\n",// 0,// info->samp_mt[0][i].tv.tv_sec,// info->samp_mt[0][i].tv.tv_usec,// info->samp_mt[0][i].slot,// info->samp_mt[0][i].x,// info->samp_mt[0][i].y,// info->samp_mt[0][i].pressure);if (!(info->samp_mt[0][i].valid & TSLIB_MT_VALID)){//for boards that no clear pressure when slot is invaild, need to do it manuallyif(!s_points[i].type == EVT_MULTI_TOUCH_DOWN){info->samp_mt[0][i].pressure = 0;}continue;}event_number++;}for (int i = 0; i < CT_MAX_TOUCH; i++){s_points[i].touch_id = 0;s_points[i].finger_id = i;if(info->samp_mt[0][i].pressure > 0) {down_number++;s_points[i].type = (s_points[i].x == info->samp_mt[0][i].x && s_points[i].y == info->samp_mt[0][i].y) ? EVT_MULTI_TOUCH_DOWN : EVT_MULTI_TOUCH_MOVE;s_points[i].x = info->samp_mt[0][i].x;s_points[i].y = info->samp_mt[0][i].y;}else{s_points[i].type = EVT_MULTI_TOUCH_UP;s_points[i].x = 0;s_points[i].y = 0;} }if(event_number == 1 && (info->last_down_number == 0 || info->last_down_number == 1)){//单点触摸...tslib_dispatch(info);}else{//多点触摸main_loop_t *loop = (main_loop_t*)info->dispatch_ctx;//multi_gesture.inc提供的函数multi_gesture_post_event_from_points(loop, touch_points, down_number, s_points);}
...
后面实测multi_gesture.inc完全不堪用,机器上双指缩放判定经常失准,双指拉大,会出现一会放大一会缩小的情况,multi_gesture.inc的业务代码我没有细看,但我估计逻辑肯定是有问题的,至少在现在的机器上无法使用。
适配层设计
只能寻找新的触摸方案了,这时候老板指出来我对触摸屏的理解有误,我一开始的想法是手指一触摸屏幕,立刻会在应用层生成一份即时的完整的触点数组数据对象,通过这个对象来获取各触点坐标,状态(按下,移动,弹起),按下手指的数量。后面才知道,触摸屏是逐个去解析按下的各个手指的数据然后处理的,并不是一次就能全部读取到,也就是说,我想像的这种数据对象的组装只能到应用层去做而不是适配层去做,之前的思路相当于把解析和业务逻辑都在适配层做,结果业务一复杂就变得不敷使用了。
照着这种思路,应该是适配层只需要把所有手指的数据一个个上报上去,业务层实现实际逻辑,这样适配层的代码逻辑会更加简单,那么AWTK有没有代表单个手指的数据?
查阅AWTK代码, 发现有一个touch_event正好符合要求,很幸运也很讽刺,awtk官方在去年12月4日的更新里面已经支持在awtk-linux-fb对touch_event事件上报,还在awtk-web整了一个多点触摸的演示demo,里面的finger_status_t已经把我设想的数据对象的功能给完成了,我累死累活自己适配了一版触摸,结果人家一个月后自己就整好了一个更好的方案,我还一直没有注意到,惭愧。。。
不过项目紧急,有现成的轮子直接拿来用总是好事,重改适配层,这次只需要把ts_read_mt读到的事件封装成touch_event事件直接丢给队列即可:
#define CT_MAX_TOUCH 10
static multi_touch_point_event_t s_points[CT_MAX_TOUCH];ret_t my_main_loop_post_touch_event(main_loop_t* l, event_type_t event_type, touch_event_t* event) {event_queue_req_t r;touch_event_t evt;main_loop_simple_t* loop = (main_loop_simple_t*)l;memset(&r, 0x00, sizeof(r));memset(&evt, 0x00, sizeof(multi_gesture_event_t));return_value_if_fail(loop != NULL, RET_BAD_PARAMS);memcpy(&evt, event, sizeof(touch_event_t));evt.e.type = event_type;evt.e.time = time_now_ms();evt.e.size = sizeof(touch_event_t);r.touch_event = evt;return main_loop_queue_event(l, &r);
}static ret_t tslib_dispatch_one_event(run_info_t* info) {....// note: down, up only trigger once(ET), move will be keep trigger(LT)for (int i = 0; i < max_slots; i++) {if (!(info->samp_mt[0][i].valid & TSLIB_MT_VALID)){continue;}if(info->samp_mt[0][i].pressure > 0){down_number++;if(s_points[i].type == 0 || s_points[i].type == EVT_TOUCH_UP){s_points[i].type = EVT_TOUCH_DOWN;}else{s_points[i].type = EVT_TOUCH_MOVE;}} else{s_points[i].type = EVT_TOUCH_UP;}s_points[i].finger_id = info->samp_mt[0][i].slot;s_points[i].x = info->samp_mt[0][i].x;s_points[i].y = info->samp_mt[0][i].y;event_number++;main_loop_t *loop = (main_loop_t*)info->dispatch_ctx;touch_event_t touch_event;touch_event_init(&touch_event, s_points[i].type, NULL, 0, info->samp_mt[0][i].slot, info->samp_mt[0][i].x / (double)info->max_x, info->samp_mt[0][i].y / (double)info->max_y, info->samp_mt[0][i].pressure);my_main_loop_post_touch_event(loop, s_points[i].type, &touch_event);...
触点丢失事件
乐极生悲,接下来的一个坑卡了一周都无法解决, 发现应用程序一旦负担比较高,比如渲染复杂图形或者事件处理极为频繁的时候,touch_up的事件经常会被打断。最后问了微信群,才知道是awtk的活动队列处理不过来导致丢事件,把main_loop_simple.h的MAIN_LOOP_QUEUE_SIZE宏从默认的20调高到100,重新编译awtk-linux-fb和应用程序后,问题解决。
对于之前的那个测试demo, 它管理了一个触点对象数组s_fingers_status
,一旦检测到手指按下就会生成一个finger_status_t
类型的触点对象并加入到s_fingers_status 中,finger_status_t
内部也维护了一个point数组,在手指移动的时候就会将手指移动的即时坐标数据加入到数组中,用于on_paint事件的画线测试,然后在手指弹起时,该触点对象会被从s_fingers_status中移除并销毁,如果touch_up事件不上报的话这个事件就会一直留在s_fingers_status里面,导致无法正常获取正确的按下手指数量,出问题的手指对象也会一直停留在move的状态,无法恢复。
on_touch_up: 3 size=0
on_touch_down : 1 size=1 460 103
on_touch_down : 3 size=2 591 214
on_touch_move: 1 size=2 459 105
on_touch_move: 3 size=2 591 214
on_touch_move: 1 size=2 459 112
on_touch_move: 3 size=2 591 219
on_touch_move: 1 size=2 458 121
on_touch_move: 3 size=2 590 225
on_touch_move: 1 size=2 458 139
on_touch_move: 3 size=2 589 237
on_touch_move: 1 size=2 459 162
on_touch_move: 3 size=2 587 251
on_touch_down : 2 size=3 295 112
on_touch_move: 2 size=3 295 113
on_touch_move: 2 size=3 296 118
on_touch_move: 2 size=3 298 125
on_touch_move: 2 size=3 302 143
on_touch_move: 1 size=3 461 193
on_touch_move: 2 size=3 308 166
on_touch_move: 3 size=3 585 272
on_touch_move: 1 size=3 464 228
on_touch_move: 2 size=3 318 200
on_touch_move: 3 size=3 582 296
on_touch_down : 0 size=4 137 279
on_touch_move: 0 size=4 137 280
on_touch_move: 0 size=4 144 294
on_touch_move: 0 size=4 154 312
on_touch_move: 0 size=4 173 348
on_touch_move: 0 size=4 195 394
on_touch_move: 1 size=4 468 268
on_touch_move: 1 size=4 493 388
on_touch_move: 2 size=4 378 391
on_touch_move: 1 size=4 506 420
on_touch_up: 2 size=3
on_touch_move: 1 size=3 518 447
on_touch_move: 1 size=3 525 466
on_touch_move: 1 size=3 525 481
on_touch_up: 1 size=2
之前没找到问题根源的时候就隐约觉得问题跟机器的性能有关,因为测试demo在公司的开发板上两指到五指都正常,但是到了自己住处, 用百问网的imx6ull开发板一测试很快就出现丢点问题,后面又在适配层尝试限制touch_move事件的频率,改为30ms上报一次touch_move事件,发现有一定的降低丢点概率的效果(手指一多还是会丢点),但是仍旧未往事件队列本身的处理能力去设想,更没有想到在awtk库里面可以调这个事件队列大小,GUI开发还有很多基础知识需要完善。
最终代码
tslib_thread.c
/*** File: tslib_thread.c* Author: AWTK Develop Team* Brief: thread to handle touch screen events** Copyright (c) 2018 - 2025 Guangzhou ZHIYUAN Electronics Co.,Ltd.** This program is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the* License file for more details.**//*** History:* ================================================================* 2018-09-07 Li XianJing <xianjimli@hotmail.com> created**/#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include "tslib.h"
#include "tkc/mem.h"
#include "tkc/utils.h"
#include "tkc/thread.h"
#include "base/keys.h"#include "tslib_thread.h"
#include <linux/input.h>
#include "multi_gesture.inc"typedef struct _run_info_t {int32_t max_x;int32_t max_y;struct tsdev* ts;void* dispatch_ctx;char* filename;input_dispatch_t dispatch;event_queue_req_t req;struct ts_sample_mt **samp_mt;struct input_absinfo slot;int32_t user_slots;int32_t max_slots;int read_samples;int last_down_number;
} run_info_t;#define RESET "\033[0m"
#define RED "\033[31m"
#define GREEN "\033[32m"
#define BLUE "\033[34m"
#define YELLOW "\033[33m"#define CT_MAX_TOUCH 10
static multi_gesture_touch_points_t* touch_points = NULL;
static multi_touch_point_event_t s_points[CT_MAX_TOUCH];static ret_t tslib_dispatch(run_info_t* info) {ret_t ret = RET_FAIL;char message[MAX_PATH + 1] = {0};// tk_snprintf(message, sizeof(message) - 1, "ts[%s]", info->filename);ret = info->dispatch(info->dispatch_ctx, &(info->req), message);info->req.event.type = EVT_NONE;return ret;
}ret_t my_main_loop_post_touch_event(main_loop_t* l, event_type_t event_type, touch_event_t* event) {event_queue_req_t r;touch_event_t evt;main_loop_simple_t* loop = (main_loop_simple_t*)l;memset(&r, 0x00, sizeof(r));memset(&evt, 0x00, sizeof(multi_gesture_event_t));return_value_if_fail(loop != NULL, RET_BAD_PARAMS);memcpy(&evt, event, sizeof(touch_event_t));evt.e.type = event_type;evt.e.time = time_now_ms();evt.e.size = sizeof(touch_event_t);r.touch_event = evt;return main_loop_queue_event(l, &r);
}static ret_t tslib_dispatch_one_event(run_info_t* info) {int ret = -1;if (info->ts != NULL) {ret = ts_read_mt(info->ts, info->samp_mt, info->max_slots, info->read_samples);}uint8_t event_number = 0, down_number = 0;event_queue_req_t* req = &(info->req);if (ret == 0) {log_warn("%s:%d: get tslib data failed, filename=%s\n", __func__, __LINE__, info->filename);sleep(1);return RET_OK;} else if (ret < 0) {sleep(2);if (access(info->filename, R_OK) == 0) {if (info->ts != NULL) {ts_close(info->ts);}info->ts = ts_open(info->filename, 0);return_value_if_fail(info->ts != NULL, RET_OK);ts_config(info->ts);if (info->ts == NULL) {log_debug("%s:%d: open tslib failed, filename=%s\n", __func__, __LINE__, info->filename);perror("print tslib: ");} else {log_debug("%s:%d: open tslib successful, filename=%s\n", __func__, __LINE__,info->filename);}}return RET_OK;}int max_slots = info->max_slots;// note: down, up only trigger once(ET), move will be keep trigger(LT)for (int i = 0; i < max_slots; i++) {if (!(info->samp_mt[0][i].valid & TSLIB_MT_VALID)){continue;}// printf(YELLOW "BSP sample %d - %ld.%06ld -" RESET " (slot %d) %6d %6d %6d\n",// 0,// info->samp_mt[0][i].tv.tv_sec,// info->samp_mt[0][i].tv.tv_usec,// info->samp_mt[0][i].slot,// info->samp_mt[0][i].x,// info->samp_mt[0][i].y,// info->samp_mt[0][i].pressure);if(info->samp_mt[0][i].pressure > 0){down_number++;if(s_points[i].type == 0 || s_points[i].type == EVT_TOUCH_UP){s_points[i].type = EVT_TOUCH_DOWN;}else{s_points[i].type = EVT_TOUCH_MOVE;}} else{s_points[i].type = EVT_TOUCH_UP;}s_points[i].finger_id = info->samp_mt[0][i].slot;s_points[i].x = info->samp_mt[0][i].x;s_points[i].y = info->samp_mt[0][i].y;event_number++;main_loop_t *loop = (main_loop_t*)info->dispatch_ctx;touch_event_t touch_event;touch_event_init(&touch_event, s_points[i].type, NULL, 0, info->samp_mt[0][i].slot, info->samp_mt[0][i].x / (double)info->max_x, info->samp_mt[0][i].y / (double)info->max_y, info->samp_mt[0][i].pressure);my_main_loop_post_touch_event(loop, s_points[i].type, &touch_event);// print debug// char *msg = "down";// if(s_points[i].type == EVT_TOUCH_MOVE){// msg = "move";// }// else if(s_points[i].type == EVT_TOUCH_UP){// msg = "up";// }// printf("slot %d %s at (%d %d) press %d\r\n", // s_points[i].finger_id, // msg,// s_points[i].x, // s_points[i].y, // info->samp_mt[0][i].pressure); }// printf("down number: %d\r\n", down_number);if(event_number == 1 && (info->last_down_number == 0 || info->last_down_number == 1)){struct ts_sample_mt e = info->samp_mt[0][0];req->event.type = EVT_NONE;req->pointer_event.x = e.x;req->pointer_event.y = e.y;// log_debug("%s%d: e.pressure=%d x=%d y=%d ret=%d\n", __func__, __LINE__, e.pressure, e.x, e.y,// ret);if (e.pressure > 0) {if (req->pointer_event.pressed) {req->event.type = EVT_POINTER_MOVE;} else {req->event.type = EVT_POINTER_DOWN;req->pointer_event.pressed = TRUE;}} else {if (req->pointer_event.pressed) {req->event.type = EVT_POINTER_UP;}req->pointer_event.pressed = FALSE;}info->last_down_number = down_number;return tslib_dispatch(info);}info->last_down_number = down_number;return 0;
}void* tslib_run(void* ctx) {run_info_t info = *(run_info_t*)ctx;if (info.ts == NULL) {log_debug("%s:%d: open tslib failed, filename=%s\n", __func__, __LINE__, info.filename);} else {log_debug("%s:%d: open tslib successful, filename=%s\n", __func__, __LINE__, info.filename);}TKMEM_FREE(ctx);while (tslib_dispatch_one_event(&info) == RET_OK) {};ts_close(info.ts);TKMEM_FREE(info.filename);return NULL;
}static run_info_t* info_dup(run_info_t* info) {run_info_t* new_info = TKMEM_ZALLOC(run_info_t);*new_info = *info;return new_info;
}tk_thread_t* tslib_thread_run(const char* filename, input_dispatch_t dispatch, void* ctx,int32_t max_x, int32_t max_y) {run_info_t info;tk_thread_t* thread = NULL;return_value_if_fail(filename != NULL && dispatch != NULL, NULL);memset(&info, 0x00, sizeof(info));info.max_x = max_x;info.max_y = max_y;info.dispatch_ctx = ctx;info.dispatch = dispatch;info.ts = ts_open(filename, 0);info.filename = tk_strdup(filename);if (info.ts != NULL) {ts_config(info.ts);}
///struct tsdev *ts = info.ts;info.read_samples = 1;info.max_slots = CT_MAX_TOUCH;printf("max_x %d max_y %d TOUCH MAX SLOT=%d\r\n", info.max_x, info.max_y, info.max_slots);info.samp_mt = malloc(info.read_samples * sizeof(struct ts_sample_mt *));if (!info.samp_mt) {printf("create samp_mt failed\r\n");ts_close(ts);return NULL;}for (int i = 0; i < info.read_samples; i++) {info.samp_mt[i] = calloc(info.max_slots, sizeof(struct ts_sample_mt));if (!info.samp_mt[i]) {printf("create samp_mt[%d] failed\r\n", i);for (i--; i >= 0; i--)free(info.samp_mt[i]);free(info.samp_mt);ts_close(ts);return NULL;}}/* 创建不可识别手指类型的多点触控句柄 */touch_points = multi_gesture_touch_points_create(15);memset(s_points, 0x0, sizeof(multi_touch_point_event_t) * CT_MAX_TOUCH);
/thread = tk_thread_create(tslib_run, info_dup(&info));if (thread != NULL) {tk_thread_start(thread);} else {multi_gesture_gesture_touch_points_destroy(touch_points);if (info.samp_mt) {for (int i = 0; i < info.read_samples; i++) {free(info.samp_mt[i]);}free(info.samp_mt);}ts_close(info.ts);TKMEM_FREE(info.filename);}return thread;
}
练习demo地址
https://gitee.com/tracker647/awtk-practice/tree/master/MapImageTouchZoomTest
为了记录做业务学到的东西,我额外写了个demo来演示手指缩放和旋转的操作,采用vgcanvas矢量画布来实现旋转和缩放的效果,原本我想直接从网上下载个地图图片然后用vgcanvas_draw_image绘制,来表示地图的旋转和缩放,但是不知道是不是imx6ull本身图形性能太拉,一在on_paint函数调用vgcanvas_draw_image事件整个应用fps就会下降至1,根本无法正常操作图片,最后只好放弃,改成画一个红色矩形来演示效果:
相关文章:

AWTK 嵌入式Linux平台实现多点触控缩放旋转以及触点丢点问题解决
前言 最近涉及海图的功能交互,多点触摸又开始找麻烦。 在PC/Web平台awtk是通过底层的sdl2库来实现多点触摸,但是在嵌入式Linux平台,可能是考虑到性能原因,awtk并没有采用sdl库来做事件处理,而是自己实现一个awtk-lin…...

尚硅谷redis7 93-97 springboot整合reids之总体概述
93 springboot整合reids之总体概述 总体概述 jedis-lettuce-RedisTemplate三者的联系 名称类型作用描述和其它的关系JedisRedis 客户端早期主流的 Java Redis 客户端,基于阻塞 I/O,同步操作可作为 RedisTemplate 的底层连接实现LettuceRedis 客户端基…...
Flutter、React Native、Unity 下的 iOS 性能与调试实践:兼容性挑战与应对策略(含 KeyMob 工具经验)
移动端跨平台开发逐渐成为常态,Flutter、React Native、Unity、Hybrid App 等框架在各类 iOS 项目中频繁出现。但随之而来的,是一系列在 iOS 设备上调试难、性能数据采集难、日志整合难的问题。 今天这篇文章,我从实际项目出发,聊…...

声纹技术体系:从理论基础到工程实践的完整技术架构
文章目录 一、声纹技术的理论基础与概念内核1.1 声纹的生物学本质与数学表征1.2 特征提取的理论基础与实现机制 二、声纹识别技术的演进逻辑与方法体系2.1 传统统计学方法的理论架构2.2 深度学习方法的技术革新2.3 损失函数的设计原理与优化策略 三、声纹识别系统的架构设计与模…...

行为型:命令模式
目录 1、核心思想 2、实现方式 2.1 模式结构 2.2 实现案例 3、优缺点分析 4、适用场景 5、实际应用 1、核心思想 目的:将指令信息封装成一个对象,并将此对象作为参数发送给接收方去执行,以使命令的请求方与执行方解耦 概念ÿ…...
构建多模型协同的Ollama智能对话系统
构建多模型协同的Ollama智能对话系统 在人工智能应用中,单一模型往往难以满足复杂场景的需求。本文将介绍如何整合多个Ollama模型,构建一个智能对话系统,实现情感分析、危机评估和智能回复的协同功能。 系统架构 该系统采用多模型pipeline…...

vue3 + WebSocket + Node 搭建前后端分离项目 开箱即用
[TOC](vue3 WebSocket Node 搭建前后端分离项目) 开箱即用 前言 top1:vue3.5搭建前端H5 top2:Node.js koa搭建后端服务接口 top3:WebSocket 长连接实现用户在线聊天 top4:接口实现模块化 Mysql 自定义 top5:文件上…...

Win10秘笈:两种方式修改网卡物理地址(MAC)
Win10秘笈:两种方式修改网卡物理地址(MAC) 在修改之前,可以先确定一下要修改的网卡MAC地址,查询方法有很多种,比如: 1、在设置→网络和Internet→WLAN/以太网,如下图所示。 2、在控…...

【软件】navicat 官方免费版
Navicat Premium Lite https://www.navicat.com.cn/download/navicat-premium-lite...

【深度学习】16. Deep Generative Models:生成对抗网络(GAN)
Deep Generative Models:生成对抗网络(GAN) 什么是生成建模(Generative Modeling) 生成模型的主要目标是从数据中学习其分布,从而具备“生成”数据的能力。两个关键任务: 密度估计࿰…...
java操作服务器文件(把解析过的文件迁移到历史文件夹地下)
第一步导出依赖 <dependency><groupId>org.apache.sshd</groupId><artifactId>sshd-core</artifactId><version>2.13.0</version></dependency> 第二步写代码 public void moveFile( List<HmAnalysisFiles> hmAnalys…...

特伦斯 S75 电钢琴:重构演奏美学的极致表达
在数字音乐时代,电钢琴正从功能性乐器升级为融合艺术、科技与生活的美学载体。特伦斯 S75 电钢琴以极简主义哲学重构产品设计,将专业级演奏体验与现代家居美学深度融合,为音乐爱好者打造跨越技术边界的沉浸式艺术空间。 一、极简主义的视觉叙…...
STM32-标准库-GPIO-API函数
1.void GPIO_DeInit(GPIO_TypeDef* GPIOx); 简明 清除GPIOx的外围寄存器下所有引脚的配置, 恢复到默认配置状态(即上电初始值) 参数 GPIOx:其中x可以是(A..G)来选择GPIO外设。 返回值 None void GPIO_DeInit(GPI…...

Java 文件操作 和 IO(4)-- Java文件内容操作(2)-- 字符流操作
Java 文件操作 和 IO(4)-- Java文件内容操作(2)-- 字符流操作 文章目录 Java 文件操作 和 IO(4)-- Java文件内容操作(2)-- 字符流操作观前提醒:1. Java中操作文件的简单介…...
机器学习与深度学习06-决策树02
目录 前文回顾5.决策树中的熵和信息增益6.什么是基尼不纯度7.决策树与回归问题8.随机森林是什么 前文回顾 上一篇文章地址:链接 5.决策树中的熵和信息增益 熵和信息增益是在决策树中用于特征选择的重要概念,它们帮助选择最佳特征进行划分。 熵&#…...
Netty 实战篇:构建简易注册中心,实现服务发现与调用路由
本文将为前面构建的轻量级 RPC 框架添加“服务注册与发现”功能,支持多服务节点动态上线、自动感知与调用路由,为构建真正可扩展的分布式系统打好基础。 一、背景:为什么需要注册中心? 如果每个客户端都硬编码连接某个 IP/端口的…...
微信小程序(uniapp)对接腾讯云IM
UniApp 对接腾讯云 IM(即时通讯)完整指南 一、项目背景与需求分析 随着社交场景的普及,即时通讯功能已成为移动应用的标配。腾讯云 IM(Tencent IM,即 TIM)提供稳定可靠的即时通讯服务,支持单聊…...

使用摄像头推流+VLC软件拉流
一、作用 使用摄像头创建rtsp链接,并使用VLC软件拉流显示。 二、步骤 1、安装FFmpeg库 下载地址:https://ffmpeg.org/download.htmlFFmpeg库的下载参考之前的博客,下载Win64版本即可:https://blog.csdn.net/beijixingcd/artic…...
python魔法函数
Python 中的魔法方法(Magic Methods),也称为特殊方法(Special Methods)或双下方法(Dunder Methods),是以双下划线 __ 开头和结尾的方法。它们用于定义类的行为,例如运算符…...

XCUITest 是什么
XCUITest(全称 Xcode UI Test)是苹果官方提供的 iOS/macOS UI 自动化测试框架,集成在 Xcode 开发工具中,专门用于测试 Swift/Objective-C 开发的应用程序。 1. XCUITest 的核心特点 ✅ 官方支持:苹果原生框架…...
使用k8s服务进行端口代理
创建registry-service.yaml 使用无Selector的Service Endpoints模式 vi registry-service.yaml编辑以下内容 apiVersion: v1 kind: Service metadata:name: registry-service spec:type: NodePortports:- name: httpprotocol: TCPport: 81 # Service内部端口targ…...

灌水论坛系统总体设计文档
一、实验题目 灌水论坛系统 二、实验目的 旨在通过一个相对完整且功能丰富的Web应用实例,全面地实践和巩固Web开发所需的各项核心技术和工程方法,从而提升其综合应用能力和解决实际开发问题的能力。它不仅仅是完成一个软件,更是一个学习、…...

Mac M1编译OpenCV获取libopencv_java490.dylib文件
Window OpenCV下载地址 https://opencv.org/releases/OpenCV源码下载 https://github.com/opencv/opencv/tree/4.9.0 https://github.com/opencv/opencv_contrib/tree/4.9.0OpenCV依赖 brew install libjpeg libpng libtiff cmake3 ant freetype构建open CV cmake -G Ninja…...

使用 Let‘s Encrypt 和 Certbot 为 Cloudflare 托管的域名申请 SSL 证书
一、准备工作 1. 确保域名解析在 Cloudflare 确保你的域名 jessi53.com 和 www.jessi53.com 的 DNS 记录已经正确配置在 Cloudflare 中,并且状态为 Active。 2. 安装 Certbot 在你的服务器上安装 Certbot 和 Cloudflare 插件。以下是基于 Debian/Ubuntu 和 Cent…...
【Python进阶】元编程、并发
目录 🌟 前言🏗️ 技术背景与价值🩹 当前技术痛点🛠️ 解决方案概述👥 目标读者说明🧠 一、技术原理剖析📊 核心架构图解💡 核心作用讲解🔧 关键技术模块说明⚖️ 技术选型对比🛠️ 二、实战演示⚙️ 环境配置要求💻 核心代码实现案例1:元类实现ORM框架…...
网络协议:[0-RTT 认证 ]
1. 为什么要 0-RTT 认证 降低延迟:SOCKS5 在无认证时需要 2 RTT(握手+请求),若加用户名/密码又要 3 RTT;0-RTT 通过合并步骤,目标是把握手+认证+请求都压缩到 1 RTT。 IE…...
单例模式的类和静态方法的类的区别和使用场景
单例模式的类和使用静态方法的类在功能上都能提供全局访问的能力,但它们在实现方式、特性和使用场景上存在差异,下面从多个方面进行比较: 1. 实现方式 单例模式的类 单例模式确保一个类只有一个实例,并提供一个全局访问点。通常…...
flowable中流程变量的概念(作用域)
核心概念:流程变量(Process Variables) 流程变量是 Flowable 工作流引擎中用于存储、传递和共享与业务流程相关的数据的机制。你可以将它们理解为附着在流程实例(或执行流、任务)上的键值对(Key-Value&…...
【基础算法】模拟算法
文章目录 算法简介1. 多项式输出解题思路代码实现 2. 蛇形方阵解题思路代码实现 3. 字符串的展开解题思路代码实现 算法简介 模拟,顾名思义,就是题目让你做什么你就做什么,考察的是将思路转化成代码的代码能力。 这类题一般较为简单…...
项目 react+taro 编写的微信 小程序,什么命令,可以减少console的显示
在 Taro 项目中,为了减少 console 的显示(例如 console.log、console.info 等),可以通过配置 terser-webpack-plugin 来移除生产环境中的 console 调用。 配置步骤: 修改 index.js 文件 在 mini.webpackChain 中添加 …...