当前位置: 首页 > news >正文

【android bluetooth 协议分析 06】【l2cap详解 11】【l2cap连接超时处理逻辑介绍】

我们在使用蓝牙的过程中, 当上层 应用 断开所有的 profile 后, 协议栈就会帮我们下发 disconnect 命令。本节就让笨叔, 带大家一起梳理这块内容,具体在协议栈如何处理的。
在这里插入图片描述

梳理开始前, 先思考一下。 我们为什么要梳理这块内容。 这个不应该是一个常规操作吗? 还需要梳理吗?

  • 非也非也,请听我娓娓道来。

1. 为什么要梳理清楚 L2CAP 连接(ACL链路)超时处理逻辑?

L2CAP 的连接超时处理,不仅仅是个“定时器逻辑”,而是:

整个蓝牙协议栈在资源管理、连接控制、安全性保障、以及系统功耗控制中的“中枢神经系统”。

下面我来从5个方面解释清楚它在协议栈中为何这么重要。


1. 蓝牙连接资源是“有限”的,需要靠 L2CAP 控制其释放

在蓝牙中,每条 ACL 链路占用资源包括:

  • 控制器侧:连接上下文、信道信息、加密状态、时钟同步…
  • Host 侧(协议栈):LCB、CCB、定时器、缓存队列、控制块结构…

这些资源非常有限,尤其在嵌入式设备中:

L2CAP 层必须主动感知“连接是否还在使用”,从而合理断开未使用连接释放资源。

梳理超时处理逻辑,有助于理解:

  • 哪些行为会被认定为“空闲”?
  • 哪些信道不会触发断开?
  • 是整个 ACL 链路断开,还是仅关闭某个信道?

2. 蓝牙协议栈的功耗控制依赖 L2CAP 的空闲感知机制

在 BLE 中,系统“保持连接”的代价比 Wi-Fi 小,但也不是零:

  • 广播间隔、连接间隔、握手仍然需要耗电
  • Android 中很多 BLE 外设(手环、门锁)其实只在需要时才通信

所以:

L2CAP 的空闲超时判断直接决定是否断开连接,从而达到省电目的。

比如:

  • l2cu_no_dynamic_ccbs() 就是发现没有动态信道后,考虑是否断开;
  • 配合 L2CA_SetIdleTimeoutByBdAddr() 动态调整断开策略。

3. ACL连接与应用层“业务活跃状态”并不同步,L2CAP需要“感知”空闲

有些情况如下:

  • GATT连接已经建立,但用户没有发任何请求(无动态信道);
  • 连接还在,但只是维持状态同步或偶尔广播;

这时,只有 L2CAP 层能根据是否有活跃的 CCB/FIXED_CHNL来判断是否真的需要继续保持连接。

如果不理会,会出现“连接假死”、“连接不释放”、“电池耗光”等问题。


4. 理解 L2CAP 的超时机制有助于掌握 Bluetooth 的连接生命周期设计思想

从 Host 层角度,整个l2cap连接生命周期基本是:

创建配置打开使用中空闲超时关闭

其中,“空闲超时”是“断开”阶段的主要触发点。这个阶段必须明确:

  • 谁负责判断空闲?
  • 是断动态信道?固定信道?整个连接?
  • 配对中是否允许断开?
  • 如何设置 timeout(API)?

因此:

理解超时机制,就是理解连接如何“自然死亡”的机制。

而不是“上层手动断开”的极端处理方式。


5. 安全和用户体验方面的逻辑也依赖这个判断链

  • 如果在 Pairing 过程中就误判“超时”断开,可能导致设备连不上;
  • 如果不设置合理 timeout,连接会“挂死”,影响用户体验;
  • 某些隐私或认证敏感设备(比如车钥匙、门锁),不能允许连接长时间闲置。

6. 总结:理解超时机制的价值

方面原因说明
资源管理限定的连接资源,需及时释放未使用连接
功耗控制超时断开连接节省功耗,是低功耗蓝牙核心设计理念
状态判定L2CAP 能准确判定“是否真正空闲”,不依赖应用主动断开
生命周期理解L2CAP 是连接生命周期的“断开阶段”的判断核心
安全&体验错误的超时设置会导致连接异常,影响配对、通信、稳定性

2. 调用路径

在实际协议栈中, 大部分会通过 如下 几个分支来触发 调用 检查是否要 启动 acl 链路断开策略。

1. L2CA_SetIdleTimeoutByBdAddr -> l2cu_no_dynamic_ccbs2. L2CA_SendFixedChnlData  -> l2cu_no_dynamic_ccbs3. L2CA_SetLeGattTimeout -> l2cu_no_dynamic_ccbs4. l2cu_release_ccb -> l2cu_no_dynamic_ccbs

我们分别对 这四个函数展开讲解一下。

1. L2CA_SetIdleTimeoutByBdAddr

