eBPF可观测之网络流量控制和管理traffic control浅尝
目录
工程背景
环境准备
安装工具
安装依赖包
安装C依赖库
操作步骤
目录结构
代码展示
效果展示
拓展提升
工程背景
首先发表一个"暴论"
eBPF在可观测方面的应用,就是各种google。
不需要学习内核,只要掌握ebpf开发套路。
好比你开发 web 开发网站, 你了解socket 底层和内核吗? 一样不了解。 知道怎么调用就行了。
而且 eBPF 的开发也没多少复杂度, 更多的是 在内核态拦截(简化的c语言) 内核数据(不管是网络数据还是tracepoint数据), 最终都是要发给用户态(可以理解为java、golang),然后用户态具体做业务处理。
所以c语言也不需要怎么学,学了也没啥用。
更多的是要学会一些Linux知识。 譬如拦截网络数据,那就得 对tcp/ip协议了解的很清楚,知道怎么寻址。
至于说寻址代码怎么写,chatgpt都能把你生成。
因此,我们就只从其如何使用切入,用短平快的方式上手掌握。目标是用c语言处理内核态,发送到用户态用golang处理,至于把监控数据塞到mysql、prom之类的,那都属于可扩展内容。
另外,通过eBPF,我们可以做到一些应用层做不到或者不应该做到的事情。譬如ddos拦截应该放在eBPF,即网卡协议层面就应该拦截。而不是放在nginx上拦截,因为进入nginx已经到用户层了,这无疑会对系统负载造成巨大压力 。
对于网络流量控制和管理,一般有traffic control、tracepoint、XDP两种常用方式,区别如下:
- Traffic Control(TC):Traffic Control 是 Linux 内核中的一个子系统,用于网络流量的控制和管理。eBPF 可以与 TC 结合使用,通过编写 eBPF 程序来对网络流量进行更细粒度的控制和处理,例如流量分类、队列管理、带宽控制等。eBPF 可以在 TC 的不同阶段插入自定义的程序逻辑,以实现高级的流量控制功能。
- Tracepoint:Tracepoint 是 Linux 内核中的一种跟踪工具,用于收集系统和应用程序的运行时信息。eBPF 提供了一种机制,可以在 Tracepoint 上运行自定义的 eBPF 程序,以收集、分析和处理 Tracepoint 产生的事件数据。通过 eBPF,可以对系统的各种事件进行跟踪和监控,例如进程创建、系统调用、网络流量等,而无需修改内核代码。
- XDP(eXpress Data Path):XDP 是 Linux 内核中的一种高性能数据包处理框架,用于在网络驱动程序接收数据包之前对其进行处理。eBPF 可以与 XDP 结合使用,编写 eBPF 程序对数据包进行高效的过滤、修改和重定向操作。XDP 允许在数据包进入网络协议栈之前进行快速的数据包处理,适用于高性能网络应用,如防火墙、负载均衡和数据包捕获。
简而言之,
- Tracepoint也可以用于网络,但是其并不修改内核,因此只能对一些事件进行跟踪监控。
- tc作用在linux流量控制器traffic controller,既可作ingress又可作egress;而xdp作用在设备驱动上,一般就作ingress,同时性能更高。
- tc是本身存在的,因此只需要创建一个clsact类型的队列作为程序挂载的入口,就像hook一样,可以更方便地修改报文,端口,地址等。而xdp需要将上下文从链路层、网络层、传输层一步步获取。
所以我们就通过TC去拦截veth设备上通过的网卡流量,甚至去篡改数据包以实现伪造源ip或者目标端口转发。
环境准备
eBPF依赖高内核版本的linux,所以我准备了:
- 操作系统 ubuntu22 内核5.15
- 安装docker golang openssl3(这里直接使用了腾讯云的容器专用的虚拟机镜像)
安装工具
go get github.com/cilium/ebpf/cmd/bpf2go
go install github.com/cilium/ebpf/cmd/bpf2go
需要添加/go/bin到环境变量中,用于执行生成的工具文件
这是转换程序,允许在Go 代码中编译和嵌入eBPF 程序
安装依赖包
sudo apt install llvm
sudo apt install clang
安装C依赖库
sudo apt install libelf-dev
git clone --depth 1 https://github.com/libbpf/libbpf
cd src
make install
这个库运行报错,拿软链接尝试解决了问题
sudo ln -s /usr/include/x86_64-linux-gnu/asm /usr/include/asm
操作步骤
简要地描述一下操作的步骤
1.项目目录下执行make把操作内核态的c文件编译生成.go和.o文件 2.编写方法来加载bpf program对象,创建队列,挂载网卡,最后供main函数调用 3.go run cmd/tc/main.go运行 4.命令行查看go的输出 或者 cat /sys/kernel/debug/tracing/trace_pipe查看bpf_printk输出(限于使用tc工具创建队列和挂载网卡)
tips: 1.通过perf list|grep sys_exit_execve 查看具体的tracepoint 2.通过cat /sys/kernel/debug/tracing/available_filter_functions|grep finish_task_switch 查看具体的kprobe(这里的名称用于用户态去link) 3.如需读取内核数据,如获取父进程pid,可以执行 bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h 包含了系统运行Linux 内核源代码中使使用的所有类型定义 4.https://github.com/torvalds/linux 查看源码获取内核函数的签名
目录结构
. ├── Makefile `用来加载环境变量并执行编译` ├── cebpf │ ├── headers `用于存放bpf相关的头文件` 下载自源码 │ └── docker `容器间网络互访,包含了xdp,tc` │ ├── doc.go `实际的编译命令存放的地方,通过makefile来指向` │ ├── loader.go `创建队列,绑定网卡,从内核获取数据` │ ├── dockertc.bpf.c `原始bpf代码` │ ├── mydockertc_bpfeb.o `⬆️ 编译生成的文件` │ ├── mydockertc_bpfeb.go `⬇️ 包含了所要加载的bpf程序对象` ├── cmd │ └── tc │ └── main.go `主函数入口`
代码展示
1.Makefile
CLANG ?= clang
CFLAGS ?= -O2 -g -Wall -WerrorEBPF_ROOT = /home/ubuntu/app/goebpf/cebpf
MY_HEADERS = $(EBPF_ROOT)/headersall: generategenerate: export BPF_CLANG=$(CLANG)
generate: export BPF_CFLAGS=$(CFLAGS)
generate: export BPF_HEADERS=$(MY_HEADERS)
generate:go generate ./...
调用 go:generate 关键词来进行编译
2.doc.go
package docker//go:generate bpf2go -cc $BPF_CLANG -cflags $BPF_CFLAGS -target amd64 mydockertc dockertc.bpf.c -- -I $BPF_HEADERS
通过这步编译命令,我们可以将 dockertc.bpf.c 编译出 .go 和 .o文件。
.go文件包含了bpg program对象,用于golang的用户态调用。
.o文件可以执行用于命令行tc可执行程序挂载网卡
有如下:
traffic control入门——命令行方式加载bpf程序 1.tc qdisc add dev docker0 clsact ---使用docker0创建一个队列 2.tc filter add dev docker0 ingress bpf direct-action obj mydockertc_x86_bpfel.o 清理命令 tc qdisc del dev docker0 clsact 查看命令 tc filter show dev docker0 ingress
3.dockettc.bpf.c
//go:build ignore
#include <vmlinux.h>
#include <bpf_helpers.h>
#include <bpf_endian.h>
#include <bpf_tracing.h>
#include <bpf_legacy.h>#define ETH_HLEN 14 //以太网头部长度
#define IP_CSUM_OFF (ETH_HLEN + offsetof(struct iphdr, check))
#define TOS_OFF (ETH_HLEN + offsetof(struct iphdr, tos))
#define TCP_CSUM_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct tcphdr, check)) //csum的偏移量
#define IP_SRC_OFF (ETH_HLEN + offsetof(struct iphdr, saddr))
#define TCP_DPORT_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct tcphdr, dest)) //目标端口的偏移量
#define TCP_SPORT_OFF (ETH_HLEN + sizeof(struct iphdr) + offsetof(struct tcphdr, source)) //目标端口的偏移量#define IS_PSEUDO 0x10char LICENSE[] SEC("license") = "GPL";struct tc_data_ip { __u32 sip; //源IP地址__u32 dip; //目的IP地址__u32 sport; //源端口__u32 dport; //目的端口
};//ringbuf
struct { //ringbuf,环形缓冲区,算是一种用户内核交互的优先选择__uint(type, BPF_MAP_TYPE_RINGBUF);__uint(max_entries,1<<20); //大概是10M大小
} tc_ip_map SEC(".maps");//从skb获取ip头部
static inline int iph_dr(struct __sk_buff *skb, struct iphdr *iph) //内连函数,编译时直接展开,减少函数调用开销
{int offset = sizeof(struct ethhdr); //计算以太网头部的偏移量return bpf_skb_load_bytes(skb, offset, iph, sizeof(*iph));
}//从skb获取tcp头部
static inline int tcph_dr(struct __sk_buff *skb, struct tcphdr *tcph) //内连函数,编译时直接展开,减少函数调用开销
{int offset = sizeof(struct ethhdr) + sizeof(struct iphdr); //计算以太网头部和ip头部的偏移量return bpf_skb_load_bytes(skb, offset, tcph, sizeof(*tcph));
}//改源ip的,没用上,先注释了
//todo 使用目标ip重定向的问题在于,就是old_ip一定得要真实存在才可以,否则连二层arp都通过不了,需要做arp欺骗
//static inline void set_tcp_ip_src(struct __sk_buff *skb, __u32 new_ip)
//{
// __u32 old_ip = bpf_htonl(load_word(skb, IP_SRC_OFF));
//
// bpf_l4_csum_replace(skb, TCP_CSUM_OFF, old_ip, new_ip, IS_PSEUDO | sizeof(new_ip));
// bpf_l3_csum_replace(skb, IP_CSUM_OFF, old_ip, new_ip, sizeof(new_ip));
// bpf_skb_store_bytes(skb, IP_SRC_OFF, &new_ip, sizeof(new_ip), 0);
//}static inline void set_tcp_dest_port(struct __sk_buff *skb, __u16 new_port)
{ //源码 —— https://github.com/torvalds/linux/blob/master/samples/bpf/tcbpf1_kern.c__u16 old_port = bpf_htons(load_half(skb, TCP_DPORT_OFF));bpf_l4_csum_replace(skb, TCP_CSUM_OFF, old_port, new_port, sizeof(new_port)); //1.修改校验和csumbpf_skb_store_bytes(skb, TCP_DPORT_OFF, &new_port, sizeof(new_port), 0); //2.重新存储到skb
}static inline void set_tcp_src_port(struct __sk_buff *skb, __u16 new_port)
{__u16 old_port = bpf_htons(load_half(skb, TCP_SPORT_OFF));bpf_l4_csum_replace(skb, TCP_CSUM_OFF, old_port, new_port, sizeof(new_port));bpf_skb_store_bytes(skb, TCP_SPORT_OFF, &new_port, sizeof(new_port), 0);
}SEC("classifier") //代表tc的流量分类
int mytc(struct __sk_buff *skb)
{struct iphdr ip;iph_dr(skb, &ip);struct tcphdr tcp;tcph_dr(skb, &tcp);//打包网络数据//如果ip包是tcp协议,才发送数据if(ip.protocol != IPPROTO_TCP){return 0;}//作用:将访问到172.17.0.3:8080重定向到172.17.0.3:80__u16 watch_port = bpf_ntohs(tcp.dest); //目标端口__u32 watch_ip = bpf_ntohl(0xAC110003); //172.17.0.3if (watch_port == 8080 && ip.daddr == watch_ip) {set_tcp_dest_port(skb, bpf_htons(80)); //修改目标端口 A -> B 8080 -> 80tcph_dr(skb, &tcp); //重新读取skb数据到tcp}//这次修改的是tcp三次握手中第二次也就是服务端响应的端口,否则客户端接收到的源端口与目标端口不一致,会重置请求__u16 src_port = bpf_ntohs(tcp.source); //源端口if (src_port == 80 && ip.saddr == watch_ip) {set_tcp_src_port(skb, bpf_htons(8080)); //修改源端口 B -> A 80 -> 8080tcph_dr(skb, &tcp);}struct tc_data_ip *ipdata;ipdata=bpf_ringbuf_reserve(&tc_ip_map, sizeof(*ipdata), 0); //在ringbuf中预留缓冲区大小if(!ipdata){return 0;}ipdata->sip = bpf_ntohl(ip.saddr); //网络字节序转换为主机字节序 否则转换成xxx.xxx.xxx.xxx后会颠倒ipdata->dip = bpf_ntohl(ip.daddr);ipdata->sport = bpf_ntohs(tcp.source);ipdata->dport = bpf_ntohs(tcp.dest);bpf_ringbuf_submit(ipdata, 0); //提交数据return 0; //代表放行,是action的一种,混合了action和classifer,分类器类型需要指定成direct-action
}
源码+chatgpt,你懂的
4.mydockertc_x86_bpfel.go
// Code generated by bpf2go; DO NOT EDIT.
//go:build 386 || amd64package dockerimport ("bytes"_ "embed""fmt""io""github.com/cilium/ebpf"
)// loadMydockertc returns the embedded CollectionSpec for mydockertc.
func loadMydockertc() (*ebpf.CollectionSpec, error) {reader := bytes.NewReader(_MydockertcBytes)spec, err := ebpf.LoadCollectionSpecFromReader(reader)if err != nil {return nil, fmt.Errorf("can't load mydockertc: %w", err)}return spec, err
}// loadMydockertcObjects loads mydockertc and converts it into a struct.
//
// The following types are suitable as obj argument:
//
// *mydockertcObjects
// *mydockertcPrograms
// *mydockertcMaps
//
// See ebpf.CollectionSpec.LoadAndAssign documentation for details.
func loadMydockertcObjects(obj interface{}, opts *ebpf.CollectionOptions) error {spec, err := loadMydockertc()if err != nil {return err}return spec.LoadAndAssign(obj, opts)
}// mydockertcSpecs contains maps and programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type mydockertcSpecs struct {mydockertcProgramSpecsmydockertcMapSpecs
}// mydockertcSpecs contains programs before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type mydockertcProgramSpecs struct {Mytc *ebpf.ProgramSpec `ebpf:"mytc"`
}// mydockertcMapSpecs contains maps before they are loaded into the kernel.
//
// It can be passed ebpf.CollectionSpec.Assign.
type mydockertcMapSpecs struct {TcIpMap *ebpf.MapSpec `ebpf:"tc_ip_map"`
}// mydockertcObjects contains all objects after they have been loaded into the kernel.
//
// It can be passed to loadMydockertcObjects or ebpf.CollectionSpec.LoadAndAssign.
type mydockertcObjects struct {mydockertcProgramsmydockertcMaps
}func (o *mydockertcObjects) Close() error {return _MydockertcClose(&o.mydockertcPrograms,&o.mydockertcMaps,)
}// mydockertcMaps contains all maps after they have been loaded into the kernel.
//
// It can be passed to loadMydockertcObjects or ebpf.CollectionSpec.LoadAndAssign.
type mydockertcMaps struct {TcIpMap *ebpf.Map `ebpf:"tc_ip_map"`
}func (m *mydockertcMaps) Close() error {return _MydockertcClose(m.TcIpMap,)
}// mydockertcPrograms contains all programs after they have been loaded into the kernel.
//
// It can be passed to loadMydockertcObjects or ebpf.CollectionSpec.LoadAndAssign.
type mydockertcPrograms struct {Mytc *ebpf.Program `ebpf:"mytc"`
}func (p *mydockertcPrograms) Close() error {return _MydockertcClose(p.Mytc,)
}func _MydockertcClose(closers ...io.Closer) error {for _, closer := range closers {if err := closer.Close(); err != nil {return err}}return nil
}// Do not access this directly.
//
//go:embed mydockertc_x86_bpfel.o
var _MydockertcBytes []byte
编译出来的文件,代码都是自动生成的
5.tc_loader.go
package dockerimport ("errors""fmt""github.com/cilium/ebpf/ringbuf""github.com/vishvananda/netlink""goebpf/pkg/helpers/nethelper""golang.org/x/sys/unix""log""os""os/signal""syscall""unsafe"
)type TcDataIp struct { //对应mydockertc.bpf.c中的structSip uint32Dip uint32Sport uint32Dport uint32
}// 在目标网卡添加clsact队列,使其成为eBPF监听的对象,来源——cillium源码
func attachIface(linkIndex int, fd int, name string) (deferFuncs []func()) {//2.1初始化队列attrs := netlink.QdiscAttrs{LinkIndex: linkIndex,// 0xffff 表示 “根”或“无父”句柄的队列规则Handle: netlink.MakeHandle(0xffff, 0),Parent: netlink.HANDLE_CLSACT, //eBPF专用 clsact}qdisc := &netlink.GenericQdisc{QdiscAttrs: attrs,QdiscType: "clsact",}//2.2添加队列 —— 好比执行了 tc qdisc add dev docker0 clsactif err := netlink.QdiscAdd(qdisc); err != nil {log.Fatalln("QdiscAdd err: ", err)}deferFuncs = append(deferFuncs, func() { //监测完删除,否则下次无法创建if err := netlink.QdiscDel(qdisc); err != nil {fmt.Println("QdiscDel err: ", err.Error())}})//3.1初始化 eBPF分类器filterattrs := netlink.FilterAttrs{LinkIndex: linkIndex,Parent: netlink.HANDLE_MIN_INGRESS | netlink.HANDLE_MIN_EGRESS,Handle: netlink.MakeHandle(0, 1),Protocol: unix.ETH_P_ALL, //所有协议Priority: 1,}filter := &netlink.BpfFilter{FilterAttrs: filterattrs,Fd: fd,Name: name,DirectAction: true,}//3.2添加分类器 —— 好比执行了 tc filter add dev docker0 ingress bpf direct-action obj dockertcxdp_bpfel_x86.oif err := netlink.FilterAdd(filter); err != nil {log.Fatalln("FilterAdd err: ", err)}deferFuncs = append(deferFuncs, func() {err := netlink.FilterDel(filter)if err != nil {fmt.Println("FilterDel err : ", err.Error())}})return
}// 加载tc ebpf 程序
func LoaderTC() {veth := nethelper.GetVeths()//1 这步和其他的eBPF程序一样,加载转化过来的eBPF程序objs := &mydockertcObjects{}err := loadMydockertcObjects(objs, nil)if err != nil {log.Fatalln("loadDockertcxdpObjects err: ", err)}//2-3 给所有veth网卡添加clsact队列for _, v := range veth {deferFuncs := attachIface(v.Index, objs.Mytc.FD(), "mytc")for _, f := range deferFuncs {defer f()}}//4开个信号阻塞住并循环读取fmt.Println("开始TC监听")go func() {rd, err := ringbuf.NewReader(objs.TcIpMap)if err != nil {log.Fatalf("creating event reader: %s", err)}defer rd.Close()for { //循环读取内核maprecord, err := rd.Read()if err != nil {if errors.Is(err, ringbuf.ErrClosed) {log.Println("Received signal, exiting..")return}log.Printf("reading from reader: %s", err)continue}//对内核态传来的数据进行解析if len(record.RawSample) > 0 {data := (*TcDataIp)(unsafe.Pointer(&record.RawSample[0])) //经过两次强制转换//转换成网络字节序saddr := nethelper.ResolveIP(data.Sip, true)daddr := nethelper.ResolveIP(data.Dip, true)fmt.Printf("监测到来源地址: %s:%d------->目标地址: %s:%d\n",saddr.To4().String(), data.Sport,daddr.To4().String(), data.Dport,)}}}() //循环读取内核态传来的数据//开信号 好处是能执行deferch := make(chan os.Signal)signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL, syscall.SIGQUIT, syscall.SIGHUP)<-chfmt.Println("TC监听结束")
}
参考cilium源码
效果展示
拓展提升
其实,我们拿到了整一个数据包,可以进一步获得报文中的Payload,将其发送到用户态。用户态进行逐字节解析,仅需要知道http协议,mysql报文, redis报文的规定格式。即可判断,并通过一系列操作保存,并作审计用。
相关文章:

