Tealium 分析

news2024/12/24 21:16:10

文章目录

  • 1.0 调用流程
    • 1.1 初始化流程
    • 1.2 发送数据流程
  • 2.0 Tealium 的设计
    • 2.1 总体设计
      • 2.1.1 Tealium Core
      • 2.1.2 Visitor
      • 2.1.3 Crash reporter
      • 2.1.4 Ad identifler
      • 2.1.5 Lifecycle
      • 2.1.6 Location
      • 2.1.7 InstallReferrer
      • 2.1.8 Hosteddatalayer
      • 2.1.8 Dispatcher
    • 2.2 网络设计
    • 2.3 本地数据存储设计
    • 2.4 发送数据策略设计
  • 3.0 借鉴的地方
    • 3.1 良好的设计模式
  • 4.0 其他
    • 4.1 有启发的代码
      • 4.1.2 重试
      • 4.2.2 线程池和协程的结合
      • 4.2.3 CoroutineExceptionHandler 处理异常

Tealium 分析, 以 1.2.4 版本为例

源码在: https://github.com/Tealium/tealium-kotlin

官方文档:https://docs.tealium.com/platforms/getting-started/

1.0 调用流程

1.1 初始化流程

在这里插入图片描述

说明:
① new 一个 TealiumConfig, 进行 Tealium 的一些配置

class TealiumConfig @JvmOverloads constructor(val application: Application,
                    val accountName: String,
                    val profileName: String,
                    val environment: Environment,
                    var dataSourceId: String? = null,
                    val collectors: MutableSet<CollectorFactory> = Collectors.core,
                    val dispatchers: MutableSet<DispatcherFactory> = mutableSetOf(),
                    val modules: MutableSet<ModuleFactory> = mutableSetOf()) {

    /**
     * A set of validators where any custom [DispatchValidator]s can be added. These will be merged
     * in with the built in validators when initializing the library.
     */
    val validators: MutableSet<DispatchValidator> = mutableSetOf()

    // 文件的存储路径
    private val pathName = "${application.filesDir}${File.separatorChar}tealium${File.separatorChar}${accountName}${File.separatorChar}${profileName}${File.separatorChar}${environment.environment}"
    val tealiumDirectory: File = File(pathName)

    /**
     * 用于存储一些额外的信息
     * Map of key-value pairs supporting override options that do not have a direct property on the
     * [TealiumConfig] object.
     */
    val options = mutableMapOf<String, Any>()

    /**
     * Gets and sets the initial LibrarySettings for the library. Useful defaults have already been
     * set on the [LibrarySettings] default constructor, but the default settings used by the
     * library can be set here.
     */
    var overrideDefaultLibrarySettings: LibrarySettings? = null

    /** 是否使用服务端的配置
     * Sets whether or not to fetch publish settings from a remote host.  
     */
    var useRemoteLibrarySettings: Boolean = false

    /**
     * 
     * Sets the remote URL to use when requesting updated remote publish settings.
     */
    var overrideLibrarySettingsUrl: String? = null

    /**
     * Set to false to disable deep link tracking.
     */
    var deepLinkTrackingEnabled: Boolean = true

    /**
     * Set to false to disable the QR code trace feature.
     */
    var qrTraceEnabled: Boolean = true

    /**
     * A list of EventTriggers for automatically starting and stopping TimedEvents.
     */
    var timedEventTriggers: MutableList<EventTrigger> = mutableListOf()

    init {
        tealiumDirectory.mkdirs()
    }
}

② 将一个 TealiumConfig 配置传入,创建一个 Tealium,可以有多个 Tealium, 内部使用 map 存储起来,同时还可以传入 onReady, 在 Tealium 初始化可以的时候进行回调

// Tealium.kt
 fun create(name: String, config: TealiumConfig, onReady: (Tealium.() -> Unit)? = null): Tealium {
            val instance = Tealium(name, config, onReady)
            instances[name] = instance
            return instance
}

③ Tealium#init 初始化 Tealium,初始化 Tealium 所需要的资源。

1.2 发送数据流程

以 track TealiumEvent 主要流程分析