/********************************************************************************* Function         L2CA_SetIdleTimeoutByBdAddr** Description      Higher layers call this function to set the idle timeout for*                  a connection. The "idle timeout" is the amount of time that*                  a connection can remain up with no L2CAP channels on it.*                  A timeout of zero means that the connection will be torn*                  down immediately when the last channel is removed.*                  A timeout of 0xFFFF means no timeout. Values are in seconds.*                  A bd_addr is the remote BD address. If bd_addr =*                  RawAddress::kAny, then the idle timeouts for all active*                  l2cap links will be changed.** Returns          true if command succeeded, false if failed** NOTE             This timeout applies to all logical channels active on the*                  ACL link.******************************************************************************/
/*
参数说明:bd_addr:目标设备的蓝牙地址。如果是 RawAddress::kAny,表示设置所有当前连接的设备。timeout:空闲超时的秒数,单位是秒:0:一旦无通道立即断开。0xFFFF:永不超时。transport:传输类型(BR/EDR 还是 LE)。*/
bool L2CA_SetIdleTimeoutByBdAddr(const RawAddress& bd_addr, uint16_t timeout,tBT_TRANSPORT transport) {if (bluetooth::shim::is_gd_l2cap_enabled()) {// 如果启用了新的 Gabeldorsche L2CAP(简称 GD L2CAP),就走 shim 层封装后的新实现,不使用 legacy L2CAP。return bluetooth::shim::L2CA_SetIdleTimeoutByBdAddr(bd_addr, timeout,transport);}tL2C_LCB* p_lcb; // 定义一个指向 LCB(Link Control Block)的指针。每个连接的对端 BD_ADDR 都会有一个 LCB 实例管理状态。if (RawAddress::kAny != bd_addr) { // 如果是设置 单个设备地址的超时(而不是全部)p_lcb = l2cu_find_lcb_by_bd_addr(bd_addr, transport); // 查找该设备的 LCB 实例,如果找不到说明未连接或无效if ((p_lcb) && (p_lcb->in_use) && (p_lcb->link_state == LST_CONNECTED)) { // LCB 找到了;该 LCB 处于活跃状态; 并且已经处于 连接状态p_lcb->idle_timeout = timeout; // 设置该 LCB 的超时值(单位秒,会在后面转为 ms)// 若当前该链路上没有动态信道(即 GATT、AVCTP 等都已释放)// 立即触发检查:l2cu_no_dynamic_ccbs() 函数将分析是否要定时断开连接,内部会根据 fixed channel 状态判断 idle timeoutif (!p_lcb->ccb_queue.p_first_ccb) l2cu_no_dynamic_ccbs(p_lcb);} elsereturn false; // 否则设置失败,返回 false} else { // 进入此分支说明调用者希望 批量设置所有活动 L2CAP 链路的 idle timeoutint xx;tL2C_LCB* p_lcb = &l2cb.lcb_pool[0]; // 遍历所有 l2cb.lcb_pool 中的 LCB 实例。l2cb.lcb_pool 是 LCB 的对象池数组for (xx = 0; xx < MAX_L2CAP_LINKS; xx++, p_lcb++) {// 对数组中所有 LCB 实例做处理if ((p_lcb->in_use) && (p_lcb->link_state == LST_CONNECTED)) {// 设置该连接的空闲超时时间。p_lcb->idle_timeout = timeout;// 同样,如果这个连接上已经没有任何动态信道,立即触发 idle 超时检查流程if (!p_lcb->ccb_queue.p_first_ccb) l2cu_no_dynamic_ccbs(p_lcb);}// 跳过未使用或未连接状态的 LCB}}return true;
}
项目内容
函数目的设置某连接的空闲超时时间,用于控制 ACL 链路是否在无信道使用时断开
控制对象所有 L2CAP 信道(动态 + 固定)都未使用后,才进入超时计时逻辑
触发函数如果立即没有 CCB,会调用 l2cu_no_dynamic_ccbs() 进一步决定是否启动断开定时器
关键用途- 精细控制连接生命周期- 动态管理资源释放时机- 实现智能断开策略- 节省功耗,提升系统效率

功能

该函数允许上层(如 GATT、AVCTP 等)设置某个 ACL 链路(由 bd_addr 指定)的“空闲超时时间”。
如果该链路上的所有 L2CAP 通道(固定 + 动态)都关闭了(空闲),并且没有禁止 idle timeout,
就会在 timeout 秒后自动断开该 ACL 链路(通过 l2cu_no_dynamic_ccbs 实现)。

简单说:
设置某个远端设备(由 Bluetooth 地址指定)的 ACL 连接的空闲超时时间。
如果一段时间内没有数据流动,根据这个超时设定,可以断开连接以节省资源(特别是 BLE 连接)。

典型调用时机

  • GATT(Generic Attribute Profile)建立连接后设置超时。比如连接上 BLE 设备后,应用想指定多长时间无业务通信就自动断开。

  • 系统为了节能,在不同连接策略下动态调整超时时间。

典型调用位置(AOSP例子)

  • 当 GATT Client 成功连接到 GATT Server 后,例如 gatt_cl.cc 里连接成功后就会设置:
    L2CA_SetIdleTimeoutByBdAddr(bda, GATT_LINK_IDLE_TIMEOUT, BT_TRANSPORT_LE);

  • 当 Pairing(配对)完成后修改超时设置

真实场景举例

  • 手机 App 连接智能手表完成数据同步后,可以调用此接口设置,比如 30 秒空闲无操作就断开 BLE,避免蓝牙资源浪费。

2. L2CA_SendFixedChnlData

  • 作用:往指定 固定信道(fixed channel)发送数据(p_buf)到指定远端设备地址(rem_bda)。

  • 返回值

    • L2CAP_DW_SUCCESS:发送成功。

    • L2CAP_DW_FAILED:发送失败。

// system/stack/l2cap/l2c_api.cc/*********************************************************************************  Function        L2CA_SendFixedChnlData**  Description     Write data on a fixed channel.**  Parameters:     Fixed CID*                  BD Address of remote*                  Pointer to buffer of type BT_HDR** Return value     L2CAP_DW_SUCCESS, if data accepted*                  L2CAP_DW_FAILED,  if error*******************************************************************************/
uint16_t L2CA_SendFixedChnlData(uint16_t fixed_cid, const RawAddress& rem_bda,BT_HDR* p_buf) {if (bluetooth::shim::is_gd_l2cap_enabled()) { // 兼容新版 GD-L2CAP(Google的L2CAP重构版)return bluetooth::shim::L2CA_SendFixedChnlData(fixed_cid, rem_bda, p_buf);}tL2C_LCB* p_lcb; // 声明链路控制块指针 p_lcbtBT_TRANSPORT transport = BT_TRANSPORT_BR_EDR; // 默认传输类型 transport 设置为 BR/EDR(传统蓝牙)// 如果 Fixed Channel ID 是 LE专用通道(ATT, SMP等),则改成 LE(低功耗蓝牙)传输if (fixed_cid >= L2CAP_ATT_CID && fixed_cid <= L2CAP_SMP_CID)transport = BT_TRANSPORT_LE; // ATT/SMP属于BLE专用固定通道// 检查 fixed_cid 是否有效:if ((fixed_cid < L2CAP_FIRST_FIXED_CHNL) ||(fixed_cid > L2CAP_LAST_FIXED_CHNL) ||(l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb ==NULL)) {// 如果无效或没注册对应通道的处理回调LOG_WARN("No service registered or invalid CID: 0x%04x", fixed_cid);osi_free(p_buf); // 释放 p_buf(因为上层已经托管了这个内存,避免内存泄漏)return (L2CAP_DW_FAILED); // 返回失败}if (!BTM_IsDeviceUp()) { // 检查蓝牙控制器是否就绪(比如初始化是否完成)。 如果没准备好:LOG_WARN("Controller is not ready CID: 0x%04x", fixed_cid);osi_free(p_buf);return (L2CAP_DW_FAILED); // 同样,打印、释放、返回失败}// 查找远端设备对应的 Link Control Block(LCB),即当前ACL连接p_lcb = l2cu_find_lcb_by_bd_addr(rem_bda, transport);if (p_lcb == NULL || p_lcb->link_state == LST_DISCONNECTING) {// 如果找不到 link,或者 link 正在断开(DISCONNECTING 状态)/* if link is disconnecting, also report data sending failure */LOG_WARN("Link is disconnecting or does not exist CID: 0x%04x", fixed_cid);osi_free(p_buf); // 打印、释放、返回失败。return (L2CAP_DW_FAILED); // 特别注意:这里即使 link 还存在,但一旦在断开中,禁止发送数据,防止 race condition}// 定义远端设备的固定通道支持掩码(bit mask)// 这是为了确认对端是否支持要发送的 fixed_cidtL2C_BLE_FIXED_CHNLS_MASK peer_channel_mask;// 根据不同传输类型(BR/EDR vs LE),选择本地/对端的通道掩码// Select peer channels mask to use depending on transportif (transport == BT_TRANSPORT_LE)peer_channel_mask = l2cb.l2c_ble_fixed_chnls_mask;elsepeer_channel_mask = p_lcb->peer_chnl_mask[0];// 检查对方是否支持这个 fixed_cidif ((peer_channel_mask & (1 << fixed_cid)) == 0) { // 如果不支持LOG_WARN("Peer does not support fixed channel CID: 0x%04x", fixed_cid);osi_free(p_buf);return (L2CAP_DW_FAILED);}// 准备数据包p_buf->event = 0; // 一般L2CAP层内部标志,无需事件触发p_buf->layer_specific = L2CAP_FLUSHABLE_CH_BASED; // 表示可基于通道丢弃(flushable),优化性能if (!p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]) {// 如果这条 fixed_cid 通道对应的 Channel Control Block(CCB)还没初始化// 尝试初始化一个固定通道 CCBif (!l2cu_initialize_fixed_ccb(p_lcb, fixed_cid)) { // 如果初始化失败LOG_WARN("No channel control block found for CID: 0x%4x", fixed_cid);osi_free(p_buf);return (L2CAP_DW_FAILED);}}if (p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]->cong_sent) { // 检查当前通道是否拥塞(congested)// 打印详细的拥塞状态(队列长度、配额大小),释放缓冲区并返回失败LOG_WARN("Unable to send data due to congestion CID: 0x%04x xmit_hold_q.count: ""%zu buff_quota: %u",fixed_cid,fixed_queue_length(p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]->xmit_hold_q),p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]->buff_quota);osi_free(p_buf);return (L2CAP_DW_FAILED);}// 正常情况下,将数据包入队列LOG_INFO("Enqueued data for CID: 0x%04x len:%hu", fixed_cid, p_buf->len);l2c_enqueue_peer_data(p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL],p_buf); // 把包放到发送队列中l2c_link_check_send_pkts(p_lcb, 0, NULL); // 检查链路是否可以发送数据,如果可以就触发发送// If there is no dynamic CCB on the link, restart the idle timer each time// something is sentif (p_lcb->in_use && p_lcb->link_state == LST_CONNECTED &&!p_lcb->ccb_queue.p_first_ccb) {// 如果没有动态通道(动态CCB)存在,即只剩固定信道,那么每次发完数据需要重启Idle Timer, 防止 link 因"闲置"超时被系统断开l2cu_no_dynamic_ccbs(p_lcb);}if (p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]->cong_sent) {LOG_INFO("Link congested for CID: 0x%04x", fixed_cid);// 如果发送完毕后,检测到发送队列已满(出现新拥塞),返回 拥塞状态return (L2CAP_DW_CONGESTED);}return (L2CAP_DW_SUCCESS); // 一切顺利,最终返回成功
}
阶段动作
参数合法性检查fixed_cid,蓝牙模块状态,链路存在性检查
peer支持性检查检查peer的fixed_cid支持情况
资源初始化没有CCB则动态初始化
拥塞状态判断发送前检查是否congestion
发送动作将数据包入队列,尝试发送
链路状态管理没有动态CCB时重新管理Idle Timer
发送后检测发送后是否导致新的拥塞
返回值根据情况返回 Success、Failed 或 Congested

功能

通过指定的 Fixed Channel ID(CID)发送数据。
Fixed Channel 是 L2CAP 预定义的一些不需要动态分配的频道,比如:

  • 0x0004:ATT(GATT用)

  • 0x0001:SMP(Security Manager Protocol,配对)

  • 0x003F:LE Credit-Based Flow Control

典型调用时机

  • GATT 或 SMP 协议需要发送数据时。

  • 应用层模块通过固定频道直接发包,比如发送 GATT 请求或应答。

典型调用位置(AOSP例子)

  • 发送 GATT 请求,如 gatt_cl.cc / gatt_sr.cc 中,发 GATT write 或 read 命令时,实际底层就是调用:

    L2CA_SendFixedChnlData(L2CAP_ATT_CID, bda, p_buf);

  • 发送 SMP pairing request/response(在配对阶段,SMP 通过 L2CAP Fixed Channel 发送数据)。

真实场景举例

  • 手机发起 GATT Read Request 查询蓝牙灯泡状态时,GATT模块组好一个ATT包,底层就是通过 L2CA_SendFixedChnlData 把数据发出去的。

这个函数, 我们真正的应该关注是在, 向固定通道发完数据后, 如果没有动态通道(动态CCB)存在,即只剩固定信道,那么每次发完数据需要重启Idle Timer, 防止 link 因"闲置"超时被系统断开。

3. L2CA_SetLeGattTimeout

system/stack/l2cap/l2c_api.cc/********************************************************************************* Function         L2CA_SetLeGattTimeout** Description      Higher layers call this function to set the idle timeout for*                  a fixed channel. The "idle timeout" is the amount of time*                  that a connection can remain up with no L2CAP channels on*                  it. A timeout of zero means that the connection will be torn*                  down immediately when the last channel is removed.*                  A timeout of 0xFFFF means no timeout. Values are in seconds.*                  A bd_addr is the remote BD address.** Returns          true if command succeeded, false if failed功能:设置一个固定通道(这里主要是 GATT ATT 通道)的空闲超时时间。空闲超时定义:如果一条连接上没有任何 L2CAP 通道,且达到超时时间,则断开连接。特殊值:0:立即断开。0xFFFF:永不超时。单位:秒。返回值:是否设置成功。*******************************************************************************/
bool L2CA_SetLeGattTimeout(const RawAddress& rem_bda, uint16_t idle_tout) {if (bluetooth::shim::is_gd_l2cap_enabled()) {return bluetooth::shim::L2CA_SetLeGattTimeout(rem_bda, idle_tout);}constexpr uint16_t kAttCid = 4; // 定义常量,ATT协议固定占用 L2CAP 的 CID 0x0004, 后面直接用 kAttCid 来查找。/* Is a fixed channel connected to the remote BDA ?*/// 尝试通过 远端地址(rem_bda) 和 LE链路类型 找到对应的 LCB(Link Control Block)// p_lcb 就是那条到远端设备的物理连接的信息结构体tL2C_LCB* p_lcb = l2cu_find_lcb_by_bd_addr(rem_bda, BT_TRANSPORT_LE);if (((p_lcb) == NULL) ||(!p_lcb->p_fixed_ccbs[kAttCid - L2CAP_FIRST_FIXED_CHNL])) { // 检查连接是否有效: 如果没找到 p_lcb,说明没有这条连接; 或者,即便有连接,但本连接没有 ATT 固定通道控制块(fixed channel control block)LOG(WARNING) << __func__ << " BDA: " << rem_bda<< StringPrintf(" CID: 0x%04x not connected", kAttCid); // 如果上述检查失败,输出警告日志return (false); // 然后直接 返回 设置失败}// 找到对应的 fixed_ccb(固定通道控制块),直接把它的 fixed_chnl_idle_tout 字段赋值为用户传入的 idle_tout,这样就把空闲超时时间设置上去了p_lcb->p_fixed_ccbs[kAttCid - L2CAP_FIRST_FIXED_CHNL]->fixed_chnl_idle_tout =idle_tout;if (p_lcb->in_use && p_lcb->link_state == LST_CONNECTED &&!p_lcb->ccb_queue.p_first_ccb) { // 即没有临时开的 L2CAP 动态通道,只剩下固定通道了/* If there are no dynamic CCBs, (re)start the idle timer in case we changed* it */l2cu_no_dynamic_ccbs(p_lcb); // 通知 L2CAP 层重新计算和启动空闲定时器(Idle Timer),确保新的超时设置能立即生效}return true;
}
步骤说明
1如果启用了 GD L2CAP,走 shim 接口。
2查找对应的连接控制块(LCB)。
3如果找不到连接或没有固定通道,返回失败。
4设置 ATT 通道的空闲超时时间。
5如果连接只剩下固定通道,重启 Idle Timer。
6返回成功。

功能

设置针对 LE (Low Energy) 链路上 GATT 信道的专属超时时间。
这个超时专门用于控制GATT层面连接的生命周期,比如如果设备长时间无 GATT 活动,可以更快主动断开。

典型调用时机

  • 蓝牙连接建立后,根据系统策略调整GATT信道专属超时。

  • 高级应用要求不同设备设定不同的GATT超时时。

典型调用位置(AOSP例子)

  • 比如:BLE连接建立后,仅用于GATT服务交互(如读写特征值),没有别的动态通道。

  • 上层可以通过这个接口设置「连接多久没用就自动断开」,节省功耗避免链路空占

  • gatt_main.cc 等文件中,GATT初始化连接后,会根据配置调用:
    L2CA_SetLeGattTimeout(bda, timeout_in_seconds);

  • 有些连接参数更新完成后(比如 PHY更新,MTU更新后),也可能根据新策略重新设置。

真实场景举例

  • 连接智能手环后,如果只是后台保持状态同步,系统可以降低超时阈值,比如设置 GATT 空闲超时为 60秒,这样在用户长期不用时及时释放资源。

4. l2cu_release_ccb

  • 释放一个动态信道的 CCB(Channel Control Block),是 L2CAP 信道生命周期中“清理收尾”的关键步骤。
// system/stack/l2cap/l2c_utils.ccvoid l2cu_release_ccb(tL2C_CCB* p_ccb) {tL2C_LCB* p_lcb = p_ccb->p_lcb;// 取出该信道所属的物理连接控制块(LCB, Link Control Block),一个 LCB 代表一个远端设备连接tL2C_RCB* p_rcb = p_ccb->p_rcb; // 获取注册控制块(RCB, Registration Control Block),用于标识该信道对应的上层 PSM 注册信息L2CAP_TRACE_DEBUG("l2cu_release_ccb: cid 0x%04x  in_use: %u",p_ccb->local_cid, p_ccb->in_use);/* If already released, could be race condition */if (!p_ccb->in_use) return; // 如果这个 CCB 已经被释放了,就直接退出(避免重复释放)if (p_rcb && (p_rcb->psm != p_rcb->real_psm)) {// 如果这是个临时映射的 PSM(例如 LE SMP 使用 0x0001 作为伪 PSM),则清除对应安全策略绑定;通常用于临时逻辑通道,如 LE 安全管理(SMP)通道BTM_SecClrServiceByPsm(p_rcb->psm);}/* Free the timer */alarm_free(p_ccb->l2c_ccb_timer); // 释放 CCB 上的定时器,防止内存泄露p_ccb->l2c_ccb_timer = NULL; // L2CAP 信道可能设置有定时器,如响应超时或配置超时等fixed_queue_free(p_ccb->xmit_hold_q, osi_free); // 清除待发送的数据队列(暂存的数据帧队列), 使用 osi_free 作为释放每个队列元素的回调p_ccb->xmit_hold_q = NULL;l2c_fcr_cleanup(p_ccb); // 清理 FCR(Flow Control and Retransmission)相关资源, 如果信道启用了增强重传模式/流控机制(如 AVCTP),此处会释放相关缓存/计时器等/* Channel may not be assigned to any LCB if it was just pre-reserved */if ((p_lcb) && ((p_ccb->local_cid >= L2CAP_BASE_APPL_CID))) {l2cu_dequeue_ccb(p_ccb); // 如果这个 CCB 已经绑定在某个 LCB 上(动态信道),就将其从 LCB 的 CCB 队列中移除/* Delink the CCB from the LCB */p_ccb->p_lcb = NULL; // 然后断开与 LCB 的关联(从逻辑连接中解绑)}// 下面几行将 CCB 归还给“空闲池”// 维护一个链表用于管理空闲的 CCB(资源池管理)。// 提高资源重用效率,避免频繁 malloc/free/* Put the CCB back on the free pool */if (!l2cb.p_free_ccb_first) {// 空闲链表为空l2cb.p_free_ccb_first = p_ccb;l2cb.p_free_ccb_last = p_ccb;p_ccb->p_next_ccb = NULL;p_ccb->p_prev_ccb = NULL;} else {// 挂在链表尾部p_ccb->p_next_ccb = NULL;p_ccb->p_prev_ccb = l2cb.p_free_ccb_last;l2cb.p_free_ccb_last->p_next_ccb = p_ccb;l2cb.p_free_ccb_last = p_ccb;}/* Flag as not in use */p_ccb->in_use = false; // 标记为未使用状态,允许被下一次连接分配// 清空远端通道 ID、本地信令 ID 以及“待移除”标记。// Clear Remote CID and Local Idp_ccb->remote_cid = 0;p_ccb->local_id = 0;p_ccb->pending_remove = false;// 最后:连接层状态感知,决定是否要设置“链接空闲超时”或调整配额/* If no channels on the connection, start idle timeout */if ((p_lcb) && p_lcb->in_use) {if (p_lcb->link_state == LST_CONNECTED) { // 如果 link 正常连接中.if (!p_lcb->ccb_queue.p_first_ccb) { // 当前 link 上没有任何 CCB(即所有通道都断了)// Closing a security channel on LE device should not start connection// timeoutif (p_lcb->transport == BT_TRANSPORT_LE &&p_ccb->local_cid == L2CAP_SMP_CID)return; // 特殊 case:如果是 LE SMP 信道断开,不要触发超时计时器(可能仍有 ATT 保活)// 否则,启动链接的 idle timer(如 L2CAP 闲置一段时间后断开连接)l2cu_no_dynamic_ccbs(p_lcb);} else {// 如果还有通道存在,就调整带宽/通道配额(QoS 优化)/* Link is still active, adjust channel quotas. */l2c_link_adjust_chnl_allocation();}} else if (p_lcb->link_state == LST_CONNECTING) {// 当 link 还处于“连接中”状态,但所有 CCB 都被释放(比如连接失败、超时),则主动断开 LE 链路// 典型用于 ATT 建链失败或上层取消连接等场景。if (!p_lcb->ccb_queue.p_first_ccb) {if (p_lcb->transport == BT_TRANSPORT_LE &&p_ccb->local_cid == L2CAP_ATT_CID) {L2CAP_TRACE_WARNING("%s - disconnecting the LE link", __func__);l2cu_no_dynamic_ccbs(p_lcb);}}}}
}

l2cu_release_ccb 是用于在 L2CAP 通道关闭后彻底释放信道控制块的函数,是连接生命周期中“善后阶段”的核心操作。

类型说明
功能释放动态信道 CCB,清理资源、回收内存
输入tL2C_CCB* p_ccb,表示要释放的 L2CAP 信道
最终效果从 LCB 中移除通道,释放定时器、队列、状态,重置 CCB,更新空闲池
特别处理对 SMP/ATT 的连接状态做了特殊保护(如不启动 idle timeout)
常见触发点信道断开、配置失败、连接超时、上层释放连接请求

功能:

  • 释放一个动态 L2CAP 信道的控制块(CCB)

  • 断开与该通道关联的资源、队列、计时器等。

  • 将 CCB 从 LCB 的队列中移除,使其可以被重用或销毁。


典型调用时机

l2cu_release_ccb() 的调用并不是主动触发的 API,而是作为状态机驱动下的“清理收尾动作”出现。

调用源头函数(常见):

  1. l2c_csm_execute(L2CAP 信道状态机执行器)

    • 多种状态处理函数中在某些事件下调用:

      • L2CEVT_DISCONNECT_REQ

      • L2CEVT_DISCONNECT_IND

      • L2CEVT_TIMEOUT

  2. l2c_csm_closed()

    • 在信道关闭后,调用此函数进行 CCB 回收。
  3. l2cu_disconnect_chnl()

    • 在断开信道时,最终调用 l2cu_release_ccb() 来释放资源。

常见应用场景分析

场景编号典型场景说明
1A2DP/AVRCP 等 L2CAP 信道连接断开通道断开后,释放相应 CCB,释放内存、清理状态
2L2CAP 信道超时未响应超时事件触发状态机跳转至 CLOSED,调用本函数清理
3上层主动请求断开连接上层通过 L2CA_DisconnectReq 发起断开,最后清理 CCB
4连接失败(配置阶段失败等)连接未建立成功,提前退出并释放 CCB
5L2CAP 信道异常中止如收到非法 L2CAP PDU 或远端突然断开等场景

5. 小结

函数作用常见调用时机场景举例
L2CA_SetIdleTimeoutByBdAddr设置ACL链路整体空闲超时GATT连接成功后,Pairing完成后手机连接手表后30秒无通信断开
L2CA_SendFixedChnlData通过固定信道发送L2CAP数据包发ATT读写命令,发SMP配对请求手机读灯泡状态,发pairing包
L2CA_SetLeGattTimeout设置LE链路GATT专属超时时间GATT连接建立后,连接参数更新后手环连接超时控制

3. 检查是否要断开 acl

1. L2CA_SetIdleTimeoutByBdAddr -> l2cu_no_dynamic_ccbs2. L2CA_SendFixedChnlData  -> l2cu_no_dynamic_ccbs3. L2CA_SetLeGattTimeout -> l2cu_no_dynamic_ccbs4. l2cu_release_ccb -> l2cu_no_dynamic_ccbs

上面都是 通过 触发 l2cu_no_dynamic_ccbs 来定时检查 是否要断开acl, 本节就来梳理一下对应的逻辑。

1. l2cu_no_dynamic_ccbs

  • 当一个连接(LCB)上已经没有动态信道(CCB)时,根据固定信道的闲置超时时间,决定是否要断开这条连接
// system/stack/l2cap/l2c_utils.cc
void l2cu_no_dynamic_ccbs(tL2C_LCB* p_lcb /* 参数是指向 tL2C_LCB(L2CAP Link Control Block)的指针,表示当前要检查的连接对象 */) {tBTM_STATUS rc; // 后面调用安全管理器断开连接时用到的返回值。uint64_t timeout_ms = p_lcb->idle_timeout * 1000; // 初始化为LCB的默认空闲超时时间(秒转成毫秒)。bool start_timeout = true; // 标记是否要启动超时定时器。int xx; // 循环变量,后面遍历固定信道数组用// 这段循环就是:在所有固定信道里,找出最长闲置时间作为连接的超时依据。for (xx = 0; xx < L2CAP_NUM_FIXED_CHNLS; xx++) {// 遍历所有固定信道(固定信道数量是常量 L2CAP_NUM_FIXED_CHNLS,比如典型的ATT/GATT信道)。if ((p_lcb->p_fixed_ccbs[xx] != NULL)/*如果这个固定信道存在*/ &&(p_lcb->p_fixed_ccbs[xx]->fixed_chnl_idle_tout * 1000 > timeout_ms /*并且它配置的闲置超时时间(毫秒)比当前记录的更长*/)) { // 那么就认为以这个固定信道的超时时间为准。if (p_lcb->p_fixed_ccbs[xx]->fixed_chnl_idle_tout ==L2CAP_NO_IDLE_TIMEOUT) { // 如果固定信道设置的是 永不超时(L2CAP_NO_IDLE_TIMEOUT)// 打印日志,说明这个固定信道禁止空闲断开L2CAP_TRACE_DEBUG("%s NO IDLE timeout set for fixed cid 0x%04x",__func__, p_lcb->p_fixed_ccbs[xx]->local_cid);start_timeout = false; // 于是设置 start_timeout = false,表示不启动定时器}// 更新 timeout_ms 为当前信道的空闲超时时间。timeout_ms = p_lcb->p_fixed_ccbs[xx]->fixed_chnl_idle_tout * 1000;}}/* If the link is pairing, do not mess with the timeouts */// 如果正在配对(bonding),直接返回,不断开也不启动超时. 因为配对期间连接是有意义的,即使没有数据传输.if (p_lcb->IsBonding()) return;// 打日志,记录下with_active_local_clients(是否有主动使用ATT/GATT的本地客户端)L2CAP_TRACE_DEBUG("l2cu_no_dynamic_ccbs() with_active_local_clients=%d",p_lcb->with_active_local_clients);// Inactive connections should not timeout, since the ATT channel might still// be in use even without a GATT client. We only timeout if either a dynamic// channel or a GATT client was used, since then we expect the client to// manage the lifecycle of the connection.// FOR T ONLY: We add the outer safety-check to only do this for LE/ATT, to// minimize behavioral changes outside a dessert release. But for consistency// this should happen throughout on U (i.e. for classic transport + other// fixed channels too)if (p_lcb->p_fixed_ccbs[L2CAP_ATT_CID - L2CAP_FIRST_FIXED_CHNL] != NULL) {// 如果 ATT信道(0x0004)是打开的,if (bluetooth::common::init_flags::finite_att_timeout_is_enabled() &&!p_lcb->with_active_local_clients) {// 并且全局开关 finite_att_timeout 打开,且本地没有活跃的GATT客户端.// 则不设置超时,不断开// (原因:即使没有GATT Client,还可能有其他用途,比如GATT Server还在等。return;}}// 如果超时时间是 0,立即断开连接.if (timeout_ms == 0) {// 打日志,表示准备断开。L2CAP_TRACE_DEBUG("l2cu_no_dynamic_ccbs() IDLE timer 0, disconnecting link");// 调用 btm_sec_disconnect() 通过安全管理器发起断链操作.// 原因码是 HCI_ERR_PEER_USER,即用户主动断开rc = btm_sec_disconnect(p_lcb->Handle(), HCI_ERR_PEER_USER,"stack::l2cap::l2c_utils::l2cu_no_dynamic_ccbs Idle timer popped");// 然后根据断链函数返回结果分别处理if (rc == BTM_CMD_STARTED) {// 如果断开已经开始(但是异步进行中)l2cu_process_fixed_disc_cback(p_lcb);p_lcb->link_state = LST_DISCONNECTING; // 更新LCB状态为 LST_DISCONNECTINGtimeout_ms = L2CAP_LINK_DISCONNECT_TIMEOUT_MS; // 并设置断链超时时间(短时间,比如2秒,防止卡死)} else if (rc == BTM_SUCCESS) {// 如果断链 立刻成功l2cu_process_fixed_disc_cback(p_lcb);/* BTM SEC will make sure that link is release (probably after pairing is* done) */p_lcb->link_state = LST_DISCONNECTING;// 同样更新状态,但这里就不需要再启动新的超时定时器了start_timeout = false;} else if (p_lcb->IsBonding()) {// 如果当前正在 Bonding// 直接调用ACL层断开函数,进入断开流程。acl_disconnect_from_handle(p_lcb->Handle(), HCI_ERR_PEER_USER,"stack::l2cap::l2c_utils::l2cu_no_dynamic_ccbs Bonding no traffic");l2cu_process_fixed_disc_cback(p_lcb);p_lcb->link_state = LST_DISCONNECTING;timeout_ms = L2CAP_LINK_DISCONNECT_TIMEOUT_MS; // 超时时间设置为断开保护时长。} else {// 如果其他情况(比如没有Buffer可发断链包),/* probably no buffer to send disconnect */timeout_ms = BT_1SEC_TIMEOUT_MS; // 设置成1秒的保护超时。}}// 最后,启动或取消定时器if (start_timeout) {// 启动定时器,超时后会回调 l2c_lcb_timer_timeout() 继续处理LOG_INFO("trace_l2c_lcb_timer_timeout [%s:%d]", __func__, __LINE__);alarm_set_on_mloop(p_lcb->l2c_lcb_timer, timeout_ms, l2c_lcb_timer_timeout,p_lcb);LOG_INFO("Started link IDLE timeout_ms:%lu", (unsigned long)timeout_ms);} else {// 否则,取消之前可能设置过的定时器。alarm_cancel(p_lcb->l2c_lcb_timer);}
}

如果超时后, 将触发 l2c_lcb_timer_timeout 调用

// system/stack/l2cap/l2c_main.ccvoid l2c_lcb_timer_timeout(void* data) {tL2C_LCB* p_lcb = (tL2C_LCB*)data;l2c_link_timeout(p_lcb); // 最终调用 它来处理
}