eBPF可观测之网络流量控制和管理traffic control浅尝
目录 工程背景 环境准备 安装工具 安装依赖包 安装C依赖库 操作步骤 目录结构 代码展示 效果展示 拓展提升 工程背景 首先发表一个"暴论" eBPF在可观测方面的应用,就是各种google。 不需要学习内核,只要掌握ebpf开发套路。…...
Java技术精粹:高级面试问题与解答指南(二)
Java面试问题及答案 1. 什么是Java中的集合框架?请简述其主要接口和类。 答案: Java中的集合框架是一个设计用来存储和操作大量数据的统一架构。它主要由以下几个接口及其实现类组成: Collection: 它是最基本的集合接口,所有单列…...

地下停车场FM信号覆盖系统技术原理用与应用
随着我国城市化水平的快速推进与房地产的快速发展,城市停车场称为每栋建筑物的硬性配套建筑,尤其是商业综合体、医院、政府机关、机场、高铁站等场所出现了超大规模停车场,停放车辆可达数千辆,停车场的智能化与信息化水平也越来越…...

idea 出现 cpu占用100%
一、IDEA的CPU占用率过高 二、解决办法 idea安装路径bin目录 修改idea64.exe.vmoptions配置文件 原来的 -Xms128m -Xmx750m -XX:ReservedCodeCacheSize240m -XX:UseConcMarkSweepGC -XX:SoftRefLRUPolicyMSPerMB50 修改为(IDEA优化内存配置) -Xms2048m -Xmx4096m -XX:Reser…...