在这里插入图片描述

① GenericDispatch 也是继承 Dispatch,它的 payload 是从传入的 dispatch 进行复制

internal class GenericDispatch(dispatch: Dispatch) : Dispatch {

    override val id: String = dispatch.id
    override var timestamp: Long? = dispatch.timestamp ?: System.currentTimeMillis()

    private val payload = shallowCopy(dispatch.payload())

    override fun payload(): Map<String, Any> {
        return payload.toMap()
    }

    override fun addAll(data: Map<String, Any>) {
        payload.putAll(data)
    }

② SessionManager#track
用于通知 SessionManager 发送一个新的 Dispatch,如果 Session 已经过期,则会生成一个新的 session.

③ DispatchRouter#track
发放 Dispatch 的入口

fun track(dispatch: Dispatch) {
    if (settings.disableLibrary) {
        return
    }

    scope.launch(Logger.exceptionHandler) {
        // Collection
        dispatch.addAll(collect()) // 收集注册的 Collector
        transform(dispatch) // 在本地换缓存的 Data Layer 中查找

        // Validation - Drop ⑥
        if (shouldDrop(dispatch)) {
            scope.launch(Logger.exceptionHandler) {
                eventRouter.onDispatchDropped(dispatch)
            }
            return@launch
        }

        // Dispatch Ready ⑦
        scope.launch(Logger.exceptionHandler) {
            eventRouter.onDispatchReady(dispatch)
        }

        // Validation - Queue ⑧
        if (shouldQueue(dispatch)) {
            dispatchStore.enqueue(dispatch)
            scope.launch(Logger.exceptionHandler) {
                eventRouter.onDispatchQueued(dispatch)
            }
            return@launch
        }

        // Dispatch Send ⑨
        val queue = dequeue(dispatch).sortedBy { d -> d.timestamp }
        sendDispatches(queue) // ⑩
    }
}

④ collect
根据不同的 Collector, 收集不同的信息添加到 dispatch 的 map 中存储,
这些信息包含 app 信息,网络信息, 设备信息等,后面有详细的介绍。

⑤ transform
更新存储在本地的 dispatch 信息

⑥ shouldDrop
通过不同的 DispatchValidator 觉得是否抛弃 dispatch

⑦ onDispatchReady
分发到不同的 DispatchReadyListener, 准备 dispatch 已经 ready.

class EventDispatcher : EventRouter { 
    ...

    override fun onDispatchReady(dispatch: Dispatch) {
        listeners.forEach {
            when (it) {
                is DispatchReadyListener -> it.onDispatchReady(dispatch)
            }
        }
    }

    ...
}

⑧ shouldQueue 是否新进排队队列

⑨ dequeue 从队列里面弹出,也会触发 remote commands

⑩ sendDispatches 网络发送
里面会根据要发送的 Dispatche 数量,决定是否是单个发生还是批量发生

class EventDispatcher : EventRouter { 
    ...

    // 单个发生
    override suspend fun onDispatchSend(dispatch: Dispatch) {
        listeners.forEach {
            when (it) {
                is DispatchSendListener -> {
                    if (it !is Module || it.enabled) {
                        it.onDispatchSend(dispatch)
                    }
                }
            }
        }
    }

    // 批量发送
    override suspend fun onBatchDispatchSend(dispatches: List<Dispatch>) {
    listeners.forEach {
        when (it) {
            is BatchDispatchSendListener -> {
                if (it !is Module || it.enabled) {
                    it.onBatchDispatchSend(dispatches)
                }
            }
        }
    }
}

    ...
}

2.0 Tealium 的设计

2.1 总体设计

在这里插入图片描述

2.1.1 Tealium Core

Tealium core 是 Tealium 的核心,它包含 dispatcher, collection, messaging, setting, presistence, network, validation, events

Tealium
Tealium.kt 是 Tealium 入口,也是组织这个 Tealium 的核心。初始化的配置使用 TealiumConfig, TealiumContext 作为 Tealium 的上下文,在整个 lib 中都用使用。

Dispatcher
Dispatcher 里面包含用于分发事件的 TealiumEvent 和 TealiumView,以及 Dispatcher,关于这部分的详细说明在 2.1.8 Dispatcher 一节。

Collection
Collection 里面的 Collector 是用于收集一些信息

在这里插入图片描述