2. l2c_link_timeout

我们先来看一下 l2c_link_timeout 函数做了那些事情:

这是 L2CAP 连接(ACL链路)超时处理函数,比较核心:

  • 目的:在某条 ACL物理链路 (p_lcb) 的超时事件到达时,进行处理。
  • 参数p_lcb 是指向当前超时链路的 tL2C_LCB 结构体(L2CAP的"Link Control Block")。
  • 触发场景
    • 连接超时(比如蓝牙配对中对方长时间无回应)。
    • 正常连接后因为空闲太久而超时。
    • 正在断开但未完成,超时清理。
// system/stack/l2cap/l2c_link.cc
void l2c_link_timeout(tL2C_LCB* p_lcb) {tL2C_CCB* p_ccb; // 指向某一条L2CAP Channel(逻辑信道,CCB是Channel Control Block)。tBTM_STATUS rc; // 保存调用底层断链(btm_sec_disconnect)函数的返回状态// 打印链路超时时,链路的状态和是否在Bonding过程(配对过程中)。LOG_INFO("L2CAP - l2c_link_timeout() link state:%s is_bonding:%s",link_state_text(p_lcb->link_state).c_str(),logbool(p_lcb->IsBonding()).c_str());/* If link was connecting or disconnecting, clear all channels and drop the* LCB *//*第一部分:链路如果是连接中/断开中如果链路状态是:LST_CONNECTING_WAIT_SWITCH:连接中,等待role switch(主从角色切换)。LST_CONNECTING:连接过程中。LST_CONNECT_HOLDING:连接建立后,准备阶段。LST_DISCONNECTING:正在断开。说明连接还没成功或者正在断开,但超时了。处理措施:连接还没建好 or 正在断开又超时了?——强制清理掉所有channel,释放链路!*/if ((p_lcb->link_state == LST_CONNECTING_WAIT_SWITCH) ||(p_lcb->link_state == LST_CONNECTING) ||(p_lcb->link_state == LST_CONNECT_HOLDING) ||(p_lcb->link_state == LST_DISCONNECTING)) {p_lcb->p_pending_ccb = NULL; // 清空当前正在等待连接的channel。/* For all channels, send a disconnect indication event through *//* their FSMs. The CCBs should remove themselves from the LCB   */// 遍历所有挂在这条链路的L2CAP频道(CCB)。for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb;) {tL2C_CCB* pn = p_ccb->p_next_ccb;// 给每个CCB的状态机发送 L2CEVT_LP_DISCONNECT_IND 消息:// 告诉Channel“链路层断了”,每个Channel要自己清理自己。l2c_csm_execute(p_ccb, L2CEVT_LP_DISCONNECT_IND, NULL);p_ccb = pn;}/* Release the LCB */l2cu_release_lcb(p_lcb); // 把这条链路 p_lcb 彻底释放掉(删掉)。}/*第二部分:链路如果是已连接*//* If link is connected, check for inactivity timeout */if (p_lcb->link_state == LST_CONNECTED) { // 链路当前是 已经连接成功的状态。/* If no channels in use, drop the link. */if (!p_lcb->ccb_queue.p_first_ccb) { // 子情况一:没有任何Channel在用了/*ccb_queue 空了 -> 没有任何应用在用这个连接了(比如应用断开了所有L2CAP频道)。该断开物理链路了!*/uint64_t timeout_ms;bool start_timeout = true;LOG_WARN("TODO: Remove this callback into bcm_sec_disconnect");rc = btm_sec_disconnect(p_lcb->Handle(), HCI_ERR_PEER_USER/*传入理由是 HCI_ERR_PEER_USER(本机主动断开)*/,"stack::l2cap::l2c_link::l2c_link_timeout All channels closed"/*并附带一些日志描述*/); // 请求底层断开ACL链路。// 根据断链调用结果处理if (rc == BTM_CMD_STORED) { // Security Manager暂时保存了断链命令,稍后处理。/* Security Manager will take care of disconnecting, state will be* updated at that time */start_timeout = false;} else if (rc == BTM_CMD_STARTED) { // 成功发起了断链 → 设置状态为“正在断开”,并设定断开超时时间。p_lcb->link_state = LST_DISCONNECTING;timeout_ms = L2CAP_LINK_DISCONNECT_TIMEOUT_MS;} else if (rc == BTM_SUCCESS) { // 已经断了,直接处理固定通道的断开回调,标记断开中。l2cu_process_fixed_disc_cback(p_lcb);/* BTM SEC will make sure that link is release (probably after pairing* is done) */p_lcb->link_state = LST_DISCONNECTING;start_timeout = false;} else if (rc == BTM_BUSY) { // 底层还在忙(比如配对流程还没结束),就不着急处理。/* BTM is still executing security process. Let lcb stay as connected */start_timeout = false;} else if (p_lcb->IsBonding()) { // 如果正在Bonding配对中,主动发起ACL断开。acl_disconnect_from_handle(p_lcb->Handle(), HCI_ERR_PEER_USER,"stack::l2cap::l2c_link::l2c_link_timeout ""Timer expired while bonding");l2cu_process_fixed_disc_cback(p_lcb);p_lcb->link_state = LST_DISCONNECTING;timeout_ms = L2CAP_LINK_DISCONNECT_TIMEOUT_MS;} else { // 其他意外情况(比如没资源发断开命令)→ 延时1秒后再检查。/* probably no buffer to send disconnect */timeout_ms = BT_1SEC_TIMEOUT_MS;}// 是否需要重新设置定时器?if (start_timeout) {// 如果需要(start_timeout为true),就重新设个超时定时器,继续监视。alarm_set_on_mloop(p_lcb->l2c_lcb_timer, timeout_ms,l2c_lcb_timer_timeout, p_lcb);}} else {// 子情况二:还有Channel在用// 检查一下是否有积压的包需要发送(比如之前被流控block住的)。/* Check in case we were flow controlled */l2c_link_check_send_pkts(p_lcb, 0, NULL);}}
}