如何学到数据库从入门到入土(MySQL篇)
本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. 🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人能接…...
安卓手机APP开发__Wi-Fi扫描概述
安卓手机APP开发__Wi-Fi扫描概述 目录 概述 Wi-Fi的扫描过程 限制 权限 Android 8.0 and Android 8.1: Android 9: Android 10 (API 级别 29) 和 更高版本: 扫描频率的限制 Android 8.0 and Android 8.1: Android 9: Android 10 and higher: 概述 你能使用Wi-Fi的…...
深入理解二叉树及其在C语言中的实现
一、引言 二叉树是数据结构中一种非常基础且重要的树形结构,它的每个节点最多有两个子节点,通常被称为左子节点和右子节点。二叉树在计算机科学中有着广泛的应用,如搜索、排序、存储数据等。本文将详细介绍二叉树的基本概念、特性以及在C语言…...

基于ssm+vue图书管理系统
基于ssmvue图书管理系统 ssm477图书管理系统 相关技术 javassmmysqlvueelementui...
高防ip能防护变异CC攻击吗
高防ip能防护变异CC攻击吗?随着互联网的不断发展,网络安全问题也日益突出,各类网络攻击层出不穷,其中CC攻击(Challenge Collapsar攻击)及其变种——变异CC攻击,更是让众多企业和个人网站头疼不已…...