  • AppCollector: 收集 app 相关的信息,例如: appVersion
  • ConnectivityCollector: 收集网络连接的信息
  • DeviceCollector: 收集设备相关的信息
  • TealiumCollector: 收集 Tealium 相关的信息,例如:tealium_account,tealium_profile 等
  • TimeCollector: 收集时间相关内容,例如:timestamp
  • ModuleCollector: 收集 tealium 中 enable 的 module
  • SessionCollector: 收集 session id

messaging
主要包括 DispatchRouter, EventRouter 用来分发 message

setting
主要是 tealium 库的设置 LibrarySettings
在启动的时候会打印出来设置
例如:

 LibrarySettings updated: LibrarySettings(
    collectDispatcherEnabled=true, 
    tagManagementDispatcherEnabled=true, 
    batching=Batching(
        batchSize=1, 
        maxQueueSize=-1, 
        expiration=86400
    ), 
    batterySaver=false, 
    wifiOnly=false, 
    refreshInterval=60,
    disableLibrary=false, 
    logLevel=DEV
)

persistence
主要是本地数据库 SQLite 的存储,用来存放要发送的 message

network
主要是用来将 message 使用网络的形式发送出去,主要类是 HttpClient

validation
控制 Dispatch 是否发送,还是放到队列,亦或是抛弃,这个在 2.4 发送数据策略设计 中详细说明

events
主要是 TimedEvent 事件,以及它的相关管理,TimedEventsManager

2.1.2 Visitor

Visitor 模块 主要是用来管理 visitor 身份和属性相关内容

主要的类图,有省略

在这里插入图片描述

2.1.3 Crash reporter

Crash reporter 主要是用来上报 crash 相关的信息。
在 app crash 的时候,用 SharedPreferences save 存储 crash 的相关信息,然后在 ActivityObserver#onActivityResumed 的时候将存储的信息发送出去。

2.1.4 Ad identifler

获取谷歌广告的信息,然后存储在 dataLayer

2.1.5 Lifecycle

Lifecycle 模块主要是用来发送真个 app 的生命周期事件,包括启动,唤醒和休眠等。

2.1.6 Location

Location 模块主要是获取用户的经纬度坐标信息。
通过 gms:play-services-location 获取

2.1.7 InstallReferrer

InstallReferrer 模块用来获取 app 安装时的信息

2.1.8 Hosteddatalayer

Hosteddatalayer 模块管理其他模块提供的数据。

2.1.8 Dispatcher

Dispatcher 是 Tealium 的外围部分,是我自己的理解把它们放到一起的。它包含 collectdispatcher 模块,remotecommanddispatcher 模块和 tagmanagementdispatcher 模块

collectdispatcher
collectdispatcher 是将 Dispatch 发送到 NetworkClient 中,它包含两个函数,一个是发送一个 Dispatch 的 onDispatchSend,另外一个是批量发送的 onBatchDispatchSend。

RemoteCommand Dispatcher
remotecommanddispatcher 通过tag 或 JSON 控制处理和评估客户端提供的命令.

Tagmanagement Dispatcher
tagmanagementdispatcher 是一个通过 webview 执行 JavaScript 的模块

Dispatch 的相关类

在这里插入图片描述

2.2 网络设计

在这里插入图片描述

通过 HttpClient#post 函数发送数据,它直接使用 HttpURLConnection 进行网络连接。