整个 l2c_link_timeout() 流程可以概括为两种场景:

链路状态处理逻辑
正在连接/断开中清空所有Channel,释放链路
已连接(但空闲)如果没Channel了,开始断链;如果还有Channel,就检查发包

而且在断链时,细分了各种不同的返回值处理,比如 BTM_CMD_STOREDBTM_BUSY 等非常仔细。

相关文章:

【android bluetooth 协议分析 06】【l2cap详解 11】【l2cap连接超时处理逻辑介绍】

我们在使用蓝牙的过程中&#xff0c; 当上层 应用 断开所有的 profile 后&#xff0c; 协议栈就会帮我们下发 disconnect 命令。本节就让笨叔&#xff0c; 带大家一起梳理这块内容&#xff0c;具体在协议栈如何处理的。 梳理开始前&#xff0c; 先思考一下。 我们为什么要梳理…...

Spring、Spring MVC 与 Spring Boot 的关系与核心用途

1. 三者关系图解 ------------------- | Spring Boot | → 基于 Spring&#xff0c;简化配置与部署 -------------------▲| 依赖 ------------------- | Spring Framework | → 核心容器&#xff08;IoC/AOP&#xff09;与基础模块 -------------------▲| 扩展 ---…...

如何搭建spark yarn 模式的集群集群