从多站点到多活,XEOS 对象数据容灾能力再提升
近日, XSKY SDS V6.4 新版本发布,其中 XEOS V6.4 全新升级并完善了统一命名空间功能,更进一步增强和完善了异地容灾方案,配合强一致代理读,可以实现异地多活;同时大幅降低管理复杂度,有效降低容…...

3D开发工具HOOPS在BIM系统中的应用
建筑信息模型是一种革命性的建筑设计、施工和管理方法。它通过创建和利用数字信息来优化建筑项目的设计、施工和运营过程。在这个过程中,3D开发工具HOOPS扮演着至关重要的角色,为BIM系统提供了强大的技术支持和丰富的功能。HOOPS中文网http://techsoft3d…...

适合下班做的副业兼职、1天挣300,7天涨粉2万
最近小红书上有类视频火了! 周周近财:让网络小白少花冤枉钱,赚取第一桶金 利用AI制作的漫画解说历史小说视频。视频以《明朝那些事儿》为蓝本,一上线就疯狂吸粉,多条视频内容都大爆了。 就是这个账号,仅仅…...

JS中getElementById与querySelector区别收录
JS中getElementById与querySelector区别收录 getElementById 和 querySelector 都是 JavaScript 中用于从文档中选取元素的方法,但两者之间存在一些关键区别: 选择器语法: getElementById:这个方法只接受一个参数,即元…...

