硬件环境: SLTB010A(BRD4184A Rev A02 / EFR32BG22C224F512IM40)
软件环境: SimplicityStudio5/gecko_sdk_3.2.3
分析工程: Bluetooth Mesh SensorClient

恶补了 BluetoothMesh 相关知识,首次接触 SiliconLabs 芯片,搜全网,中文资料少的可怜,又一人狂啃了官方很多全英文文档,搞明白了烧录、编译、调试等基础知识,但是对软件开发方式还是云里雾里,然后着手分析源码才大致了解 SiliconLabs 的源码框架,总算是知道怎么玩这颗芯片了。

本章文章主要是分析设备已经入网之后的 SensorClinet 上电运行过程,应该是全网独一份。

代码结构:

main 有个 while(1), 不停的查询和从事件队列里面获取 mesh 事件,然后再根据不同的事件进行不同的处理。

void sl_btmesh_step(void)
{
  sl_btmesh_msg_t evt;   uint32_t event_len = sl_btmesh_event_pending_len();
  // For preventing from data loss, the event will be kept in the stack's queue
  // if application cannot process it at the moment.
  if ((event_len == 0) || (!sl_btmesh_can_process_event(event_len))) {
    return;
  }   // Pop (non-blocking) a Bluetooth stack event from event queue.
  sl_status_t status = sl_btmesh_pop_event(&evt);
  if(status != SL_STATUS_OK){
    return;
  }
  sl_btmesh_process_event(&evt);
} void sl_btmesh_process_event(sl_btmesh_msg_t *evt)
{
  sl_btmesh_handle_btmesh_logging_events(evt);
  sl_btmesh_handle_provisioning_decorator_event(evt);
  sl_btmesh_factory_reset_on_event(evt);
  sl_btmesh_handle_sensor_client_on_event(evt);
  sl_btmesh_on_event(evt);
}

源码概况:

主要分析三个事件,在已经入网的情况下,并且有设备节点响应,就只有三个事件:
1. sl_btmesh_evt_node_initialized_id
2. sl_btmesh_evt_sensor_client_descriptor_status_id
3. sl_btmesh_evt_sensor_client_status_id

一、sl_btmesh_evt_node_initialized_id

芯片上电后由蓝牙协议栈完成初始化之后发出的事件(猜测)

分析总结:

1. 如果已经入网, 则初始化传感器客户端模型: sl_btmesh_sensor_client_init();

[sl_btmesh_sensor_client.c]sl_btmesh_handle_sensor_client_on_event(evt);    // 重要:初始化 sensor client 模型
case sl_btmesh_evt_node_initialized_id:
if (evt->data.evt_node_initialized.provisioned)
    mesh_sensor_client_init();
    --> sl_btmesh_sensor_client_init(); // 初始化传感器客户端模型。Sensor Client 没有任何内部配置,它只激活蓝牙网格栈中的模型。

2. 如果已经入网, 则启动单次定时器, 如果未入网则发布广播让配置者知道, 等待被配置入网

[app.c]sl_btmesh_on_event(evt); // 重要: 如果已经被配置, 则启动单次定时器, 如果没有被配置则开启广播, 等待配置。
--> case sl_btmesh_evt_node_initialized_id:
    --> handle_node_initialized_event(&(evt->data.evt_node_initialized));
        if (evt->provisioned)    // 如果已经本设备已经被配置(即已入网)
            sl_simple_timer_start(..., DEVICE_REGISTER_SHORT_TIMEOUT(100), app_update_registered_devices_timer_cb,..., true);
        else                    // 开启广播等待配置入网
            sl_btmesh_node_start_unprov_beaconing(PB_ADV | PB_GATT);

3. 单次定时器会先清空注册的设备列表, 并记录感兴趣的温度传感器ID, 然后启动搜索指定传感器ID,再启动周期2秒的定时器请求数据