&#xff08;一&#xff09;什么是SparkONYarn模式 Spark on YARN&#xff08;Yet Another Resource Negotiator&#xff09;是 Spark 框架在 Hadoop 集群中运行的一种部署模式&#xff0c;它借助 Hadoop YARN 来管理资源和调度任务。 架构组成 ResourceManager&#xff1a;作为…...

共探蓝海赛道增长新方法 阿里国际站智能AI全球买家分析峰会在深落幕

来源&#xff1a;深圳晚报 随着全球贸易环境不断变化&#xff0c;跨境电商已成为推动企业发展的重要动力。为帮助企业更好地应对新的市场挑战&#xff0c;阿里巴巴国际站深莞惠大区于4月29日举办了“万亿商机 蓝海新市场”智能AI全球买家分析峰会&#xff0c;现已圆满落幕&…...

今日行情明日机会——20250429

指数依然在区间震荡&#xff0c;等待方向&#xff0c;重点关注决定大盘方向的板块&#xff0c;如证券的走势~ 2025年4月29日涨停主要行业方向分析 一、核心主线方向 一季报增长&#xff08;业绩驱动资金避险&#xff09; • 涨停家数&#xff1a;16家。 • 代表标的&#xff…...

什么是缓存?在NGINX中如何配置缓存以提升性能?

