一、总述Overview在鸿蒙软总线SoftBus的蓝牙连接模块中有三个关键机制共同保障了数据传输的稳定性、可靠性和高效性机制作用类比基于ACK的滑动窗口拥塞控制根据网络质量动态调整发送速率高速公路上的车流控制流量控制限制单位时间内的发送总量水龙头的流量调节ACL碰撞处理处理蓝牙底层连接冲突交通路口的冲突协调这三个机制分别解决了不同层面的问题拥塞控制解决发太快对方收不过来的问题流量控制解决单位时间发太多的问题ACL碰撞处理解决蓝牙连接冲突的问题二、基于ACK的滑动窗口拥塞控制ACK-based Sliding Window Congestion Control2.1 设计思路想象你在高速公路上开车前方路况好时可以加速路况差时需要减速。滑动窗口拥塞控制就是这个原理网络质量好 → 窗口增大 → 发送更多数据 → 提高吞吐量 网络质量差 → 窗口减小 → 减少发送量 → 避免拥塞2.2 核心数据结构在 ConnBrConnection 结构体中定义了四个关键参数typedef struct { // ... 其他字段 // 拥塞控制相关参数Congestion Control Parameters int32_t window; // 滑动窗口大小控制每批发送的数据包数量 int64_t sequence; // 当前发送序列号记录已发送的数据包序号 int64_t waitSequence; // 等待确认的序列号标记需要等待ACK的位置 int32_t ackTimeoutCount; // ACK超时计数器记录连续超时次数 } ConnBrConnection;2.3 窗口参数定义在 softbus_conn_br_trans.h 中定义了窗口的边界#define MIN_WINDOW 10 // 最小窗口网络最差时至少发送10个包 #define MAX_WINDOW 80 // 最大窗口网络最好时最多发送80个包 #define DEFAULT_WINDOW 20 // 默认窗口初始值为20个包 #define ACK_FAILED_TIMES 3 // ACK失败阈值连续失败3次触发恢复 #define TIMEOUT_TIMES 2 // 超时次数阈值每2次超时减少窗口2.4 工作流程详解阶段一发送数据并触发ACK请求在 SendHandlerLoop 发送循环中// 序列号递增Sequence Number Increment connection-sequence 1; // 当序列号是窗口的整数倍时发送ACK请求 // When sequence number is a multiple of window, send ACK request if (connection-sequence % connection-window 0) { if (SendAck(connection, socketHandle) SOFTBUS_OK) { connection-waitSequence connection-sequence; // 记录等待确认的序列号 } }每个包都有序列号当前待发送的序列号是窗口的整数倍时候发送ack请求。通俗理解假设 window 20 发送第20个包 → 发送ACK请求等待对方确认 发送第40个包 → 发送ACK请求等待对方确认 发送第60个包 → 发送ACK请求等待对方确认 ...阶段二等待ACK响应// 当发送了window-1个包且有待确认的ACK时阻塞等待 // When window-1 packets sent and ACK pending, block and wait if (window 1 sequence % window window - 1 waitSequence ! 0) { WaitAck(connection); // 等待ACK确认 }阶段三根据ACK结果调整窗口在 WaitAck 函数中实现窗口调整逻辑static void WaitAck(ConnBrConnection *connection) { // 等待ACK响应超时时间为100毫秒 // Wait for ACK response, timeout is 100ms int32_t ret ConnBrGetBrPendingPacket(..., WAIT_ACK_TIMEOUT_MILLS, ...); switch (ret) { case SOFTBUS_ALREADY_TRIGGERED: // ACK成功收到ACK Successfully Received connection-ackTimeoutCount 0; // 重置超时计数 // 窗口增大但不超过最大值 // Window increases, but not exceeding maximum connection-window connection-window MAX_WINDOW ? connection-window 1 : MAX_WINDOW; break; case SOFTBUS_TIMOUT: // ACK超时ACK Timeout connection-ackTimeoutCount 1; // 超时计数加1 // 每TIMEOUT_TIMES次超时窗口减1 // Every TIMEOUT_TIMES timeouts, decrease window by 1 if (connection-window MIN_WINDOW connection-ackTimeoutCount % TIMEOUT_TIMES 0) { connection-window connection-window - 1; } // 如果窗口过小且连续失败恢复到默认值 // If window too small and continuous failures, restore to default if (connection-window DEFAULT_WINDOW connection-ackTimeoutCount ACK_FAILED_TIMES) { connection-window DEFAULT_WINDOW; } break; } }这里ack响应处理分两种情况1.成功收到。将超时计时器重置如果窗口小于最大窗口预设值窗口大小1.如果已经增大到上线了就只能是MAX_WINDOW 802.如果ack超时了这里设置的是每2次超时把窗口大小-1。如果窗口大小小于默认大小并且超时次数大于ACK_FAILED_TIMES3次把窗口大小重置为默认大小20这里不是很理解按道理是设置最小值MIN_WINDOW102.5 ACK控制消息格式ACK请求和响应通过JSON格式的控制消息传递// ACK消息序列化上下文ACK Message Serialization Context typedef struct { uint32_t connectionId; // 连接ID int32_t flag; // 优先级标志 enum BrCtlMessageMethod method; // 消息类型 union { struct { int32_t window; // 当前窗口大小 int64_t seq; // 当前序列号 } ackRequestResponse; }; } BrCtlMessageSerializationContext;消息类型定义enum BrCtlMessageMethod { BR_METHOD_NOTIFY_REQUEST 1, // 通知请求 BR_METHOD_NOTIFY_RESPONSE 2, // 通知响应 BR_METHOD_NOTIFY_ACK 4, // ACK通知 BR_METHOD_ACK_RESPONSE 5, // ACK响应 };2.6 完整流程图发送数据流程窗口调整流程三、流量控制Flow Control3.1 设计思路如果说拥塞控制是根据路况调整车速那么流量控制就是限制每小时的总行驶里程。它基于时间窗口和配额两个维度进行限制。3.2 核心数据结构在 softbus_conn_flow_control.h 中定义// 流量控制参数范围Flow Control Parameter Range #define MIN_WINDOW_IN_MILLIS 100 // 最小时间窗口100毫秒 #define MAX_WINDOW_IN_MILLIS 2000 // 最大时间窗口2秒 #define MIN_QUOTA_IN_BYTES (10 * 1024) // 最小配额10KB #define MAX_QUOTA_IN_BYTES (2 * 1024 * 1024) // 最大配额2MB struct ConnSlideWindowController { // 核心接口Core Interfaces int32_t (*apply)(struct ConnSlideWindowController *self, int32_t expect); int32_t (*enable)(struct ConnSlideWindowController *self, int32_t windowInMillis, int32_t quotaInBytes); int32_t (*disable)(struct ConnSlideWindowController *self); // 受锁保护的字段Lock-Protected Fields SoftBusMutex lock; bool active; // 是否激活流量控制 int32_t windowInMillis; // 时间窗口大小毫秒 int32_t quotaInBytes; // 配额大小字节 ListNode histories; // 历史记录链表 };3.3 工作原理3.4 Apply方法核心逻辑在 Apply 函数中实现static int32_t Apply(struct ConnSlideWindowController *self, int32_t expect) { // 如果未激活直接返回期望值不限制 // If not active, return expected value directly (no limit) if (!self-active) { return expect; } // 清理过期记录计算当前窗口内的已发送总量 // Clean expired records, calculate total sent in current window int32_t appliedTotal 0; timestamp_t now SoftBusGetSysTimeMs(); timestamp_t expiredTimestamp now - (timestamp_t)self-windowInMillis; LIST_FOR_EACH_ENTRY_SAFE(it, next, self-histories, ...) { if (it-timestamp expiredTimestamp) { appliedTotal it-amount; // 累加有效记录 } else { ListDelete(it-node); // 删除过期记录 SoftBusFree(it); } } // 如果已发送量达到配额等待后重试 // If sent amount reaches quota, wait and retry if (self-quotaInBytes appliedTotal) { unsigned int sleepMs self-windowInMillis - (now - currentWindowStartTimestamp); SoftBusSleepMs(sleepMs); return Apply(self, expect); // 递归重试 } // 计算实际可发送量 // Calculate actual sendable amount int32_t remain self-quotaInBytes - appliedTotal; int32_t amount remain expect ? expect : remain; // 记录本次发送历史 // Record this send history struct HistoryNode *history SoftBusCalloc(sizeof(*history)); history-amount amount; history-timestamp now; ListAdd(self-histories, history-node); return amount; }3.5 在发送流程中的应用在 BrTransSend 函数中int32_t BrTransSend(uint32_t connectionId, int32_t socketHandle, uint32_t mtu, const uint8_t *data, uint32_t dataLen) { uint32_t waitWriteLen dataLen; while (waitWriteLen 0) { // 计算本次期望发送量 // Calculate expected send amount for this time uint32_t expect waitWriteLen mtu ? mtu : waitWriteLen; // 通过流量控制器申请实际可发送量 // Apply for actual sendable amount through flow controller int32_t amount g_flowController-apply(g_flowController, (int32_t)expect); // 实际写入数据 // Actually write data int32_t writeLen g_sppDriver-Write(socketHandle, data, amount); data writeLen; waitWriteLen - (uint32_t)writeLen; } return SOFTBUS_OK; }3.6 配置接口通过 ConnBrTransConfigPostLimit 进行配置int32_t ConnBrTransConfigPostLimit(const LimitConfiguration *configuration) { if (!configuration-active) { // 禁用流量控制 // Disable flow control ret g_flowController-disable(g_flowController); } else { // 启用流量控制设置时间窗口和配额 // Enable flow control, set time window and quota ret g_flowController-enable(g_flowController, configuration-windowInMillis, configuration-quotaInBytes); } }四、ACL碰撞处理ACL Collision Handling4.1 什么是ACL碰撞ACLAsynchronous Connection-Less异步无连接是蓝牙的一种数据传输模式。当两个设备同时尝试建立连接时就会发生ACL碰撞导致连接失败。4.2 碰撞检测条件在 AuthenticationFailedAndRetry 函数中定义static int32_t AuthenticationFailedAndRetry(ConnBrConnection *connection, ConnBrDevice *connectingDevice, const char *anomizeAddress) { bool collision false; // 遍历底层连接状态列表 // Iterate through underlying connection status list LIST_FOR_EACH_ENTRY(it, connection-connectProcessStatus-list, ...) { // 判断是否为碰撞相关错误 // Check if its a collision-related error if (it-result CONN_BR_CONNECT_UNDERLAYER_ERROR_CONNECTION_EXISTS || // 连接已存在 it-result CONN_BR_CONNECT_UNDERLAYER_ERROR_CONTROLLER_BUSY || // 控制器忙 it-result CONN_BR_CONNECT_UNDERLAYER_ERROR_CONN_SDP_BUSY || // SDP忙 it-result CONN_BR_CONNECT_UNDERLAYER_ERROR_CONN_AUTH_FAILED || // 认证失败 it-result CONN_BR_CONNECT_UNDERLAYER_ERROR_CONN_RFCOM_DM) { // RFCOMM断开 connection-retryCount 1; // 重试计数加1 collision true; break; } } }4.3 碰撞处理策略// 碰撞处理超时定义Collision Handling Timeout Definitions #define BR_CONNECTION_ACL_RETRY_CONNECT_COLLISION_MILLIS (3 * 1000) // RFCOMM错误等待3秒 #define BR_CONNECTION_ACL_CONNECT_COLLISION_MILLIS (6 * 1000) // 其他错误等待6秒 #define MAX_RETRY_COUNT 2 // 最大重试次数 if (collision connection-retryCount MAX_RETRY_COUNT) { CONN_LOGW(CONN_BR, acl collision, wait for retry...); // 根据错误类型选择等待时间 // Choose wait time based on error type uint32_t time (it-result CONN_BR_CONNECT_UNDERLAYER_ERROR_CONN_RFCOM_DM) ? BR_CONNECTION_ACL_RETRY_CONNECT_COLLISION_MILLIS : // 3秒 BR_CONNECTION_ACL_CONNECT_COLLISION_MILLIS; // 6秒 // 清除正在连接的设备标记 // Clear the connecting device flag g_brManager.connecting NULL; // 处理ACL碰撞异常 // Process ACL collision exception ProcessAclCollisionException(connectingDevice, anomizeAddress, time); return SOFTBUS_OK; }4.4 碰撞处理流程在 ProcessAclCollisionException 函数中static void ProcessAclCollisionException(ConnBrDevice *device, const char *anomizeAddress, uint32_t duration) { CONN_LOGI(CONN_BR, addr%{public}s, duration%{public}u, anomizeAddress, duration); // 构造连接选项 // Construct connection option ConnectOption option; option.type CONNECT_BR; strcpy_s(option.brOption.brMac, BT_MAC_LEN, device-addr); // 将连接请求挂起指定时间 // Pend the connection request for specified duration BrPendConnection(option, duration); // 设置设备状态为挂起 // Set device state to pending device-state BR_DEVICE_STATE_PENDING; // 将设备加入挂起队列 // Add device to pending queue PendingDevice(device, anomizeAddress); }4.5 挂起机制在 BrPendConnection 中实现static int32_t BrPendConnection(const ConnectOption *option, uint32_t time) { // 验证参数挂起时间不能超过最大值 // Validate parameters: pend time cannot exceed maximum CONN_CHECK_AND_RETURN_RET_LOGW(time BR_CONNECTION_PEND_TIMEOUT_MAX_MILLIS, ...); // 查找或创建挂起记录 // Find or create pending record BrPending *target NULL; LIST_FOR_EACH_ENTRY(it, g_brManager.pendings-list, BrPending, node) { if (StrCmpIgnoreCase(it-addr, option-brOption.brMac) 0) { target it; break; } } if (target ! NULL) { // 更新挂起时间 // Update pending time target-pendInfo-duration time; } else { // 创建新的挂起记录 // Create new pending record BrPending *pending SoftBusCalloc(sizeof(BrPending)); strcpy_s(pending-addr, BT_MAC_LEN, option-brOption.brMac); ListTailInsert(g_brManager.pendings-list, pending-node); } }4.6 完整处理流程五、三大机制的协同工作Collaboration of Three Mechanisms5.1 数据发送完整流程5.2 机制对比维度流量控制拥塞控制ACL碰撞处理控制目标限制发送速率适应网络质量处理连接冲突时间尺度毫秒级100ms-2s包级别每100ms秒级3-6s调整依据时间窗口配额ACK成功率底层错误码作用范围全局单个连接单个连接实现位置flow_control.cbr_trans.cbr_manager.c六、总结Conclusion软总线连接模块的这三个核心机制体现了以下设计哲学6.1 分层控制Layered Control6.2 自适应调节Adaptive Adjustment拥塞控制根据ACK反馈动态调整窗口大小流量控制根据时间窗口自动清理过期记录ACL碰撞处理根据错误类型智能选择重试策略6.3 容错设计Fault Tolerance最大重试次数防止无限重试超时保护避免永久阻塞状态恢复失败后恢复到默认状态6.4 核心设计原则原则体现防御性编程参数校验、边界检查、错误处理资源管理历史记录清理、内存释放、锁保护可配置性窗口大小、超时时间、配额均可配置可观测性详细的日志记录、审计信息上报通过这三个机制的协同工作软蓝牙连接模块能够在复杂的网络环境下保持高效、稳定、可靠的数据传输能力。