    override suspend fun post(payload: String, urlString: String, gzip: Boolean) = coroutineScope {
        if (isActive && isConnected) {
            try {
                with(URL(urlString).openConnection() as HttpURLConnection) {
                    try {
                        doOutput = true
                        setRequestProperty("Content-Type", "application/json")
                        val dataOutputStream = when (gzip) {
                            true -> {
                                setRequestProperty("Content-Encoding", "gzip")
                                DataOutputStream(GZIPOutputStream(outputStream))
                            }
                            false -> {
                                DataOutputStream(outputStream)
                            }
                        }
                        dataOutputStream.write(payload.toByteArray(Charsets.UTF_8))
                        dataOutputStream.flush()
                        dataOutputStream.close()
                    } catch (e: Exception) {
                        networkClientListener?.onNetworkError(e.toString())
                    }
                    networkClientListener?.onNetworkResponse(responseCode, responseMessage)
                }
            } catch (e: ConnectException) {
                Logger.prod(BuildConfig.TAG, "Could not connect to host: $e.")
                networkClientListener?.onNetworkError(e.toString())
            } catch (e: Exception) {
                Logger.prod(BuildConfig.TAG, "An unknown exception occurred: $e.")
                networkClientListener?.onNetworkError(e.toString())
            }
        }
    }

2.3 本地数据存储设计

本地数据存储在 Tealium core 的 presistence 中

DispatchStorage 对外的接口

在这里插入图片描述

说明