大家好&#xff0c;我是锋哥。今天分享关于【什么是缓存&#xff1f;在NGINX中如何配置缓存以提升性能&#xff1f;】面试题。希望对大家有帮助&#xff1b; 什么是缓存&#xff1f;在NGINX中如何配置缓存以提升性能&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java…...

价值投资笔记:企业护城河——虚假陷阱与隐性壁垒的深度解析

一、护城河的本质与误判风险 护城河是企业抵御竞争、维持超额利润的核心能力。然而&#xff0c;市场中充斥着大量“虚假护城河”&#xff0c;它们看似构成壁垒&#xff0c;实则脆弱易碎。晨星公司研究显示&#xff0c;超过60%的企业竞争优势被误判为护城河&#xff0c;投资者需…...

2025年04月29日Github流行趋势

项目名称&#xff1a;Deep-Live-Cam 项目地址url&#xff1a;https://github.com/hacksider/Deep-Live-Cam项目语言&#xff1a;Python历史star数&#xff1a;52291今日star数&#xff1a;380项目维护者&#xff1a;hacksider, KRSHH, vic4key, pereiraroland26, kier007项目简…...

docker排查OOM Killer

文章目录 一.检查1.内存不足 (OOM Killer)2. CPU 资源限制3. 存储空间不足4. 应用自身崩溃5. 健康检查失败针对性建议 二.内存不足问题根源解决方案&#xff08;按优先级排序&#xff09;1. 紧急措施&#xff1a;立即释放内存2. 启用 Swap 交换空间&#xff08;必须做&#xff…...

leetcode继续c++10/100

不应该是10-13-3吗 ChatGLM 引用 从代码片段来看&#xff0c;函数 findAnagrams 的目的是在字符串 s 中找到所有与字符串 p 是字母异位词的子串的起始索引。 代码中有一些调试输出语句&#xff0c;这些语句可能会影响程序的正常逻辑。具体来说&#xff1a; cpp 复制 cout …...

Kubernetes集群使用Harbor容器镜像仓库

