硬件环境: 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. 前后端分离项目(十一):实现"删"功能(前后端)

    好家伙,本篇介绍如何实现"删"功能 来看效果,  数据库 (自然是没什么毛病) "增"搞定了,其实"删"非常简单 (我不会告诉你我是为了水一 ...

  2. vue3中使用computed

    演示示例(vant组件库的轮播图): <van-swipe :loop="false" :width="150" class="my-Swipe ...

  3. Day11.2:标签的使用

    标签的使用 当我们在嵌套语句中,例如当我们在for的嵌套循环语句中,想要终止或重新开始当前循环以外的循环的时候,单独仅靠break和continue和还不够,需要在我们想要作用的循环语句处加上一个标签 ...

  4. 版本控制工具Git介绍-01

    使用版本控制工具是为了方便团队开发,比如多人共同维护一个项目的时候,用版本控制工具可以很方便的维护项目代码,如果哪天你改了一个版本,出问题了,我们也可以很快的找到你改了什么,这里介绍使用比较多的版本控 ...

  5. python安装/环境变量配置/多版本共存

    Python学习之路Day02: 一.今日学习内容概括: 计算机五大组成部分详解 计算机三大核心硬件 操作系统 编程与编程语言 编程语言的发展 编程语言分类 python解释器 Python Pyth ...

  6. xml中出现< >&等特殊字符如何存储

    特殊字符用下面对应得符号代替. < <= > >= & ' " < <= > >= & &apos; "

  7. 《浅谈亚 log 数据结构在 OI 中的应用》阅读随笔

    这篇又长长长了! \(8435\to 8375\to 9729\) 早就馋这篇了!终于学了( 压位 Trie 确实很好写啊 但是总感觉使用范围不是很广的样子 似乎是见的题少 原文里都在用 \(\log ...

  8. BFS算法套路框架

    一.概念 1.定义 Broad First Search 2.与DFS区别 BFS找到的路径最短 3.本质 找出图中从起点到终点的最近距离 二.二叉树的最小高度111 1.代码 /** * Defin ...

  9. K8S 核心组件 kubelet 与 kube-proxy 分析

    kubelet kubelet 进程用于处理master 下发的任务, 管理pod 中的容器, 注册 自身所在的节点. 节点管理 启动参数说明 --register-node #如果设置为true 则 ...

  10. 简易博客页面小项目 html css

    项目预览 代码 html: <!DOCTYPE html> <html lang="en"> <head> <meta charset=& ...