[app.c]app_update_registered_devices_timer_cb()
--> [sl_btmesh_sensor_client.c]sl_btmesh_sensor_client_update_registered_devices(property:current_property(0x004f))
    registered_devices.count = 0; // 清空已注册设备的列表
    memset(registered_devices.address_table, 0, sizeof(registered_devices.address_table));
    registering_property = property; // 将要默认要注册的温度传感器ID放入全局变量 0x004f
    sl_btmesh_sensor_client_on_discovery_started(property_id:property);  // 这里只输出了打印信息
    --> app_log("BT mesh Sensor Device discovery is started. (property_id: 0x%04x)\r\n", property_id);
    // 重点: 启动探索指定传感器 0x004f, 如果有节点响应, 会触发 sl_btmesh_evt_sensor_client_descriptor_status_id 事件
    sc = sl_btmesh_sensor_client_get_descriptor(PUBLISH_ADDRESS(0x0000), BTMESH_SENSOR_CLIENT_MAIN(0), IGNORED(0), NO_FLAGS, property);
    if (SL_STATUS_OK == sc) {
        log_info(SENSOR_CLIENT_LOGGING_START_REGISTERING_DEVICES, property);
        --> #define SENSOR_CLIENT_LOGGING_START_REGISTERING_DEVICES "Registration of devices for property ID %4.4x started\r\n"
    else
        log_btmesh_status_f(sc, SENSOR_CLIENT_LOGGING_REGISTERING_DEVICES_FAILED, property);
        --> #define SENSOR_CLIENT_LOGGING_REGISTERING_DEVICES_FAILED "Registration of devices for property ID %4.4x failed\r\n"
// 重点: 启动周期为 2 秒的定时器定时发出获取数据的请求
--> sl_simple_timer_start(..., SENSOR_DATA_TIMEOUT(2000), app_sensor_data_timer_cb, ..., true); 

4. 周期2秒的定时器,会以 2 秒的频率发出获取所有节点的指定传感器数据(这里是温度传感器 0x004f)

[app.c]app_sensor_data_timer_cb
    --> sl_btmesh_sensor_client_get_sensor_data(property:current_property(0x004f));
        sc = sl_btmesh_sensor_client_get(PUBLISH_ADDRESS(0x0000), BTMESH_SENSOR_CLIENT_MAIN(0), IGNORED(0), NO_FLAGS, property);
        if (SL_STATUS_OK == sc)
            log_info(SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY, property);
            --> #define SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY "Get Sensor Data from property ID %4.4x started\r\n"
        else 
            log_btmesh_status_f(sc, SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY_FAIL, property);
            --> #define SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY_FAIL "Get Sensor Data from property ID %4.4x failed\r\n"

源码走读:

[main.c]main() -> while(1) // 上电后协议栈会触发该事件
--> [sl_system_process_action.c]sl_system_process_action()
--> [sl_event_handler.c]sl_stack_process_action()
--> [sl_btmesh.c]sl_btmesh_step()
sl_btmesh_pop_event(&evt); // 取出 mesh 事件类型
--> sl_btmesh_process_event(&evt); // 处理 mest 事件类型
--> [sl_btmesh_event_log.c]sl_btmesh_handle_btmesh_logging_events(evt); // 这里仅仅只是用于打印当前收到的事件类型
--> case sl_btmesh_evt_node_initialized_id:
app_log("evt:mesh_node_initialized\r\n");
--> [sl_btmesh_provisioning_decorator.c]sl_btmesh_handle_provisioning_decorator_event(evt); // 打印输出是否已经被配置过
--> case sl_btmesh_evt_node_initialized_id:
--> sl_btmesh_on_provision_init_status(provisioned, address, iv_index);
--> if (provisioned) // 如果已经本设备已经被配置(即已入网)
app_show_btmesh_node_provisioned(address, iv_index);
--> app_log("BT mesh node is provisioned (address: 0x%04x, iv_index: 0x%lx)\r\n", address, iv_index);
else
app_log("BT mesh node is unprovisioned, started unprovisioned " "beaconing...\r\n");
--> [sl_btmesh_factory_reset.c]sl_btmesh_factory_reset_on_event(evt); // 忽略:只处理重置事件 sl_btmesh_evt_node_reset_id
--> [sl_btmesh_sensor_client.c]sl_btmesh_handle_sensor_client_on_event(evt); // 重要:初始化 sensor client 模型
case sl_btmesh_evt_node_initialized_id:
if (evt->data.evt_node_initialized.provisioned)
mesh_sensor_client_init();
--> sl_btmesh_sensor_client_init(); // 初始化传感器客户端模型。Sensor Client 没有任何内部配置,它只激活蓝牙网格栈中的模型。
--> [app.c]sl_btmesh_on_event(evt); // 重要: 如果已经被配置, 则启动单次定时器, 如果没有被配置则开启广播, 等待配置。
case sl_btmesh_evt_node_initialized_id:
--> handle_node_initialized_event(&(evt->data.evt_node_initialized));
if (evt->provisioned) // 如果已经本设备已经被配置(即已入网)
sl_simple_timer_start(..., DEVICE_REGISTER_SHORT_TIMEOUT(100), app_update_registered_devices_timer_cb,..., true);
else // 开启广播等待配置入网
sl_btmesh_node_start_unprov_beaconing(PB_ADV | PB_GATT);

主要是启动探索指定传感器并且启动以2秒为周期定时器来请求获取传感器数据

[app.c]app_update_registered_devices_timer_cb()
--> [sl_btmesh_sensor_client.c]sl_btmesh_sensor_client_update_registered_devices(property:current_property(0x004f))
registered_devices.count = 0; // 清空已注册设备的列表
memset(registered_devices.address_table, 0, sizeof(registered_devices.address_table));
registering_property = property; // 将要默认要注册的温度传感器ID放入全局变量 0x004f
sl_btmesh_sensor_client_on_discovery_started(property_id:property); // 这里只输出了打印信息
--> app_log("BT mesh Sensor Device discovery is started. (property_id: 0x%04x)\r\n", property_id);
// 重点: 启动探索指定传感器 0x004f, 如果有节点响应, 会触发 sl_btmesh_evt_sensor_client_descriptor_status_id 事件
sc = sl_btmesh_sensor_client_get_descriptor(PUBLISH_ADDRESS(0x0000), BTMESH_SENSOR_CLIENT_MAIN(0), IGNORED(0), NO_FLAGS, property);
if (SL_STATUS_OK == sc) {
log_info(SENSOR_CLIENT_LOGGING_START_REGISTERING_DEVICES, property);
--> #define SENSOR_CLIENT_LOGGING_START_REGISTERING_DEVICES "Registration of devices for property ID %4.4x started\r\n"
else
log_btmesh_status_f(sc, SENSOR_CLIENT_LOGGING_REGISTERING_DEVICES_FAILED, property);
--> #define SENSOR_CLIENT_LOGGING_REGISTERING_DEVICES_FAILED "Registration of devices for property ID %4.4x failed\r\n"
// 重点: 启动周期为 2 秒的定时器定时发出获取数据的请求
--> sl_simple_timer_start(..., SENSOR_DATA_TIMEOUT(2000), app_sensor_data_timer_cb, ..., true);

周期为 2 秒的定时器定时获取 Server 模型中的所有传感器数据

// 周期为 2 秒的定时器定时获取 Server 模型中的所有传感器数据
[app.c]app_sensor_data_timer_cb
--> sl_btmesh_sensor_client_get_sensor_data(property:current_property(0x004f));
sc = sl_btmesh_sensor_client_get(PUBLISH_ADDRESS(0x0000), BTMESH_SENSOR_CLIENT_MAIN(0), IGNORED(0), NO_FLAGS, property);
if (SL_STATUS_OK == sc)
log_info(SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY, property);
--> #define SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY "Get Sensor Data from property ID %4.4x started\r\n"
else
log_btmesh_status_f(sc, SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY_FAIL, property);
--> #define SENSOR_CLIENT_LOGGING_GET_DATA_FROM_PROPERTY_FAIL "Get Sensor Data from property ID %4.4x failed\r\n"

二、sl_btmesh_evt_sensor_client_descriptor_status_id

服务器节点响应事件, 服务器如果节点不存在, 则不会有此事件

分析总结:
1. 当启动搜索传感器的时候,若有节点响应,才会触发此事件。
2. 会检查当前的节点属性是否为感兴趣的温度传感器。
3. 并且检查该节点是否在设备列表内,不在就加入到设备列表内。
4. 这设备列表很重要,只有在这列表内的节点的数据才不会被忽略。

if (descriptor.property_id == registering_property && number_of_devices < SENSOR_CLIENT_DISPLAYED_SENSORS(5)
    && !mesh_address_already_exists(&registered_devices, evt->server_address)) 
    registered_devices.address_table[number_of_devices] = evt->server_address;
    registered_devices.count = number_of_devices + 1;

源码走读:

[main.c]main() -> while(1) // 该事件由 sl_btmesh_sensor_client_get_descriptor() 触发
--> [sl_system_process_action.c]sl_system_process_action()
--> [sl_event_handler.c]sl_stack_process_action()
--> [sl_btmesh.c]sl_btmesh_step()
sl_btmesh_pop_event(&evt); // 取出 mesh 事件类型
--> sl_btmesh_process_event(&evt); // 处理 mest 事件类型
--> [sl_btmesh_event_log.c]sl_btmesh_handle_btmesh_logging_events(evt); // 这里仅仅只是用于打印当前收到的事件类型
--> case sl_btmesh_evt_sensor_client_descriptor_status_id:
app_log("evt:mesh_sensor_client_descriptor_status\r\n");
--> [sl_btmesh_provisioning_decorator.c]sl_btmesh_handle_provisioning_decorator_event(evt); // 忽略:该函数未处理这个事件
--> [sl_btmesh_factory_reset.c]sl_btmesh_factory_reset_on_event(evt); // 忽略:只处理重置事件 sl_btmesh_evt_node_reset_id
--> [sl_btmesh_sensor_client.c]sl_btmesh_handle_sensor_client_on_event(evt);
case sl_btmesh_evt_sensor_client_descriptor_status_id:
--> handle_sensor_client_events(&(evt->data.evt_sensor_client_descriptor_status));
case sl_btmesh_evt_sensor_client_descriptor_status_id:
--> handle_sensor_client_descriptor_status(&(evt->data.evt_sensor_client_descriptor_status));
--> [sl_btmesh_sensor.c]mesh_lib_sensor_descriptors_from_buf(&descriptor, evt->descriptors.data, SIZE_OF_DESCRIPTOR); // 数据转换为传感器描述符
// 如果当前 mesh 事件的属性ID就是我们要的 registering_property:0x004f, 并且未在设备注册列表内则加入该列表
uint8_t number_of_devices = registered_devices.count;
if (descriptor.property_id == registering_property && number_of_devices < SENSOR_CLIENT_DISPLAYED_SENSORS(5)
&& !mesh_address_already_exists(&registered_devices, evt->server_address))
registered_devices.address_table[number_of_devices] = evt->server_address;
registered_devices.count = number_of_devices + 1;
--> [app_out_log.c]sl_btmesh_sensor_client_on_new_device_found(descriptor.property_id, evt->server_address);
--> app_log("BT mesh Sensor Device (address: 0x%04x, property_id: 0x%04x) is found.\r\n", address, property_id);

三、sl_btmesh_evt_sensor_client_status_id

服务器节点状态响应事件, 服务器如果节点状态未响应或不存在, 则不会有此事件

分析总结:
1. 该事件所附带的数据包可能携带了多个节点数据, 因此需要逐一拆分
2. 过滤不在设备列表内的节点,只留下设备列表内并且是感兴趣的温度传感器数据才会处理。

源码走读:

[main.c]main() -> while(1) // 该事件由 sl_btmesh_sensor_client_get_descriptor() 触发
--> [sl_system_process_action.c]sl_system_process_action()
--> [sl_event_handler.c]sl_stack_process_action()
--> [sl_btmesh.c]sl_btmesh_step()
sl_btmesh_pop_event(&evt); // 取出 mesh 事件类型
--> sl_btmesh_process_event(&evt); // 处理 mest 事件类型
--> [sl_btmesh_event_log.c]sl_btmesh_handle_btmesh_logging_events(evt); // 这里仅仅只是用于打印当前收到的事件类型
--> case sl_btmesh_evt_sensor_client_status_id: // 打印输出当前响应的地址是群组地址、单播地址还是虚拟地址
app_log("evt:mesh_sensor_client_status %s\r\n", (evt->data.evt_sensor_client_status.client_address & 0xC000) == 0xC000 ? "(group broadcast)" : (evt->data.evt_sensor_client_status.client_address & 0x1000) == 0 ? "(unicast)" : "(virtual)");
--> [sl_btmesh_provisioning_decorator.c]sl_btmesh_handle_provisioning_decorator_event(evt); // 忽略:该函数未处理这个事件
--> [sl_btmesh_factory_reset.c]sl_btmesh_factory_reset_on_event(evt); // 忽略:只处理重置事件 sl_btmesh_evt_node_reset_id
--> [sl_btmesh_sensor_client.c]sl_btmesh_handle_sensor_client_on_event(evt); // 重点: 这个函数内会提取匹配Server的传感器数据
case sl_btmesh_evt_sensor_client_status_id:
--> handle_sensor_client_events(&(evt->data.evt_sensor_client_descriptor_status));
case sl_btmesh_evt_sensor_client_status_id:
--> static void handle_sensor_client_status(sl_btmesh_evt_sensor_client_status_t *evt)
{
uint8_t *sensor_data = evt->sensor_data.data;
uint8_t data_len = evt->sensor_data.len;
uint8_t pos = 0;
while (pos < data_len)
{ // 遍历各个 Server 节点消息
if (data_len - pos > PROPERTY_ID_SIZE) {
mesh_device_properties_t property_id = (mesh_device_properties_t)(sensor_data[pos] + (sensor_data[pos + 1] << 8));
uint8_t property_len = sensor_data[pos + PROPERTY_ID_SIZE];
uint8_t *property_data = NULL;
// 只处理在设备注册表内的 Service 节点消息
if (mesh_address_already_exists(&registered_devices, evt->server_address)) {
sl_btmesh_sensor_client_data_status_t status;
uint16_t address;
uint8_t sensor_idx;
if (property_len && (data_len - pos > PROPERTY_HEADER_SIZE)) {
property_data = &sensor_data[pos + PROPERTY_HEADER_SIZE];
}
address = evt->server_address;
sensor_idx = mesh_get_sensor_index(&registered_devices, address);
status = SL_BTMESH_SENSOR_CLIENT_DATA_NOT_AVAILABLE;
switch (property_id) {
......
case PRESENT_AMBIENT_TEMPERATURE:
temperature_8_t temperature = SL_BTMESH_SENSOR_CLIENT_TEMPERATURE_UNKNOWN;
if (property_len == 1) {
mesh_device_property_t new_property = mesh_sensor_data_from_buf(PRESENT_AMBIENT_TEMPERATURE, property_data);
temperature = new_property.temperature_8;
if (temperature == SL_BTMESH_SENSOR_CLIENT_TEMPERATURE_UNKNOWN) {
status = SL_BTMESH_SENSOR_CLIENT_DATA_UNKNOWN;
} else {
status = SL_BTMESH_SENSOR_CLIENT_DATA_VALID;
}
} else {
status = SL_BTMESH_SENSOR_CLIENT_DATA_NOT_AVAILABLE;
}
--> [app_out_log.c]sl_btmesh_sensor_client_on_new_temperature_data(sensor_idx, address, status, temperature);
if (PRESENT_AMBIENT_TEMPERATURE != app_get_current_property()) {
return;
}
if (SL_BTMESH_SENSOR_CLIENT_DATA_VALID == status) {
app_log("BT mesh Sensor Temperature (from 0x%04x): %3d.%1d 掳C\r\n", address, INT_TEMP(temperature), FRAC_TEMP(temperature));
} else if (SL_BTMESH_SENSOR_CLIENT_DATA_UNKNOWN == status) {
app_log("BT mesh Sensor Temperature (from 0x%04x): UNKNOWN\r\n", address);
} else {
app_log("BT mesh Sensor Temperature (from 0x%04x): NOT AVAILABLE\r\n", address);
}
break;
......
}
pos += PROPERTY_HEADER_SIZE + property_len;
}
} else {
pos = data_len;
}
}
}

四、按键处理逻辑

以上即是核心的逻辑处理, 至于按键触发的逻辑,有了上面的基础,就简单很多了。
app.c 通过重定义 app_button_press.c 的按键处理回调实现拦截按键事件

[app_button_press.c ]SL_WEAK void app_button_press_cb(uint8_t button, uint8_t duration)
{
(void)button;
(void)duration;
} [app.c]void app_button_press_cb(uint8_t button, uint8_t duration)
{
(void)duration;
if (duration == APP_BUTTON_PRESS_NONE) {
return;
}
// button pressed
if (button == BUTTON_PRESS_BUTTON_0) {
if (duration < APP_BUTTON_PRESS_DURATION_LONG) {
app_log("PB0 pressed\r\n");
sensor_client_change_current_property(); // 短按按键则调整感兴趣的传感器类型
} else {
app_log("PB0 long pressed\r\n");
update_registered_devices(); // 长按则重新启动搜索指定传感器ID
}
} else if (button == BUTTON_PRESS_BUTTON_1) {
app_log("PB1 pressed\r\n");
update_registered_devices();
}
} [app.c]static void sensor_client_change_current_property(void)
{
switch (current_property) {
case PRESENT_AMBIENT_TEMPERATURE:
current_property = PEOPLE_COUNT;
break;
case PEOPLE_COUNT:
current_property = PRESENT_AMBIENT_LIGHT_LEVEL;
break;
case PRESENT_AMBIENT_LIGHT_LEVEL:
current_property = PRESENT_AMBIENT_TEMPERATURE;
break;
default:
app_log("Unsupported property ID change\r\n");
break;
}
} // 参考 sl_btmesh_evt_node_initialized_id 的第三第四步骤
void update_registered_devices(void)
{
sl_status_t sc;
sl_btmesh_sensor_client_update_registered_devices(current_property);
sc = sl_simple_timer_start(&app_sensor_data_timer,SENSOR_DATA_TIMEOUT, app_sensor_data_timer_cb, NO_CALLBACK_DATA,true);
app_assert_status_f(sc, "Failed to start periodic timer\n");
}

五、芯片的开发方式

1. 可以发现最后的结果都是在当前工程的 app_out_log.c 输出
2. 而 app_out_log.c 是通过 sl_btmesh_sensor_client.c 里面 SL_WEAK 修饰的接口来截取结果
3. 当工程中某个源文件有与 SL_WEAK 修饰的函数相同时, 编译会只选择编译该源文件的函数实现。
4. 所以要熟悉 sl_btmesh_sensor_client.c 的逻辑实现,需要拿到什么结果就重新实现 SL_WEAK 修饰的函数即可。

【分析笔记】SiliconLabs EFR32BG22 Bluetooth Mesh SensorClient 源码分析的更多相关文章

  1. 并发编程学习笔记(9)----AQS的共享模式源码分析及CountDownLatch使用及原理

    1. AQS共享模式 前面已经说过了AQS的原理及独享模式的源码分析,今天就来学习共享模式下的AQS的几个接口的源码. 首先还是从顶级接口acquireShared()方法入手: public fin ...

  2. 并发编程学习笔记(8)----ThreadLocal的使用及源码分析

    1. ThreadLocal的理解 ThreadLocal,顾名思义,就是线程的本地变量,ThreadLocal会为每个线程创建一个本地变量副本,使得使用ThreadLocal管理的变量在多线程的环境 ...

  3. STL 源码分析《2》----nth_element() 使用与源码分析

    Select 问题: 在一个无序的数组中 找到第 n 大的元素. 思路 1: 排序,O(NlgN) 思路 2: 利用快排的 RandomizedPartition(), 平均复杂度是 O(N) 思路 ...

  4. Netty源码分析 (三)----- 服务端启动源码分析

    本文接着前两篇文章来讲,主要讲服务端类剩下的部分,我们还是来先看看服务端的代码 /** * Created by chenhao on 2019/9/4. */ public final class ...

  5. HashMap源码分析(史上最详细的源码分析)

    HashMap简介 HashMap是开发中使用频率最高的用于映射(键值对 key value)处理的数据结构,我们经常把hashMap数据结构叫做散列链表: ObjectI entry<Key, ...

  6. Memcached源码分析

    作者:Calix,转载请注明出处:http://calixwu.com 最近研究了一下memcached的源码,在这里系统总结了一下笔记和理解,写了几 篇源码分析和大家分享,整个系列分为“结构篇”和“ ...

  7. ffplay源码分析1-概述

    本文为作者原创,转载请注明出处:https://www.cnblogs.com/leisure_chn/p/10301215.html ffplay是一个很简单的播放器,但是初次接触仍会感到概念和细节 ...

  8. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  9. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

  10. 开源分布式数据库中间件MyCat源码分析系列

    MyCat是当下很火的开源分布式数据库中间件,特意花费了一些精力研究其实现方式与内部机制,在此针对某些较为重要的源码进行粗浅的分析,希望与感兴趣的朋友交流探讨. 本源码分析系列主要针对代码实现,配置. ...

随机推荐

  1. 独立按键控制led灯

    #include "regx51.h"typedef unsigned int u16; void delay_us(u16 time){ while(time--){} }voi ...

  2. 记录因Sharding Jdbc批量操作引发的一次fullGC

    周五晚上告警群突然收到了一条告警消息,点开一看,应用 fullGC 了. 于是赶紧联系运维下载堆内存快照,进行分析. 内存分析 使用 MemoryAnalyzer 打开堆文件 mat 下载地址:htt ...

  3. 从0到1搭建redis6.0.7

    redis集群搭建 一.安装redis 源码安装: 1.下载源码包: wget http://download.redis.io/releases/redis-6.0.7.tar.gz 2.解压到指定 ...

  4. 如何在 .NET MAUI 中加载 json 文件?

    引言: 按core传统方式添加 AddJsonFile("appsettings.json") 在windows平台和ssr工作正常,但是在 ios 和 android 无法用这种 ...

  5. Hashcat使用指南

    Hashcat使用指南 免责声明: 0×01 Hashcat破解linux shadow的密码-首先了解shadow文件到底是什么? 0×02 hashcat的使用 参数补充: -m 参数 -a 参数 ...

  6. Linux内存泄露案例分析和内存管理分享

    作者:李遵举 一.问题 近期我们运维同事接到线上LB(负载均衡)服务内存报警,运维同事反馈说LB集群有部分机器的内存使用率超过80%,有的甚至超过90%,而且内存使用率还再不停的增长.接到内存报警的消 ...

  7. Go实现常用软件设计模式二:工厂模式

    目录: 举个栗子 概念介绍 使用场景 1.举个栗子 类图 ``` @startuml'https://plantuml.com/class-diagramclass Elephant { String ...

  8. 数据结构初阶--顺序表(讲解+C++类模板实现)

    顺序的概念与结构 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储.在数组上完成数据的增删查改. 一般分为两种:静态顺序表和动态顺序表 静态顺序表 #define ...

  9. qtcreator配置cmake+mingw开发环境

    环境准备 cmake 添加到PATH环境变量. mingw: 这里我用的mingw是Qt自带的,我将其添加到PATH环境变量中. 在cmd中执行 mingw32-make.exe 和 cmake查看命 ...

  10. 一文带你了解 Spring 的@Enablexxx 注解

    layout: post categories: Java title: 一文带你了解 Spring 的@Enablexxx 注解 tagline: by 子悠 tags: - 子悠 前面的文章给大家 ...