实验环境 一、容器镜像仓库Harbor部署 1、配置主机名 192.168.10.14&#xff1a; hostnamectl set-hostname harbor 2、安装Docker wget -O /etc/yum.repos.d/docker-ce.repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo yum -y install docker-…...

归并排序排序总结

1. 归并排序 1.1 基本思想 归并排序&#xff08;Merge Sort&#xff09;是采用分治法&#xff08;Divide and Conquer&#xff09;的一个非常典型的应用。它的基本思想是将一个数组分成两个子数组&#xff0c;分别对这两个子数组进行排序&#xff0c;然后将排好序的子数组合并…...

面试手撕——快速排序

思路 partition方法将整个区间分为两部分&#xff0c;一部分比pivot小&#xff0c;一部分比pivot大&#xff0c; i表示&#xff0c;小于等于pivot的下标&#xff0c;j表示当前遍历到哪一个元素了&#xff0c;如果发现当前元素j小于等于pivot&#xff0c;i&#xff0c;在i1的位…...

大模型微调之LLaMA-Factory 系列教程大纲

LLaMA-Factory 系列教程大纲 一、基础入门篇&#xff1a;环境搭建与核心功能解析 环境部署与框架特性 硬件要求&#xff1a; 单机训练&#xff1a;推荐 24GB 显存 GPU&#xff08;如 RTX 4090&#xff09;&#xff0c;支持 7B-32B 模型 LoRA 微调。分布式训练&#xff1a;2 块…...

26考研 | 王道 | 计算机网络 | 第一章 计算机网络的体系结构

26考研 | 王道 | 第一章 计算机网络的体系结构 文章目录 26考研 | 王道 | 第一章 计算机网络的体系结构1.1 计算机网络概述1.计算机网络的概念2.计算机网络的组成**从组成部分看****从工作方式看****从逻辑功能看** 3.计算机网络的功能4.电路交换、报文交换、分组交换1. 电路交…...

CentosLinux系统crontab发现执行删除命令失效解决方法

权限或安全策略限制 ​​可能场景​​&#xff1a; ​​### ​​目录权限冲突​​&#xff1a; 你的目录权限为 drwxr-xr-x&#xff08;属主 mssql&#xff09;&#xff0c;但 cron 任务以 root 执行。 ​​风险点​​&#xff1a;若目录内文件属主为 mssql 且权限为 700&…...

UniApp页面路由详解

一、路由系统概述 1.1 路由机制原理 UniApp基于Vue.js实现了一套跨平台的路由管理系统&#xff0c;其核心原理是通过维护页面栈来管理应用内不同页面之间的跳转关系。在小程序端&#xff0c;UniApp的路由系统会映射到对应平台的原生导航机制&#xff1b;在H5端则基于HTML5 Hi…...

探索无人机模拟环境的多元景象及AI拓展

无人驾驶飞行器&#xff08;UAVs&#xff09;在各行各业的迅速普及&#xff0c;从农业和检测到空中操作和人机交互等令人兴奋的前沿领域&#xff0c;都引发了一个关键需求&#xff1a;强大而逼真的模拟环境。直接在物理硬件上测试尖端算法存在固有的风险——成本高昂的坠机、中…...

Java后端开发day39--方法引用

&#xff08;以下内容全部来自上述课程&#xff09; 1.1 含义 把已经有的方法拿过来用&#xff0c;当作函数式接口中抽象方法的方法体。 已经有的方法&#xff1a;可以是Java自己写的&#xff0c;也可以是第三方的。 示例语句&#xff1a; &#xff1a;&#xff1a;是方法引…...

C# 14 field keyword:属性简化新利器

引言 在 C# 的不断发展历程中&#xff0c;每一个新版本都带来了令人期待的新特性&#xff0c;而 C# 14 中的 field keyword 无疑是其中一颗璀璨的明星 。对于广大 C# 开发者来说&#xff0c;属性的使用频率极高&#xff0c;而 field keyword 的出现&#xff0c;为我们简化属性…...

破茧成蝶:一家传统制造企业的年轻化转型之路

2004 年&#xff0c;在长三角的轻工业重镇杭集&#xff0c;一家专注于植毛机器设备研发的小工厂悄然诞生。那时&#xff0c;它以 “齿轮与钢铁” 为语言&#xff0c;为全国近千家牙刷生产企业提供核心装备&#xff0c;用机械臂的精准律动&#xff0c;编织着传统制造业的经纬。然…...

【语法】C++的继承

目录 继承基本语法&#xff1a; protected访问限定符&#xff1a; 子类和父类之间的赋值兼容规则&#xff1a; 重定义(隐藏)&#xff1a; 继承中的友元/继承中的静态成员&#xff1a; 子类中的默认成员函数 构造函数/拷贝构造函数&#xff1a; 赋值重载函数&#xff…...

如何知道Ubuntu的端口是否被占用,被那个进程占用?如何终止进程

要检查Ubuntu系统中某个端口&#xff0c;比如5034&#xff0c;是否被占用及终止对应进程&#xff0c;请按以下步骤操作&#xff1a; 1. 检查端口占用情况 方法一&#xff1a;使用 lsof 命令 sudo lsof -i :5034输出结果会显示占用该端口的进程名、PID等信息。 方法二&#x…...

verdi使用tcl脚本批量添加波形

打开verdi console功能 在verdi的tools 里使能工具中的console功能; 在console执行tcl脚本 set cell_list { ts_0_lockup_latchn_clkc45_intno45811_i u_rst_scan_n_tp/u_scan_crl_reg/u_cell u_scan_crl_reg/u_cell u_scan_crl_reg/u_cell } ## specify the waveform window…...

【行业特化篇3】制造业简历优化指南:技术参数与标准化流程的关键词植入艺术

写在最前 作为一个中古程序猿,我有很多自己想做的事情,比如埋头苦干手搓一个低代码数据库设计平台(目前只针对写java的朋友),比如很喜欢帮身边的朋友看看简历,讲讲面试技巧,毕竟工作这么多年,也做到过高管,有很多面人经历,意见还算有用,大家基本都能拿到想要的offe…...

oracle怎样通过固化较优执行计划来优化慢sql

一 问题描述 有次生产环境cpu使用率增高&#xff0c;ADDM报告提示某条sql比较耗费cpu&#xff1a; 提示&#xff1a; 在分析期间, 此 SQL 语句至少利用了 6 个不同的执行计划 #查看该sql都有哪些执行计划 SELECT * FROM table(DBMS_XPLAN.DISPLAY_AWR(sqlid值)); 我手动执…...

【无标题】好用的远程链接插件

现在在做后端开发有的时候需要链接到远程服务器,有很多插件看不到整体的目录结构 推荐 trae的 ssh Client 有很清晰的目录结构...

Plant Simulation MultiPortalCrane Store 小案例

一个天车从库区移动商品到指定地点的案例 库区商品&#xff1a;库区上随机位置摆放商品&#xff0c;在源上绑定方法&#xff08;应该也可以直接在库区上生成&#xff0c;我这里是使用源可以改变生成多少个商品&#xff09; // 源的self.OnExit var Store : object : 存储 var …...

MyBatis 使用 POJO 参数动态查询教程

项目结构概览&#xff08;基于图片描述&#xff09;&#xff1a; mybatis02 ├─ src/main/java │ └─ cn.cjxy │ ├─ domain # 实体类&#xff08;如 Emp.java&#xff09; │ ├─ mapper # Mapper 接口&#xff08;如 EmpMapper.java&#xff09; │…...

【MCP Node.js SDK 全栈进阶指南】高级篇(5):MCP之微服务架构

引言 在软件架构中,微服务模式已成为构建可扩展系统的主流方案。 将MCP与微服务架构结合,能够为AI驱动的应用带来显著优势。 本文将探讨如何在微服务环境中集成和部署MCP服务,以及如何利用云原生技术实现高可用、高性能的MCP应用。 目录 MCP在微服务中的角色服务网格集成容…...

UBUS 通信接口的使用——添加一个object对象(ubus call)