Android:使用Kotlin搭建MVC架构模式
一、简介Android MVC架构模式 M 层 model ,负责处理数据,例如网络请求、数据变化 V 层 对应的是布局 C 层 Controller, 对应的是Activity,处理业务逻辑,包含V层的事情,还会做其他的事情,导致 ac…...
delete原理
一 原理 new申请动态内存,delete释放内存,将内存的数据块标记为可覆盖,可再次使用。在调用delete时不会将内存块的数据,全部初始化为0。 二 new动态内存,不调用delete的后果? 造成内存泄漏。new申请的动…...

青少年 CTF 练习平台:Misc(一)
前言 当然,我可以更详细地介绍一下青少年CTF练习平台。 青少年CTF练习平台是一个专为青少年设计的网络安全竞赛和训练平台。该平台由思而听(山东)网络科技有限公司与克拉玛依市思而听网络科技有限公司共同建设,自2018年创建以来…...
展锐平台+Android系统开发概要
文章目录 一、缩略语二、系统分区1. UIS7885android13的系统分区 三、系统编译四、开发调试 一、缩略语 BBAT:Baseband Auto Test,基带自带测试CRC:Cyclic Redundancy Check,循环冗余检验SPL:Secondary Program Loade…...

unity开发Hololens 制作滑动框
一定要做到最后一步,才会有效果 1、创建空物体 ,并添加组件 创建空物体 命名ScrollingObjectCollection, 添加组件如下图 下面是各个组件展开的内容 2、在ScrollingObjectCollection 下面创建两个空物体,分别命名Container、Clipping…...

【JavaEE进阶】——Spring Web MVC (响应)
目录 🚩学习Spring MVC 🎈返回静态网页 🎈返回数据ResponseBody 🎈返回html代码片段 🎈返回JSON 🎈设置状态码 🎈设置Header 🚩学习Spring MVC 既然是 Web 框架, 那么当⽤⼾在…...

基于springboot+vue的公司资产网站(全套)
一、系统架构 前端:vue2 | element-ui 后端:springboot | mybatis 环境:jdk1.8 | mysql | maven | node 二、代码及数据库 三、功能介绍 01. 管理后台-登录 02. 管理后台-首页 03. 管理后台-个人中心-修改密码 04. 管理后台-个人…...

7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...

centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
python报错No module named ‘tensorflow.keras‘
是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
SQL Server 触发器调用存储过程实现发送 HTTP 请求
文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...