  • DatabaseHelper 实现抽象类 SQLiteOpenHelper,用来管理数据库。
  • DispatchStorage 数据管理对外的一个类,外部的对数据的调用都是通过此类。

2.4 发送数据策略设计

fun track(dispatch: Dispatch) {
    
    ...

    scope.launch(Logger.exceptionHandler) {
        ...

        // 抛弃
        if (shouldDrop(dispatch)) {
            scope.launch(Logger.exceptionHandler) {
                eventRouter.onDispatchDropped(dispatch)
            }
            return@launch
        }

        // Dispatch Ready
        scope.launch(Logger.exceptionHandler) {
            eventRouter.onDispatchReady(dispatch)
        }

        // 加入队列
        if (shouldQueue(dispatch)) {
            dispatchStore.enqueue(dispatch)
            scope.launch(Logger.exceptionHandler) {
                eventRouter.onDispatchQueued(dispatch)
            }
            return@launch
        }

        // 发送
        val queue = dequeue(dispatch).sortedBy { d -> d.timestamp }
        sendDispatches(queue)
    }

是否抛弃 shouldDrop

fun shouldDrop(dispatch: Dispatch): Boolean {
    return validators.filter { it.enabled }.fold(false) { input, validator ->
        input || validator.shouldDrop(dispatch).also { dropping ->
            if (dropping) Logger.qa(BuildConfig.TAG, "Dropping dispatch requested by: ${validator.name}")
        }
    }
}

是否进队列

fun shouldQueue(dispatch: Dispatch?, override: Class<out DispatchValidator>? = null): Boolean {
    return validators.filter { it.enabled }.fold(false) { input, validator ->
        input || if (override != null && override.isInstance(validator)) {
                    false
                } else validator.shouldQueue(dispatch).also { queueing ->
                    if (queueing) {
                        Logger.qa(BuildConfig.TAG, "Queueing dispatch requested by: ${validator.name}")
                        if (validator.name == "BATCHING_VALIDATOR") {
                            attemptSendRemoteCommand(dispatch)
                        }
                    }
                }
    }
}

Validator 决定 Dispatch 是否抛掉或者放到发送队列里面

/**
 * A DispatchValidator can be used to control the flow of Dispatches through the system. Each new
 * Dispatch will be sent to each Dispatch Validator; any one of them can signify that the Dispatch
 * should be either queued or dropped.
 */
interface DispatchValidator: Module {

    /**
     * Will be called for each new dispatch and for any revalidation events signified by a null
     * value for the [dispatch] parameter.
     *
     * @param dispatch the new dispatch, or null
     * @return true if the dispatch should be queued, otherwise false
     */
    fun shouldQueue(dispatch: Dispatch?): Boolean

    /**
     * Will be called for each new dispatch.
     *
     * @param dispatch the new dispatch
     * @return true if the dispatch should be queued, otherwise false
     */
    fun shouldDrop(dispatch: Dispatch): Boolean
}

在 Tealium#initializeValidators 中添加


/**
* Instantiates the built in validators and joins them with any supplied custom validators.
*/
private fun initializeValidators(customValidators: Set<DispatchValidator>): Set<DispatchValidator> {
    customValidators.forEach { it.enabled = true }
    return setOf<DispatchValidator>(
            BatteryValidator(config, librarySettingsManager.librarySettings, events),
            ConnectivityValidator(connectivity, librarySettingsManager.librarySettings),
            BatchingValidator(dispatchStore, librarySettingsManager.librarySettings, eventRouter)
    ).union(customValidators)
}
  • BatteryValidator

如果是 enabled 获取是低电量则将 Dispatch 先放到队列中;
shouldDrop 默认 false.


override fun shouldQueue(dispatch: Dispatch?): Boolean {
    return (enabled && isLowBattery).also { queueing ->
        if (queueing) Logger.qa(BuildConfig.TAG, "Battery is low ($batteryLevel%)")
    }
}

override fun shouldDrop(dispatch: Dispatch): Boolean {
    return false
}

  • ConnectivityValidator
    如果在当我们对 Tealium 的 config 设定只是在 wifi 情况下发生,没有连接或者在其他网络也没连接的情况下,放到队列中;
    shouldDrop 默认 false.
override fun shouldQueue(dispatch: Dispatch?): Boolean {
    return when(librarySettings.wifiOnly) {
        true -> {
            !(connectivityRetriever.isConnected() && connectivityRetriever.isConnectedWifi())
        }
        false -> {
            !connectivityRetriever.isConnected()
        }
    }
}

override fun shouldDrop(dispatch: Dispatch): Boolean {
    return false
}
  • BatchingValidator
    如果数据库里面存的数据数量 + 当前要发生的 1 个,小于设置的 batchSize, 则需要放到队列总。
    shouldDrop 默认 false.
override fun shouldQueue(dispatch: Dispatch?): Boolean {
    val count = dispatchStorage.count()
    return batchSettings.maxQueueSize != 0 &&
            count + 1 < batchSettings.batchSize
}

override fun shouldDrop(dispatch: Dispatch): Boolean {
    return false
}

Batching 默认值如下

data class Batching(var batchSize: Int = 1,
                    var maxQueueSize: Int = 100,
                    var expiration: Int = 86400) 

3.0 借鉴的地方

3.1 良好的设计模式

  • 良好的分层设计,将核心和外围拓展功能比较清晰的划分
  • Collector 使用了迭代器设计模式

4.0 其他

4.1 有启发的代码

4.1.2 重试

suspend fun <T> retry(numRetries: Int, timeout: Long, block: suspend (Int) -> T?): T? {
    for (i in 1..numRetries) {
        try {
            return withTimeout(timeout) {
                Logger.dev(BuildConfig.TAG, "Fetching resource; attempt number $i of $numRetries.")
                block(i)
            }
        } catch (e: TimeoutCancellationException) {
            Logger.prod(BuildConfig.TAG, "Timed out, could not fetch resource.")
        }
    }
    return block(0)
}

4.2.2 线程池和协程的结合

private val singleThreadedBackground = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
   
private val backgroundScope = CoroutineScope(singleThreadedBackground)

 // Initialize everything else in the background.
backgroundScope.launch { 
    bootstrap()
}

这样 singleThreadedBackground 就是一个可以重复利用线程池里面的线程执行,并且可以使用协程

4.2.3 CoroutineExceptionHandler 处理异常

// Logger.kt
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
        prod(BuildConfig.TAG, "Caught $exception")
        exception.stackTrace?.let { stackStrace ->
            prod(BuildConfig.TAG, stackStrace.joinToString { element ->
                element.toString() + "\n"
            })
        }
    }