1&#xff0c;引入 ubus提供了一种多进程通信的机制。存在一个守护进程ubusd&#xff0c;所以进程都注册到ubusd&#xff0c;ubusd进行消息的接收、分发管理。 ubus对多线程支持的不好&#xff0c;例如在多个线程中去请求同一个服务&#xff0c;就有可能出现不可预知的结果。 …...

强化学习贝尔曼方程推导

引言 强化学习中贝尔曼方程的重要性就不说了&#xff0c;本文利用高中生都能看懂的数学知识推导贝尔曼方程。 回报 折扣回报 G t G_t Gt​的定义为&#xff1a; G t R t 1 γ R t 2 γ 2 R t 3 ⋯ ∑ k 0 ∞ γ k R t k 1 (1) G_t R_{t1} \gamma R_{t2} \gamm…...

【MCP Node.js SDK 全栈进阶指南】高级篇(2):MCP高性能服务优化

前言 随着MCP应用规模的扩大和用户量的增加,性能优化成为系统稳定运行的关键因素。高性能的MCP服务不仅能提供更好的用户体验,还能降低运营成本,提高系统的可扩展性。本文将深入探讨MCP TypeScript-SDK的性能优化策略,帮助开发者构建高效、稳定的MCP服务。 1. 性能瓶颈识…...

图片识别为提示词,背景信息提取 -从头设计数字生命第7课, demucs——仙盟创梦IDE

1. 图像内容理解与标注 用途&#xff1a;在大规模图像数据集的整理和标注工作中&#xff0c;通过特定提示词可引导图片识别系统更准确地提取图像中的背景信息&#xff0c;并进行标注。例如在医学图像库标注中&#xff0c;使用 “疾病相关背景特征” 作为提示词&#xff0c;系统…...

域对齐是什么

域对齐&#xff08;Domain Alignment&#xff09;是在机器学习和计算机视觉等领域中常用的技术 定义 域对齐旨在将不同域&#xff08;Domain&#xff09;的数据映射到一个共同的特征空间中&#xff0c;使得来自不同域的数据在该空间中具有相似的分布。这里的“域”可以指代不…...

opencv 直方图均衡化

直方图均衡化 1. 啥叫直方图2. 绘制直方图3. 直方图均衡化3.1 自适应直方图均衡化&#xff08;cv2.equalizeHist()&#xff09;3.2 对比度受限的自适应直方图均衡化(cv2.createCLAHE()) 1. 啥叫直方图 直方图是对数据进行统计的一种方法&#xff0c;并且将统计值组织到一系列实…...

JDK 8 函数式接口全集

JDK 8 函数式接口全集 函数式接口如何定义关于注解 FunctionalInterface 函数式接口的分类与简单使用生产型接口 Supplier使用 消费型接口 Consumer使用 ​​函数型接口&#xff08;Function&#xff09;​​实例(合并字符串) ​​断言型接口&#xff08;Predicate&#xff09;…...

网站防护无惧DDoS攻击:2025年实战指南

在数字化时代&#xff0c;DDoS攻击已成为企业生存的“生死线”。2024年全球日均攻击峰值突破5.4Tbps&#xff08;Cloudflare数据&#xff09;&#xff0c;电商、金融行业更是重灾区。本文将结合最新技术趋势和实战案例&#xff0c;为你提供一套低成本、高可靠的防御方案。 一、…...

【AI论文】BitNet v2:针对1位LLM的原生4位激活和哈达玛变换

摘要&#xff1a;激活异常值阻碍了1位大型语言模型&#xff08;LLM&#xff09;的有效部署&#xff0c;这使得低比特宽度的量化变得复杂。 我们介绍了BitNet v2&#xff0c;这是一个新的框架&#xff0c;支持1位LLM的原生4位激活量化。 为了解决注意力和前馈网络激活中的异常值…...

windows 使用 FFmpeg 放大视频原声

问题&#xff1a;原视频声音太小&#xff0c;就算把视频音量调到最大&#xff0c;声音也听不太清 一、下载 下载地址&#xff1a;Download FFmpeg 根据需要选择合适版本下载解压&#xff0c;如浏览器下载速度慢&#xff0c;可使用迅雷下载 二、配置环境变量 1.把解压的文件放…...

RHCE第七章:SElinux

一、SElinux SELinux 是一套安全策略系统 1.作用&#xff1a; &#xff08;1&#xff09;SELinux 域限制&#xff1a;对服务程序的功能进行限制&#xff0c;以确保服务程序做不了出格的事 &#xff08;2&#xff09;SELinux 安全上下文&#xff1a;对文件资源的访问限制&am…...

一文简单记录打通K8s+Kibana流程如何启动(Windows下的Docker版本)

为ES和Kibana组建Docker网络 docker network create elastic下载8.18.0版本镜像Es并启动 docker run --name es-node01 --net elastic -p 9200:9200 -p 9300:9300 -t docker.elastic.co/elasticsearch/elasticsearch:8.18.0启动Kibana&#xff08;简单一些直接咯和ES对应版本…...

【系统参数合法性校验】spring-boot-starter-validation

JSR303校验 统一校验的需求 前端请求后端接口传输参数&#xff0c;是在controller中校验还是在Service中校验&#xff1f; 答案是都需要校验&#xff0c;只是分工不同。 Contoller中校验请求参数的合法性&#xff0c;包括&#xff1a;必填项校验&#xff0c;数据格式校验&…...

蓝桥杯 10. 凯撒加密

凯撒加密 原题目链接 题目描述 给定一个单词&#xff0c;请使用凯撒密码将这个单词加密。 凯撒密码是一种替换加密的技术&#xff0c;单词中的所有字母都在字母表上向后偏移 3 位后被替换成密文。 即&#xff1a; a → db → e⋯w → zx → ay → bz → c 输入描述 输入…...

Discord多账号注册登录:如何同时管理多个账户?

Discord是许多人、特别是游戏玩家和社区管理者的重要沟通工具。随着用户需求的增长&#xff0c;越来越多的人开始在Discord上注册多个账号进行管理。例如&#xff0c;个人和工作账号的区分&#xff0c;多个游戏社区的参与&#xff0c;或者通过不同的身份进行更灵活的社交互动。…...

Harbor默认Redis与Notary组件弱口令漏洞分析与修复指南

一、 背景 某资源池控制面和运行面生产环境部署的harbor被漏扫出弱口令需要进行整改&#xff0c;主要涉及 default、server、signer用户存在弱口令。 二、 分析与处理 首先需求确认这三个用户是harbor那个组件使用&#xff0c;最好确认的是default这个用户&#xff0c;它是r…...

【Prometheus-Postgres Exporter安装配置指南,开机自启】

目录 内容概述 一、安装步骤1. 安装 PostgreSQL Exporter2. 创建 PostgreSQL 监控用户3. 配置 Systemd 服务4. 启动并验证服务5. 集成到 Prometheus 内容概述 本教程详细指导如何安装 PostgreSQL Exporter&#xff08;版本 0.15.0&#xff09;&#xff0c;包括&#xff1a; 软…...

leetcode:3005. 最大频率元素计数(python3解法)

难度&#xff1a;简单 给你一个由 正整数 组成的数组 nums 。 返回数组 nums 中所有具有 最大 频率的元素的 总频率 。 元素的 频率 是指该元素在数组中出现的次数。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,2,3,1,4] 输出&#xff1a;4 解释&#xff1a;元素 1 和 2 的…...

论文导读 - 基于特征融合的电子鼻多任务深度学习模型研究

基于特征融合的电子鼻多任务深度学习模型研究 原论文地址&#xff1a;https://www.sciencedirect.com/science/article/pii/S0925400524009365 引用此论文&#xff08;GB/T 7714-2015&#xff09;&#xff1a; NI W, WANG T, WU Y, et al. Multi-task deep learning model f…...

VSCode突然连接不上服务器(已解决)

可恶&#xff0c;不知道昨天还好好的VSCode还可以连接到服务器&#xff0c;今天打开就连接不上了&#xff0c;搜了一圈很多都说是服务端的vscode-server这个文件里面保存的commitID与当前我们VSCode上的ID不一致导致连接失败&#xff08;据说是因为VSCode自动更新导致的&#x…...