硬件环境: 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. 线程(Thread)基本用法

    一.线程的调用 1.无参 def run_01(): for i in range(6, 10): print("test01", i) time.sleep(1) th_01 = ...

  2. vs2019中使用Git,新建项目时总提示部分项目位于解决方案文件夹外

    最终还是用Git工具传上去的. 小伙子,用Git Bush或者Git CMD 和Git GUI传吧 我是用Git GUI. Git GUI汉化.感谢大佬 https://blog.csdn.net/u ...

  3. oracle 内置函数(二)字符函数

    主要函数: 大小写转换函数 获取子字符串函数(字符串截取) 获取字符串长度函数 字符串连接函数 去除子字符串函数 字符替换函数 字符串出现次数 字符串按照特定符号拆分多行 一.大小写转换 1.uppe ...

  4. 移除元素-LeetCode27 双指针

    力扣链接:https://leetcode.cn/problems/remove-element/ 题目 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返 ...

  5. mysql-DuplicateUpdate和java的threadpool的"死锁"

    大家千万不要被文章的标题给迷惑了,他两在本篇文章是没有关系的, 今天给大家讲讲最近2个有意思的issue,分享一下我学到的 mysql DuplicateUpdate的用法要注意的点 java的thr ...

  6. JavaEE Day05 JDBC(用Java语言操作数据库)

    今日内容 基本概念 快速入门 对JDBC中各个接口和类的详解 一.基本概念 1.概念:Java Database Connectivity:Java数据库连接,Java语言操作数据库 2.本质:官方( ...

  7. 【每日一题】【遍历orSet】2022年2月1日-NC66 两个链表的第一个公共结点

    描述输入两个无环的单向链表,找出它们的第一个公共结点,如果没有公共节点则返回空.(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的) 输入描述:输入分为是3段,第 ...

  8. 动手实验查看MySQL索引的B+树的高度

    一:文中几个概念 h:统称索引的高度: h1:主键索引的高度: h2:辅助索引的高度: k:非叶子节点扇区个数. 二:索引结构 叶子节点其实是双向链表,而叶子节点内的行数据是单向链表,该图未体现. 磁 ...

  9. LeetCode HOT 100:在排序数组中查找元素的第一个和最后一个位置

    题目:34. 在排序数组中查找元素的第一个和最后一个位置 题目描述: 给你一个递增数组,和一个目标值target,最终返回数组中第一次出现target和最后一次出现target的下标.如果该数组中没有 ...

  10. Vuex极速入门

    01.什么是Vuex? 1.1.为什么需要状态管理? 在复杂的系统中,我们会把系统按照业务逻辑拆分为多个层次.多个模块,采用组件式的开发方式.而此时不同模块.父子模块之间的通信就成了一个问题. 为了解 ...