 scope.launch(Logger.exceptionHandler) {
    ...
 }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/64002.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

黄菊华老师,Java Servlet毕业设计毕设辅导课(4):Servlet 实例

Servlet 实例 Servlet 是服务 HTTP 请求并实现 javax.servlet.Servlet 接口的 Java 类。Web 应用程序开发人员通常编写 Servlet 来扩展 javax.servlet.http.HttpServlet&#xff0c;并实现 Servlet 接口的抽象类专门用来处理 HTTP 请求。 Hello World 示例代码 下面是 Servl…

为什么工业设计公司价格这么高?

随着经济的不断增长&#xff0c;各种工业设计公司逐渐出现&#xff0c;但价格不同&#xff0c;有些价格高&#xff0c;有些价格低&#xff0c;让一些人到处比较价格&#xff0c;低价格压低别人的高价格。有些人会想&#xff0c;为什么工业设计公司在设计产品时价格这么高&#…

ORB-SLAM2 ---- Tracking::UpdateLastFrame函数

目录 1.函数作用 2.步骤 3.code 4.函数解释 4.1 利用参考关键帧更新上一帧在世界坐标系下的位姿 4.2 对于双目或rgbd相机&#xff0c;为上一帧生成新的临时地图点 1.函数作用 更新上一帧位姿&#xff0c;在上一帧中生成临时地图点。 单目情况&#xff1a;只计算了上一帧…

【富文本编辑器】简记功能:neditor上传操作时提交额外数据

目录 编辑器下载&#xff08;本文使用版本v2.1.19&#xff09; 功能需求 解决思路 相关代码 调用实例的html neditor.config.js&#xff08;搜索修改内容&#xff1a;/* 设置额外请求参数 */&#xff09; 完成&#xff0c;如有其它方法&#xff0c;欢迎一起讨论 编辑器下…

使用 Fluent Bit 实现云边统一可观测性

本文基于 KubeSphere 可观测性与边缘计算负责人霍秉杰在北美 KubeCon 的 Co-located event Open Observability Day 闪电演讲的内容进行整理。 整理人&#xff1a;米开朗基杨、大飞哥 Fluent Operator 简介 2019 年 1 月 21 日&#xff0c;KubeSphere 社区为了满足以云原生的方…

NX上配置TLD的环境---对opencv的版本没有要求

一、TLD工程编译及运行 1.1 源码下载 网上的TLD有两个版本&#xff0c;一个是Zdenek Kalal自己使用matlabvs混合编程实现的&#xff0c;另外一个是 arthurv利用c和opencv实现的。 我利用的是arthurv版本的Tracking-Learning-Detection 连接&#xff1a;https://github.com/al…

基于Android平台的手机安全卫士的设计与实现

目 录 第1章 引言 1 1.1 研究背景及意义 1 1.2 安全软件的现状 1 1.3 本文主要工作 2 1.4 本文的组织结构 2 第2章 Android的相关技术介绍及分析 3 2.1 搭建Android开发环境 3 2.1.1 搭建Ubuntu系统下Java开发环境 3 2.1.2 搭建Ubuntu系统下Android开发环境 3 2.2 Android项目目…

「企企通」完成Pre-D轮融资,加速采购供应链工业软件和 SaaS 网络生态构建

企企通作为领先的采购供应链工业软件和SaaS生态平台&#xff0c;在一年内再次宣布获得Pre-D轮融资&#xff0c;全年合计融资额达数亿元人民币&#xff0c;目前还有意向投资机构在进行&#xff0c;并开始启动IPO的筹备工作。本轮投资由华映资本独家投资。华映资本是企企通C2轮融…

flutter系列之:flutter中的变形金刚Transform

文章目录简介Transform简介Transform的使用总结简介 虽然我们在开发APP的过程中是以功能为主&#xff0c;但是有时候为了美观或者其他的特殊的需求&#xff0c;需要对组件进行一些变换。在Flutter中这种变换就叫做Transform。 flutter的强大之处在于&#xff0c;可以对所有的…

IBM MQ 通道

一&#xff0c;定义 通道是分布式队列管理器在IBM MQ MQI 客户端和IBM MQ服务器之间或两个IBM MQ服务器之间使用的逻辑通信链接。 通道是提供从一个队列管理器到另一个队列管理器的通信路径的对象。通道在分布式队列中用于将消息从一个队列管理器移动到另一个队列管理器&#x…

计算机网络-应用层详解(持续更新中)

应用层概述 应用层是解决通过应用进程的交互来实现特定网络应用的问题。 应用层是计算机网络体系结构的最顶层&#xff0c;是设计和建立计算机网络的最终目的&#xff0c;也是计算机网络中发展最快的部分。 早期基于文本的应用&#xff08;电子邮件、远程登录、文件传输、新…

百度联合行业头部企业新发5个行业大模型,大模型产业落地路径愈发清晰

本文已在【飞桨PaddlePaddle】公众号平台发布&#xff0c;详情请戳链接&#xff1a;百度联合行业头部企业新发5个行业大模型&#xff0c;大模型产业落地路径愈发清晰 11月30日&#xff0c;由深度学习技术与应用国家工程研究中心主办、飞桨承办的WAVE SUMMIT2022深度学习开发者…

WLAN AP安全策略中WPA认证与WPA2认证的差异

一、安全策略WPA认证&#xff08;PSK认证TKIP加密&#xff09;的案例 组网需求&#xff1a; 设备作为FAT AP&#xff0c;为用户提供WLAN服务&#xff0c;用户可以搜索到名为huawei的无线网络&#xff0c;采用的安全策略为WPA-PSK认证TKIP加密的方式。 组网图如下&#xff1a;…

3 内存访问

内存访问 1 字的存储 寄存器中&#xff1a;16位寄存器存一个字。高8位放高位字节&#xff0c;低8位当低位字节。 内存中&#xff1a;内存单元是字节单元&#xff0c;一个字要用2个连续的内存单元保存。低位字节保存在低地址内存单元&#xff0c;高位字节保存在高地址内存单元…

vmware上的centos8没有网络

目录一、先了解虚拟机的三种网络模式二、目前的网络模式和网络状况三、 解决网络不可用问题一、先了解虚拟机的三种网络模式 虚拟机的三种网络模式 二、目前的网络模式和网络状况 我的虚拟机网络配置 我们要将虚拟机和本机在同一个局域网络里才能入网 进入cmd输入ipconfig…

Java多线程之常用的相关方法总结(线程停止、线程休眠、线程礼让、线程优先级、守护线程等等)

Java多线程之相关常用方法一、线程方法二、线程停止1、思路2、样例三、线程休眠&#xff08;sleep&#xff09;1、思路2、样例四、线程礼让&#xff08;yield&#xff09;1、思路2、样例五、线程强制执行&#xff08;join&#xff09;1、思路2、样例六、观测线程状态1、相关概念…

Mac配置python wind量化接口

首先Mac与Windows的wind配置完全不同&#xff1a; Windows&#xff1a;wind相对容易配置&#xff0c;直接用软件就可以点击并添加配置环境即可Mac配置如下 文章目录Mac上Wind的基本情况Mac配置python Wind量化接口1. 在App Store中下载并打开 “Wind App” 这个应用2. 配置pyt…

带有SPI接口的独立CAN控制器DP2515

DP2515是一款独立控制器局域网络&#xff08;Controller Area Network&#xff0c; CAN&#xff09;协议控制器&#xff0c;完全支持 CAN V2.0B 技术规范。该器件能发送和接收标准和扩展数据帧以及远程帧。 DP2515自带的两个验收屏蔽寄存器和六个验收滤波寄存器可以过滤掉不想要…

MySql使用MyCat分库分表(五)MyCat 管理及监控

视频学习地址&#xff1a;17-尚硅谷-垂直分库_哔哩哔哩_bilibili 笔记参考地址&#xff1a;MySQL 分库分表 | xustudyxus Blog (frxcat.fun) MyCat 管理 Mycat默认开通2个端口&#xff0c;可以在server.xml中进行修改。 8066 数据访问端口&#xff0c;即进行 DML 和 DDL 操…

Java搭建宝塔部署实战毕设项目SSM学生学籍管理系统源码

大家好啊&#xff0c;我是测评君&#xff0c;欢迎来到web测评。 本期给大家带来一套Java开发的毕设项目SSM学生学籍管理系统的源码&#xff0c;适合拿来做毕业设计的同学。可以下载来学习一下&#xff0c;本期把这套系统分享给大家。 技术架构 技术框架&#xff1a;ssm layui…