【Bluedroid】HFP连接流程源码分析(二)
接上一篇【Bluedroid】HFP连接流程源码分析(一)-CSDN博客分析。本篇主要围绕RFCOMM Connect 与 RFCOMM UA Frame 的处理流程来展开分析。
RFCOMM Connect
RFCOMM(Radio Frequency Communication)作为蓝牙协议栈的关键部分,旨在蓝牙设备间模拟串口通信,使传统串口应用能无缝迁移至蓝牙环境。RFCOMM连接的建立是一个复杂且有序的过程,涉及多个关键步骤:
-
服务发现与初始化:
-
利用蓝牙服务发现协议(SDP)查找目标设备上的RFCOMM服务。
-
本地设备RFCOMM层初始化,包括链路参数、配置信息等设置。
-
配置L2CAP层,为后续链路建立奠定基础。
-
-
连接请求:
-
发起方设备通过L2CAP层向目标设备发送连接请求。
-
请求携带本地设备标识、期望链路参数等信息。
-
-
配置协商:
-
目标设备接收连接请求后,双方进行配置协商。
-
协商内容包括最大传输单元(MTU)、流控方式等。
-
使用L2CA_ConfigInd和L2CA_ConfigRsp等消息进行交互。
-
-
链路建立:
-
完成配置协商后,正式建立RFCOMM链路。
-
链路两端进入就绪状态,可传输数据。
-
支持音频数据、文本信息等传输。
-
RFCOMM UA
RFCOMM UA(Unnumbered Acknowledge,无编号确认)帧在RFCOMM连接过程中扮演着重要角色:
-
帧的功能:
-
用于确认之前收到的特定控制帧。
-
如接收方同意并准备好链路连接,会回发UA帧确认SABME帧的链路建立请求。
-
-
帧结构:
-
遵循RFCOMM协议规定的帧结构。
-
包含控制字段、地址字段等。
-
控制字段携带帧类型、轮询/终结标志等信息。
-
地址字段用于标识链路连接。
-
-
通信流程关联性:
-
UA帧是RFCOMM连接流程中的关键反馈环节。
-
从链路建立初期的控制帧交互到后续链路状态变更的确认场景,UA帧均适时出现。
-
确保通信双方知晓链路相关操作是否被正确接收与认可,促进通信流程顺畅运转。
-
RFCOMM Connect与RFCOMM UA Frame共同构成了RFCOMM协议中连接建立与确认的重要机制。通过服务发现、连接请求、配置协商与链路建立等步骤,RFCOMM Connect实现了蓝牙设备间的串口通信模拟。而RFCOMM UA Frame则作为关键反馈环节,确保了通信双方对链路相关操作的正确接收与认可。
RFCOMM_CreateConnectionWithSecurity
/packages/modules/Bluetooth/system/stack/rfcomm/port_api.cc
/********************************************************************************* Function RFCOMM_CreateConnectionWithSecurity** Description RFCOMM_CreateConnectionWithSecurity function is used from*the application to establish serial port connection to the peer device, or*allow RFCOMM to accept a connection from the peer application.** Parameters: scn - Service Channel Number as registered with* the SDP (server) or obtained using SDP from* the peer device (client).* is_server - true if requesting application is a server* mtu - Maximum frame size the application can accept* bd_addr - address of the peer (client)* p_handle - OUT pointer to the handle.* p_mgmt_cb - pointer to callback function to receive* connection up/down events.* sec_mask - bitmask of BTM_SEC_* values indicating the* minimum security requirements for this*connection Notes:** Server can call this function with the same scn parameter multiple times if* it is ready to accept multiple simulteneous connections.** DLCI for the connection is (scn * 2 + 1) if client originates connection on* existing none initiator multiplexer channel. Otherwise it is (scn * 2).* For the server DLCI can be changed later if client will be calling it using* (scn * 2 + 1) dlci.*******************************************************************************/
int RFCOMM_CreateConnectionWithSecurity(uint16_t uuid, uint8_t scn,bool is_server, uint16_t mtu,const RawAddress& bd_addr,uint16_t* p_handle,tPORT_CALLBACK* p_mgmt_cb,uint16_t sec_mask) {*p_handle = 0;if ((scn == 0) || (scn > RFCOMM_MAX_SCN)) {// Server Channel Number (SCN) should be in range [1, 30]log::error("Invalid SCN, bd_addr={}, scn={}, is_server={}, mtu={}, uuid={}",ADDRESS_TO_LOGGABLE_STR(bd_addr), static_cast<int>(scn),is_server, static_cast<int>(mtu), loghex(uuid));return (PORT_INVALID_SCN);}// For client that originates connection on the existing none initiator// multiplexer channel, DLCI should be odd.// DLCI计算:根据SCN和连接类型(客户端或服务器)计算DLCI(数据链路连接标识符)// 如果客户端在非发起方复用通道上发起连接,DLCI应为奇数;否则为偶数。uint8_t dlci;tRFC_MCB* p_mcb = port_find_mcb(bd_addr);if (p_mcb && !p_mcb->is_initiator && !is_server) {dlci = static_cast<uint8_t>((scn << 1) + 1);} else {dlci = (scn << 1);}// On the client side, do not allow the same (dlci, bd_addr) to be opened// twice by applicationtPORT* p_port;// 检查重复连接:对于客户端,检查是否已存在具有相同(DLCI,bd_addr)的打开端口 if (!is_server) {p_port = port_find_port(dlci, bd_addr);if (p_port != nullptr) {// if existing port is also a client port, error outif (!p_port->is_server) {log::error("already at opened state {}, RFC_state={}, MCB_state={}, ""bd_addr={}, scn={}, is_server={}, mtu={}, uuid={}, dlci={}, ""p_mcb={}, port={}",static_cast<int>(p_port->state),static_cast<int>(p_port->rfc.state),(p_port->rfc.p_mcb ? p_port->rfc.p_mcb->state : 0),ADDRESS_TO_LOGGABLE_STR(bd_addr), scn, is_server, mtu, loghex(uuid),dlci, fmt::ptr(p_mcb), p_port->handle);*p_handle = p_port->handle;return (PORT_ALREADY_OPENED);}}}// On the server side, always allocate a new port.// 分配端口:对于服务器,始终分配一个新端口p_port = port_allocate_port(dlci, bd_addr);if (p_port == nullptr) {log::error("no resources, bd_addr={}, scn={}, is_server={}, mtu={}, uuid={}, ""dlci={}",ADDRESS_TO_LOGGABLE_STR(bd_addr), scn, is_server, mtu, loghex(uuid),dlci);return PORT_NO_RESOURCES;}p_port->sec_mask = sec_mask;*p_handle = p_port->handle;// Get default signal state// 设置端口参数:根据UUID设置默认信号状态,并分配其他端口参数,如MTU、连接状态、UUID、是否是服务器、SCN、事件掩码等switch (uuid) {case UUID_PROTOCOL_OBEX:p_port->default_signal_state = PORT_OBEX_DEFAULT_SIGNAL_STATE;break;case UUID_SERVCLASS_SERIAL_PORT:p_port->default_signal_state = PORT_SPP_DEFAULT_SIGNAL_STATE;break;case UUID_SERVCLASS_LAN_ACCESS_USING_PPP:p_port->default_signal_state = PORT_PPP_DEFAULT_SIGNAL_STATE;break;case UUID_SERVCLASS_DIALUP_NETWORKING:case UUID_SERVCLASS_FAX:p_port->default_signal_state = PORT_DUN_DEFAULT_SIGNAL_STATE;break;default:p_port->default_signal_state =(PORT_DTRDSR_ON | PORT_CTSRTS_ON | PORT_DCD_ON);break;}// Assign port specific valuesp_port->state = PORT_CONNECTION_STATE_OPENING;p_port->uuid = uuid;p_port->is_server = is_server;p_port->scn = scn;p_port->ev_mask = 0;// Find MTU// If the MTU is not specified (0), keep MTU decision until the PN frame has// to be send at that time connection should be established and we will know// for sure our prefered MTU// MTU处理:如果指定了MTU,将其设置为指定值// 如果未指定MTU,则使用默认L2CAP MTU减去RFCOMM数据开销uint16_t rfcomm_mtu = L2CAP_MTU_SIZE - RFCOMM_DATA_OVERHEAD;if (mtu) {p_port->mtu = (mtu < rfcomm_mtu) ? mtu : rfcomm_mtu;} else { p_port->mtu = rfcomm_mtu;}// Other states// server doesn't need to release port when closing// 其他状态设置:对于服务器,设置端口以保持端口句柄和MTU。设置本地控制信号和流控制if (is_server) {p_port->keep_port_handle = true;// keep mtu that user asked, p_port->mtu could be updated during param// negotiationp_port->keep_mtu = p_port->mtu;}p_port->local_ctrl.modem_signal = p_port->default_signal_state;p_port->local_ctrl.fc = false;p_port->p_mgmt_callback = p_mgmt_cb;p_port->bd_addr = bd_addr;log::info("bd_addr={}, scn={}, is_server={}, mtu={}, uuid={}, dlci={}, ""signal_state={}, p_port={}",ADDRESS_TO_LOGGABLE_STR(bd_addr), scn, is_server, mtu, loghex(uuid), dlci,loghex(p_port->default_signal_state), fmt::ptr(p_port));// If this is not initiator of the connection need to just waitif (p_port->is_server) {return (PORT_SUCCESS);}// Open will be continued after security checks are passedreturn port_open_continue(p_port);
}
负责创建 RFCOMM 连接的前期准备工作,包括参数检查、资源分配、状态及参数配置等,同时处理了客户端重复连接、服务器端特殊需求等情况,为安全可靠的 RFCOMM 连接奠定基础。
port_open_continue
packages/modules/Bluetooth/system/stack/rfcomm/port_rfc.cc
/********************************************************************************* Function port_open_continue** Description This function is called after security manager completes* required security checks.** Returns PORT_SUCCESS or PORT_[ERROR]*******************************************************************************/
int port_open_continue(tPORT* p_port) {log::verbose("port_open_continue, p_port:{}", fmt::ptr(p_port));/* Check if multiplexer channel has already been established */// 尝试为给定的BD地址分配一个复用器通道(Multiplexer Channel)tRFC_MCB* p_mcb = rfc_alloc_multiplexer_channel(p_port->bd_addr, true);if (p_mcb == nullptr) {log::warn("port_open_continue no mx channel");port_release_port(p_port);return (PORT_NO_RESOURCES);}p_port->rfc.p_mcb = p_mcb;// 记录该端口的DLCI(数据链路连接标识符)和句柄p_mcb->port_handles[p_port->dlci] = p_port->handle;/* Connection is up and we know local and remote features, select MTU */port_select_mtu(p_port); // 选择MTUif (p_mcb->state == RFC_MX_STATE_CONNECTED) {RFCOMM_ParameterNegotiationRequest(p_mcb, p_port->dlci, p_port->mtu); // 发送RFCOMM参数协商请求} else if ((p_mcb->state == RFC_MX_STATE_IDLE) ||(p_mcb->state == RFC_MX_STATE_DISC_WAIT_UA)) {// In RFC_MX_STATE_IDLE state, MX state machine will create connection// In RFC_MX_STATE_DISC_WAIT_UA state, MX state machine will recreate// connection after disconnecting is completedRFCOMM_StartReq(p_mcb); // 发送RFCOMM开始请求} else {// MX state machine ignores RFC_MX_EVENT_START_REQ in these states// When it enters RFC_MX_STATE_CONNECTED, it will check any openning portslog::verbose("port_open_continue: mx state({}) mx channel is opening",p_mcb->state);}return (PORT_SUCCESS);
}
继续打开端口的流程。涉及到复用器通道的分配、状态处理和MTU的选择,以及根据复用器的当前状态采取相应的动作。
RFCOMM_StartReq
/packages/modules/Bluetooth/system/stack/rfcomm/rfc_port_if.cc
/********************************************************************************* Function RFCOMM_StartReq** Description This function handles Start Request from the upper layer.* If RFCOMM multiplexer channel can not be allocated* send start not accepted confirmation. Otherwise dispatch* start event to the state machine.*******************************************************************************/
void RFCOMM_StartReq(tRFC_MCB* p_mcb) {rfc_mx_sm_execute(p_mcb, RFC_MX_EVENT_START_REQ, nullptr);
}
起到一个简单的转接作用,把来自上层的启动请求,原封不动地传递给复用器状态机 rfc_mx_sm_execute,让状态机去处理后续的逻辑。
rfc_mx_sm_execute(RFC_MX_EVENT_START_REQ)
/packages/modules/Bluetooth/system/stack/rfcomm/rfc_mx_fsm.cc
/********************************************************************************* Function rfc_mx_sm_execute** Description This function sends multiplexer events through the state* machine.** Returns void*******************************************************************************/
void rfc_mx_sm_execute(tRFC_MCB* p_mcb, tRFC_MX_EVENT event, void* p_data) {CHECK(p_mcb != nullptr) << __func__ << ": NULL mcb for event " << event;log::info("RFCOMM peer:{} event:{} state:{}",ADDRESS_TO_LOGGABLE_CSTR(p_mcb->bd_addr), event,rfcomm_mx_state_text(static_cast<tRFC_MX_STATE>(p_mcb->state)));switch (p_mcb->state) {case RFC_MX_STATE_IDLE:rfc_mx_sm_state_idle(p_mcb, event, p_data);break;case RFC_MX_STATE_WAIT_CONN_CNF:rfc_mx_sm_state_wait_conn_cnf(p_mcb, event, p_data);break;case RFC_MX_STATE_CONFIGURE:rfc_mx_sm_state_configure(p_mcb, event, p_data);break;case RFC_MX_STATE_SABME_WAIT_UA:rfc_mx_sm_sabme_wait_ua(p_mcb, event, p_data);break;case RFC_MX_STATE_WAIT_SABME:rfc_mx_sm_state_wait_sabme(p_mcb, event, p_data);break;case RFC_MX_STATE_CONNECTED:rfc_mx_sm_state_connected(p_mcb, event, p_data);break;case RFC_MX_STATE_DISC_WAIT_UA:rfc_mx_sm_state_disc_wait_ua(p_mcb, event, p_data);break;default:log::error("Received unexpected event:{} in state:{}", event,p_mcb->state);}
}
进入RFCOMM协议栈状态机,一开始的状态是IDLE(RFC_MX_STATE_IDLE)。调用 rfc_mx_sm_state_idle处理事件。
rfc_mx_sm_state_idle
packages/modules/Bluetooth/system/stack/rfcomm/rfc_mx_fsm.cc
/********************************************************************************* Function rfc_mx_sm_state_idle** Description This function handles events when the multiplexer is in* IDLE state. This state exists when connection is being* initially established.** Returns void*******************************************************************************/
void rfc_mx_sm_state_idle(tRFC_MCB* p_mcb, tRFC_MX_EVENT event, void* p_data) {switch (event) {case RFC_MX_EVENT_START_REQ: {/* Initialize L2CAP MTU */p_mcb->peer_l2cap_mtu = L2CAP_DEFAULT_MTU - RFCOMM_MIN_OFFSET - 1;uint16_t lcid = L2CA_ConnectReq(BT_PSM_RFCOMM, p_mcb->bd_addr); // 建立与对端设备的L2CAP通道if (lcid == 0) {log::error("failed to open L2CAP channel for {}",ADDRESS_TO_LOGGABLE_STR(p_mcb->bd_addr));rfc_save_lcid_mcb(nullptr, p_mcb->lcid);p_mcb->lcid = 0;PORT_StartCnf(p_mcb, RFCOMM_ERROR); // 保存L2CAP通道ID(lcid)return;}p_mcb->lcid = lcid;/* Save entry for quicker access to mcb based on the LCID */rfc_save_lcid_mcb(p_mcb, p_mcb->lcid);p_mcb->state = RFC_MX_STATE_WAIT_CONN_CNF; // 等待连接确认return;}case RFC_MX_EVENT_CONN_IND:rfc_timer_start(p_mcb, RFCOMM_CONN_TIMEOUT);p_mcb->state = RFC_MX_STATE_CONFIGURE;return;case RFC_MX_EVENT_SABME:break;case RFC_MX_EVENT_UA:case RFC_MX_EVENT_DM:return;case RFC_MX_EVENT_DISC:rfc_send_dm(p_mcb, RFCOMM_MX_DLCI, true);return;case RFC_MX_EVENT_UIH:rfc_send_dm(p_mcb, RFCOMM_MX_DLCI, false);return;default:log::error("Mx error state {} event {}", p_mcb->state, event);return;}log::verbose("RFCOMM MX ignored - evt:{} in state:{}", event, p_mcb->state);
}
专门处理复用器处于IDLE状态时的各种事件。IDLE状态通常表示连接正在初始建立阶段。
针对事件RFC_MX_EVENT_START_REQ,尝试通过L2CA_ConnectReq建立与对端设备的L2CAP通道。参数指定了协议 / 服务复用器(PSM)为 BT_PSM_RFCOMM 以及目标蓝牙设备地址。并返回唯一的L2CAP通道ID。
在蓝牙通信架构里,当建立 RFCOMM(仿真串行端口的协议)的连接时,首先需要发起 L2CAP(逻辑链路控制与适配协议)连接请求。这是因为 L2CAP 为上层协议(如 RFCOMM)提供了面向连接和无连接的数据服务,是构建稳定蓝牙链路的基础层级。比如,手机要连接蓝牙音频设备进行音频传输,底层就得先发起这个请求来准备好链路。
L2CA_ConnectReq
L2CA_ConnectReq链接分析见 https://blog.csdn.net/weixin_37800531/article/details/141754774
直接看HCIlog:
L2CAP Connection Request(RFCOMM)
当RFCOMM层需要建立一个到远程设备的连接时,它会通过L2CAP层发送一个连接请求。这个请求通常包含以下信息:
-
PSM(Protocol/Service Multiplexer):对于RFCOMM,通常使用固定的PSM值(如0x0003),以标识这是一个RFCOMM连接请求。
-
其他L2CAP参数:可能包括服务质量(QoS)要求、通道标识等。
L2CAP层接收到这个请求后,会尝试与远程设备的L2CAP层建立连接。发送一个包含上述信息的L2CAP连接请求帧到远程设备,并等待一个连接响应帧。
L2CAP Connection Response
远程设备的L2CAP层在接收到连接请求后,会根据其策略和资源情况决定是否接受连接。然后,它会发送一个L2CAP连接响应帧回给发起请求的设备。
这个响应帧通常包含以下信息:
-
状态代码:指示连接请求是否被接受(成功)或被拒绝(失败)。如果失败,可能还包含一个错误代码来说明失败的原因。
-
源CID(Channel Identifier):如果连接成功,这是分配给新建立的L2CAP通道的本地通道标识符。
-
目的CID:这是请求中指定的远程通道标识符的回声(echo),通常用于确认。
如果连接成功建立,发起请求的设备现在可以使用这个L2CAP通道与远程设备进行RFCOMM通信。RFCOMM层将使用这个通道来传输和接收数据,模拟串行端口的通信行为。
acl_rcv_acl_data
ACL数据处理流程见 https://blog.csdn.net/weixin_37800531/article/details/141754774。
直接分析协议栈对L2CAP_CMD_CONN_RSP的处理
process_l2cap_cmd(L2CAP_CMD_CONN_RSP)
/packages/modules/Bluetooth/system/stack/l2cap/l2c_main.cccase L2CAP_CMD_CONN_RSP: {uint16_t lcid;if (p + 8 > p_next_cmd) {log::warn("Not enough data for L2CAP_CMD_CONN_REQ");return;}STREAM_TO_UINT16(con_info.remote_cid, p);STREAM_TO_UINT16(lcid, p);STREAM_TO_UINT16(con_info.l2cap_result, p);STREAM_TO_UINT16(con_info.l2cap_status, p);tL2C_CCB* p_ccb = l2cu_find_ccb_by_cid(p_lcb, lcid);if (!p_ccb) {log::warn("no CCB for conn rsp, LCID: {} RCID: {}", lcid,con_info.remote_cid);break;}if (p_ccb->local_id != id) {log::warn("con rsp - bad ID. Exp: {} Got: {}", p_ccb->local_id, id);break;}if (con_info.l2cap_result == L2CAP_CONN_OK) {l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONNECT_RSP, &con_info);} else if (con_info.l2cap_result == L2CAP_CONN_PENDING) {l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONNECT_RSP_PND, &con_info);} else {l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONNECT_RSP_NEG, &con_info);p_rcb = p_ccb->p_rcb;if (p_rcb->psm == BT_PSM_RFCOMM) {bluetooth::shim::GetSnoopLogger()->AddRfcommL2capChannel(p_lcb->Handle(), p_ccb->local_cid, p_ccb->remote_cid);} else if (p_rcb->log_packets) {bluetooth::shim::GetSnoopLogger()->AcceptlistL2capChannel(p_lcb->Handle(), p_ccb->local_cid, p_ccb->remote_cid);}}break;}
负责处理来自远程设备的连接响应,并根据响应结果执行相应的状态机操作。如果l2cap_result为L2CAP_CONN_OK(连接成功),则调用l2c_csm_execute函数,传入相应的事件L2CEVT_L2CAP_CONNECT_RSP和连接信息con_info,以执行状态机。
l2c_csm_execute(L2CEVT_L2CAP_CONNECT_RSP)
case CST_W4_L2CAP_CONNECT_RSP:l2c_csm_w4_l2cap_connect_rsp(p_ccb, event, p_data);break;
l2c_csm_w4_l2cap_connect_rsp
/packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc
/********************************************************************************* Function l2c_csm_w4_l2cap_connect_rsp** Description This function handles events when the channel is in* CST_W4_L2CAP_CONNECT_RSP state.** Returns void*******************************************************************************/
static void l2c_csm_w4_l2cap_connect_rsp(tL2C_CCB* p_ccb, tL2CEVT event,void* p_data) {tL2C_CONN_INFO* p_ci = (tL2C_CONN_INFO*)p_data;tL2CA_DISCONNECT_IND_CB* disconnect_ind =p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb;tL2CA_CREDIT_BASED_CONNECT_CFM_CB* credit_based_connect_cfm =p_ccb->p_rcb->api.pL2CA_CreditBasedConnectCfm_Cb;uint16_t local_cid = p_ccb->local_cid;tL2C_LCB* p_lcb = p_ccb->p_lcb;log::debug("LCID: 0x{:04x} st: W4_L2CAP_CON_RSP evt: {}", p_ccb->local_cid,l2c_csm_get_event_name(event));switch (event) {...case L2CEVT_L2CAP_CONNECT_RSP: /* Got peer connect confirm */p_ccb->remote_cid = p_ci->remote_cid;if (p_ccb->p_lcb->transport == BT_TRANSPORT_LE) {/* Connection is completed */alarm_cancel(p_ccb->l2c_ccb_timer);p_ccb->chnl_state = CST_OPEN;l2c_csm_indicate_connection_open(p_ccb);p_ccb->local_conn_cfg = p_ccb->p_rcb->coc_cfg;p_ccb->remote_credit_count = p_ccb->p_rcb->coc_cfg.credits;l2c_csm_execute(p_ccb, L2CEVT_L2CA_CONNECT_RSP, NULL);} else {p_ccb->chnl_state = CST_CONFIG;alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CFG_TIMEOUT_MS,l2c_ccb_timer_timeout, p_ccb);}log::debug("Calling Connect_Cfm_Cb(), CID: 0x{:04x}, Success",p_ccb->local_cid);l2c_csm_send_config_req(p_ccb); // 发送一个L2CAP配置请求break;...default:log::error("Handling unexpected event:{}", l2c_csm_get_event_name(event));}log::verbose("Exit chnl_state={} [{}], event={} [{}]",channel_state_text(p_ccb->chnl_state), p_ccb->chnl_state,l2c_csm_get_event_name(event), event);
}
负责处理连接响应事件,并根据传输类型(LE或BR/EDR)执行不同的操作。它还负责启动配置过程,确保信道正确配置以供后续数据传输使用。
l2c_csm_send_config_req
/packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc
// Send a config request and adjust the state machine
static void l2c_csm_send_config_req(tL2C_CCB* p_ccb) {tL2CAP_CFG_INFO config{}; // 存储L2CAP信道的配置信息config.mtu_present = true; // 表示MTU字段是有效的config.mtu = p_ccb->p_rcb->my_mtu; // my_mtu是本地设备希望使用的MTU大小p_ccb->max_rx_mtu = config.mtu; // 表示本地设备能够接收的最大MTU大小// 设置FCR(Flow Control Rules,流控规则)if (p_ccb->p_rcb->ertm_info.preferred_mode != L2CAP_FCR_BASIC_MODE) {config.fcr_present = true; // 表示FCR字段是有效的config.fcr = kDefaultErtmOptions;}p_ccb->our_cfg = config;l2c_csm_execute(p_ccb, L2CEVT_L2CA_CONFIG_REQ, &config);
}
负责准备L2CAP信道的配置信息,包括MTU和FCR(如果适用),然后借助状态机推动链路配置进程。确保两端设备在传输参数、流控模式等关键方面达成一致,为稳定高效的蓝牙数据传输筑牢基础。
l2c_csm_execute(L2CEVT_L2CA_CONFIG_REQ)
case CST_CONFIG:l2c_csm_config(p_ccb, event, p_data);break;
l2c_csm_config
packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc
/********************************************************************************* Function l2c_csm_config** Description This function handles events when the channel is in* CONFIG state.** Returns void*******************************************************************************/
static void l2c_csm_config(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data) {tL2CAP_CFG_INFO* p_cfg = (tL2CAP_CFG_INFO*)p_data;tL2CA_DISCONNECT_IND_CB* disconnect_ind =p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb;uint16_t local_cid = p_ccb->local_cid;uint8_t cfg_result;tL2C_LCB* p_lcb = p_ccb->p_lcb;tL2C_CCB* temp_p_ccb;tL2CAP_LE_CFG_INFO* p_le_cfg = (tL2CAP_LE_CFG_INFO*)p_data;log::debug("LCID: 0x{:04x} st: CONFIG evt: {}", p_ccb->local_cid,l2c_csm_get_event_name(event));switch (event) {...case L2CEVT_L2CA_CONFIG_REQ: /* Upper layer config req */l2cu_process_our_cfg_req(p_ccb, p_cfg); // 处理本地配置请求l2cu_send_peer_config_req(p_ccb, p_cfg); // 向对端发送配置请求alarm_set_on_mloop(p_ccb->l2c_ccb_timer, L2CAP_CHNL_CFG_TIMEOUT_MS,l2c_ccb_timer_timeout, p_ccb);break;...default:log::error("Handling unexpected event:{}", l2c_csm_get_event_name(event));}log::verbose("Exit chnl_state={} [{}], event={} [{}]",channel_state_text(p_ccb->chnl_state), p_ccb->chnl_state,l2c_csm_get_event_name(event), event);
}
通道处于 CONFIG 状态时的事件处理,对上层配置请求事件进行有序响应,包括本地处理、向对等设备发送请求以及设置超时机制。
l2cu_send_peer_config_req
l2cu_send_peer_config_req见 https://blog.csdn.net/weixin_37800531/article/details/141754774
当一方设备(通常是发起连接的设备,也称为“主机”或“主设备”)希望设置与另一方设备(从设备或“从机”)之间的L2CAP信道参数时,会发送一个L2CAP配置请求(Configure Request)。这个请求包含了主机希望设置的信道参数。
直接看对应的HCI LOG:
L2CAP Configure Request ( MTU=1'691, ACL-U Flow Events)
在 L2CAP 链路建立过程中,配置请求用于协商两端设备间的链路参数,确保后续数据传输的高效与稳定。这里指定的 MTU为 1,691 字节,以及涉及 ACL-U(异步无连接链路)流事件相关配置,是为了让双方就数据传输的包大小上限与流控机制达成共识。
L2CAP配置请求包含以下参数:
-
MTU(Maximum Transmission Unit):最大传输单元设置为1691字节。MTU定义了可以通过L2CAP信道一次性传输的最大数据量。这个值可以根据应用需求进行调整,但受到蓝牙规范中定义的限制(对于基本L2CAP信道,MTU最大值为65535字节,但考虑到其他因素,如设备能力和蓝牙堆栈实现,实际可用值可能较小)。
-
ACL-U Flow Events:ACL(Asynchronous Connection-Less)数据链路上的流量事件。这个参数与流量控制相关,用于管理信道上的数据传输。ACL-U Flow Events允许设备监控和报告信道上的数据传输情况,以便进行适当的流量调整。然而,需要注意的是,不是所有的L2CAP信道都会使用流量事件,取决于信道类型和设备配置。
L2CAP Configure Response
在从设备接收到配置请求后,它会评估请求中的参数,并根据自己的能力和配置来决定是否接受这些参数。然后,从设备会发送一个L2CAP配置响应(Configure Response)回给主机。
配置响应包含以下内容:
-
结果代码:指示从设备是否接受了配置请求。如果接受,结果代码将指示成功;如果不接受,则可能包含拒绝的原因(如参数不支持、资源不足等)。
-
实际使用的参数:如果配置请求被接受,并且从设备对请求中的某些参数进行了调整(例如,降低了MTU值以匹配自己的限制),则配置响应中将包含实际使用的参数值。
-
继续标志:在某些情况下,配置过程可能需要多个步骤(例如,当双方设备需要就多个参数进行协商时)。继续标志用于指示配置过程是否已完成,或者是否还需要进一步的配置请求和响应。
一旦双方设备就信道参数达成一致,L2CAP信道就被认为是配置完成的,可以开始传输数据了。如果配置过程中出现问题,则可能需要重新尝试配置,或者连接可能会被断开。
process_l2cap_cmd(L2CAP_CMD_CONFIG_RSP)
case L2CAP_CMD_CONFIG_RSP: {uint8_t* p_cfg_end = p + cmd_len;uint16_t lcid;if (p + 6 > p_next_cmd) {log::warn("Not enough data for L2CAP_CMD_CONFIG_RSP");return;}// 解析配置响应头STREAM_TO_UINT16(lcid, p);STREAM_TO_UINT16(cfg_info.flags, p);STREAM_TO_UINT16(cfg_info.result, p);cfg_info.flush_to_present = cfg_info.mtu_present =cfg_info.qos_present = cfg_info.fcr_present = cfg_info.fcs_present =false;//循环解析配置参数while (p < p_cfg_end) {uint8_t cfg_code, cfg_len;if (p + 2 > p_next_cmd) {log::warn("Not enough data for L2CAP_CMD_CONFIG_RSP sub_event");return;}STREAM_TO_UINT8(cfg_code, p);STREAM_TO_UINT8(cfg_len, p);switch (cfg_code & 0x7F) {case L2CAP_CFG_TYPE_MTU: // 解析最大传输单元cfg_info.mtu_present = true;if (p + 2 > p_next_cmd) {log::warn("Not enough data for L2CAP_CFG_TYPE_MTU");return;}STREAM_TO_UINT16(cfg_info.mtu, p);break;case L2CAP_CFG_TYPE_FLUSH_TOUT: // 解析刷新超时cfg_info.flush_to_present = true;if (p + 2 > p_next_cmd) {log::warn("Not enough data for L2CAP_CFG_TYPE_FLUSH_TOUT");return;}STREAM_TO_UINT16(cfg_info.flush_to, p);break;case L2CAP_CFG_TYPE_QOS: // 解析服务质量参数,包括QoS标志、服务类型、令牌速率、令牌桶大小、峰值带宽、延迟和延迟变化cfg_info.qos_present = true;if (p + 2 + 5 * 4 > p_next_cmd) {log::warn("Not enough data for L2CAP_CFG_TYPE_QOS");return;}STREAM_TO_UINT8(cfg_info.qos.qos_flags, p);STREAM_TO_UINT8(cfg_info.qos.service_type, p);STREAM_TO_UINT32(cfg_info.qos.token_rate, p);STREAM_TO_UINT32(cfg_info.qos.token_bucket_size, p);STREAM_TO_UINT32(cfg_info.qos.peak_bandwidth, p);STREAM_TO_UINT32(cfg_info.qos.latency, p);STREAM_TO_UINT32(cfg_info.qos.delay_variation, p);break;case L2CAP_CFG_TYPE_FCR: // 解析流量控制参数,包括模式、发送窗口大小、最大传输次数、重传超时、监视超时和最大PDU大小cfg_info.fcr_present = true;if (p + 3 + 3 * 2 > p_next_cmd) {log::warn("Not enough data for L2CAP_CFG_TYPE_FCR");return;}STREAM_TO_UINT8(cfg_info.fcr.mode, p);STREAM_TO_UINT8(cfg_info.fcr.tx_win_sz, p);STREAM_TO_UINT8(cfg_info.fcr.max_transmit, p);STREAM_TO_UINT16(cfg_info.fcr.rtrans_tout, p);STREAM_TO_UINT16(cfg_info.fcr.mon_tout, p);STREAM_TO_UINT16(cfg_info.fcr.mps, p);break;case L2CAP_CFG_TYPE_FCS: // 解析帧校验序列cfg_info.fcs_present = true;if (p + 1 > p_next_cmd) {log::warn("Not enough data for L2CAP_CFG_TYPE_FCS");return;}STREAM_TO_UINT8(cfg_info.fcs, p);break;case L2CAP_CFG_TYPE_EXT_FLOW: // 解析扩展流量规范参数cfg_info.ext_flow_spec_present = true;if (p + 2 + 2 + 3 * 4 > p_next_cmd) {log::warn("Not enough data for L2CAP_CFG_TYPE_EXT_FLOW");return;}STREAM_TO_UINT8(cfg_info.ext_flow_spec.id, p);STREAM_TO_UINT8(cfg_info.ext_flow_spec.stype, p);STREAM_TO_UINT16(cfg_info.ext_flow_spec.max_sdu_size, p);STREAM_TO_UINT32(cfg_info.ext_flow_spec.sdu_inter_time, p);STREAM_TO_UINT32(cfg_info.ext_flow_spec.access_latency, p);STREAM_TO_UINT32(cfg_info.ext_flow_spec.flush_timeout, p);break;}}tL2C_CCB* p_ccb = l2cu_find_ccb_by_cid(p_lcb, lcid); // 查找对应的通道控制块(CCB)if (p_ccb) {if (p_ccb->local_id != id) {log::warn("cfg rsp - bad ID. Exp: {} Got: {}", p_ccb->local_id, id);break;}if (cfg_info.result == L2CAP_CFG_OK) {l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONFIG_RSP, &cfg_info);} else {l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONFIG_RSP_NEG, &cfg_info);}} else {log::warn("Rcvd cfg rsp for unknown CID: 0x{:04x}", lcid);}break;}
处理L2CAP配置响应,解析其中的配置参数,并根据配置结果更新相应的通道状态。通过这种方式,蓝牙设备可以协商并确定数据传输的参数,确保数据能够正确、高效地传输。
l2c_csm_execute(L2CEVT_L2CAP_CONFIG_RSP)
case CST_CONFIG:l2c_csm_config(p_ccb, event, p_data);break;
l2c_csm_config
/packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc case L2CEVT_L2CAP_CONFIG_RSP: /* Peer config response */l2cu_process_peer_cfg_rsp(p_ccb, p_cfg); // 根据对端的配置参数更新当前连接控制块(p_ccb)中的配置信息/* TBD: When config options grow beyong minimum MTU (48 bytes)* logic needs to be added to handle responses with* continuation bit set in flags field.* 1. Send additional config request out until C-bit is cleared in* response*/p_ccb->config_done |= OB_CFG_DONE; // 表示本端已经发送了配置请求,并且收到了对端的响应if (p_ccb->config_done & IB_CFG_DONE) { // 表示双方的配置交换已经完成/* Verify two sides are in compatible modes before continuing */// 配置兼容性检查if (p_ccb->our_cfg.fcr.mode != p_ccb->peer_cfg.fcr.mode) {l2cu_send_peer_disc_req(p_ccb); // 发送断开连接请求log::warn("Calling Disconnect_Ind_Cb(Incompatible CFG), CID: 0x{:04x} No ""Conf Needed",p_ccb->local_cid);l2cu_release_ccb(p_ccb);(*disconnect_ind)(local_cid, false); // 调用disconnect_ind回调函数通知上层断开连接break;}p_ccb->config_done |= RECONFIG_FLAG;p_ccb->chnl_state = CST_OPEN;l2c_link_adjust_chnl_allocation();alarm_cancel(p_ccb->l2c_ccb_timer);/* If using eRTM and waiting for an ACK, restart the ACK timer */// 重新启动ERTM(Enhanced Retransmission Mode)的ACK计时器if (p_ccb->fcrb.wait_ack) l2c_fcr_start_timer(p_ccb);/*** check p_ccb->our_cfg.fcr.mon_tout and*p_ccb->our_cfg.fcr.rtrans_tout** we may set them to zero when sending config request during*renegotiation*/// ERTM模式下的超时调整if ((p_ccb->our_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) &&((p_ccb->our_cfg.fcr.mon_tout == 0) ||(p_ccb->our_cfg.fcr.rtrans_tout))) {l2c_fcr_adj_monitor_retran_timeout(p_ccb);}/* See if we can forward anything on the hold queue */// 发送待处理数据包if (!fixed_queue_is_empty(p_ccb->xmit_hold_q)) {l2c_link_check_send_pkts(p_ccb->p_lcb, 0, NULL);}}if (p_ccb->config_done & RECONFIG_FLAG) { // ,表示这是重新配置后的首次成功配置响应// Notify only once// 通道打开通知bluetooth::shim::GetSnoopLogger()->SetL2capChannelOpen(p_ccb->p_lcb->Handle(), p_ccb->local_cid, p_ccb->remote_cid,p_ccb->p_rcb->psm,p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE);}log::debug("Calling Config_Rsp_Cb(), CID: 0x{:04x}", p_ccb->local_cid);// 调用配置响应回调函数p_ccb->remote_config_rsp_result = p_cfg->result;if (p_ccb->config_done & IB_CFG_DONE) {l2c_csm_indicate_connection_open(p_ccb); // 通知连接已打开}break;
L2CAP协议栈中处理配置响应事件的核心部分,涉及到配置参数的交换、配置兼容性的检查、通道状态的更新、超时管理以及数据包的发送等多个方面。
l2c_csm_indicate_connection_open
packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc
static void l2c_csm_indicate_connection_open(tL2C_CCB* p_ccb) {// 判断连接是由本地设备发起还是由远程设备发起if (p_ccb->connection_initiator == L2CAP_INITIATOR_LOCAL) {(*p_ccb->p_rcb->api.pL2CA_ConnectCfm_Cb)(p_ccb->local_cid, L2CAP_CONN_OK);} else {if (*p_ccb->p_rcb->api.pL2CA_ConnectInd_Cb) {(*p_ccb->p_rcb->api.pL2CA_ConnectInd_Cb)(p_ccb->p_lcb->remote_bd_addr, p_ccb->local_cid, p_ccb->p_rcb->psm,p_ccb->remote_id);} else {log::warn("pL2CA_ConnectInd_Cb is null");}}if (p_ccb->chnl_state == CST_OPEN && !p_ccb->p_lcb->is_transport_ble()) {(*p_ccb->p_rcb->api.pL2CA_ConfigCfm_Cb)(p_ccb->local_cid, p_ccb->connection_initiator, &p_ccb->peer_cfg); // 配置确认的回调函数}power_telemetry::GetInstance().LogChannelConnected(p_ccb->p_rcb->psm, p_ccb->local_cid, p_ccb->remote_id,p_ccb->p_lcb->remote_bd_addr);
}
负责在L2CAP连接打开后,根据连接是由本地还是远程发起,调用相应的回调函数。
RFCOMM_ConnectCnf
/packages/modules/Bluetooth/system/stack/rfcomm/rfc_l2cap_if.cc
/********************************************************************************* Function RFCOMM_ConnectCnf** Description This is a callback function called by L2CAP when* L2CA_ConnectCnf received. Save L2CAP handle and dispatch* event to the FSM.*******************************************************************************/
void RFCOMM_ConnectCnf(uint16_t lcid, uint16_t result) {tRFC_MCB* p_mcb = rfc_find_lcid_mcb(lcid); // 查找与给定lcid相关联的RFCOMM主控制块if (!p_mcb) {log::error("RFCOMM_ConnectCnf LCID:0x{:x}", lcid);return;}// 处理挂起的LCIDif (p_mcb->pending_lcid) { // 如果MCB中存在一个挂起的lcid// 意味着RFCOMM层之前发起了一个连接请求,并且正在等待对方的响应/* if peer rejects our connect request but peer's connect request is pending*/if (result != L2CAP_CONN_OK) {return;} else {log::verbose("RFCOMM_ConnectCnf peer gave up pending LCID(0x{:x})",p_mcb->pending_lcid);/* Peer gave up its connection request, make sure cleaning up L2CAP* channel */L2CA_DisconnectReq(p_mcb->pending_lcid);p_mcb->pending_lcid = 0;}}/* Save LCID to be used in all consecutive calls to L2CAP */p_mcb->lcid = lcid;rfc_mx_sm_execute(p_mcb, RFC_MX_EVENT_CONN_CNF, &result);
}
处理连接结果、管理挂起的连接请求,并将事件传递给RFCOMM的状态机以进行进一步的处理。
rfc_mx_sm_execute(RFC_MX_EVENT_CONN_CNF)
/packages/modules/Bluetooth/system/stack/rfcomm/rfc_mx_fsm.cc
/********************************************************************************* Function rfc_mx_sm_execute** Description This function sends multiplexer events through the state* machine.** Returns void*******************************************************************************/
void rfc_mx_sm_execute(tRFC_MCB* p_mcb, tRFC_MX_EVENT event, void* p_data) {CHECK(p_mcb != nullptr) << __func__ << ": NULL mcb for event " << event;log::info("RFCOMM peer:{} event:{} state:{}",ADDRESS_TO_LOGGABLE_CSTR(p_mcb->bd_addr), event,rfcomm_mx_state_text(static_cast<tRFC_MX_STATE>(p_mcb->state)));switch (p_mcb->state) {...case RFC_MX_STATE_WAIT_CONN_CNF:rfc_mx_sm_state_wait_conn_cnf(p_mcb, event, p_data);break;...default:log::error("Received unexpected event:{} in state:{}", event,p_mcb->state);}
}
继续看rfc_mx_sm_state_wait_conn_cnf。
rfc_mx_sm_state_wait_conn_cnf
/packages/modules/Bluetooth/system/stack/rfcomm/rfc_mx_fsm.cc
/********************************************************************************* Function rfc_mx_sm_state_wait_conn_cnf** Description This function handles events when the multiplexer is* waiting for Connection Confirm from L2CAP.** Returns void*******************************************************************************/
void rfc_mx_sm_state_wait_conn_cnf(tRFC_MCB* p_mcb, tRFC_MX_EVENT event,void* p_data) {log::verbose("evt {}", event);switch (event) {...case RFC_MX_EVENT_CONN_CNF: // 接收到连接确认事件if (*((uint16_t*)p_data) != L2CAP_SUCCESS) { // 连接未成功p_mcb->state = RFC_MX_STATE_IDLE; // 通知上层应用连接结果PORT_StartCnf(p_mcb, *((uint16_t*)p_data));return;}p_mcb->state = RFC_MX_STATE_CONFIGURE; // 将多路复用器的状态设置为配置状态,准备进行下一步的配置return;...default:log::error("Received unexpected event:{} in state:{}", event,p_mcb->state);}log::verbose("RFCOMM MX ignored - evt:{} in state:{}", event, p_mcb->state);
}
RFCOMM多路复用器有限状态机中处理等待连接确认状态的关键部分,根据L2CAP层的连接结果更新状态机的状态,并通知上层应用连接的结果。
rfc_mx_sm_execute(RFC_MX_STATE_CONFIGURE)
case RFC_MX_STATE_CONFIGURE:rfc_mx_sm_state_configure(p_mcb, event, p_data);break;
rfc_mx_sm_state_configure
/********************************************************************************* Function rfc_mx_sm_state_configure** Description This function handles events when the multiplexer in the* configuration state.** Returns void*******************************************************************************/
void rfc_mx_sm_state_configure(tRFC_MCB* p_mcb, tRFC_MX_EVENT event,void* p_data) {log::verbose("event {}", event);switch (event) {case RFC_MX_EVENT_START_REQ:case RFC_MX_EVENT_CONN_CNF:log::error("Mx error state {} event {}", p_mcb->state, event);return;...default:log::error("Received unexpected event:{} in state:{}", event,p_mcb->state);}log::verbose("RFCOMM MX ignored - evt:{} in state:{}", event, p_mcb->state);
}
RFCOMM_ConfigCnf
/packages/modules/Bluetooth/system/stack/rfcomm/rfc_l2cap_if.cc
/********************************************************************************* Function RFCOMM_ConfigCnf** Description This is a callback function called by L2CAP when* L2CA_ConfigCnf received. Save L2CAP handle and dispatch* event to the FSM.*******************************************************************************/
void RFCOMM_ConfigCnf(uint16_t lcid, UNUSED_ATTR uint16_t initiator,tL2CAP_CFG_INFO* p_cfg) {RFCOMM_ConfigInd(lcid, p_cfg);tRFC_MCB* p_mcb = rfc_find_lcid_mcb(lcid);if (!p_mcb) {log::error("RFCOMM_ConfigCnf no MCB LCID:0x{:x}", lcid);return;}uintptr_t result_as_ptr = L2CAP_CFG_OK;rfc_mx_sm_execute(p_mcb, RFC_MX_EVENT_CONF_CNF, (void*)result_as_ptr);
}
L2CAP配置确认(Configuration Confirmation)的回调函数。当L2CAP层接收到配置确认(L2CA_ConfigCnf)时,这个函数会被调用。主要作用是保存L2CAP的句柄(handle),并将事件分发给有限状态机(FSM,Finite State Machine)。
RFCOMM_ConfigInd
/packages/modules/Bluetooth/system/stack/rfcomm/rfc_l2cap_if.cc
/********************************************************************************* Function RFCOMM_ConfigInd** Description This is a callback function called by L2CAP when* L2CA_ConfigInd received. Save parameters in the control* block and dispatch event to the FSM.*******************************************************************************/
void RFCOMM_ConfigInd(uint16_t lcid, tL2CAP_CFG_INFO* p_cfg) {if (p_cfg == nullptr) {log::error("Received l2cap configuration info with nullptr");return;}// 查找与给定lcid相关联的RFCOMM控制块(MCB,Management Control Block)// MCB是用于存储RFCOMM连接状态和控制信息的结构体tRFC_MCB* p_mcb = rfc_find_lcid_mcb(lcid);if (!p_mcb) {log::error("RFCOMM_ConfigInd LCID:0x{:x}", lcid);for (auto& [cid, mcb] : rfc_lcid_mcb) {if (mcb != nullptr && mcb->pending_lcid == lcid) {tL2CAP_CFG_INFO l2cap_cfg_info(*p_cfg);mcb->pending_configure_complete = true; // 有一个待处理的配置完成事件mcb->pending_cfg_info = l2cap_cfg_info;return;}}return;}rfc_mx_sm_execute(p_mcb, RFC_MX_EVENT_CONF_IND, (void*)p_cfg);
}
RFCOMM协议栈中用于处理L2CAP配置指示(Configuration Indication)的回调函数。当L2CAP层接收到一个配置指示时,这个函数会被调用,以处理该指示并采取相应的动作。
rfc_mx_sm_execute(RFC_MX_EVENT_CONF_IND)
case RFC_MX_STATE_CONFIGURE:rfc_mx_sm_state_configure(p_mcb, event, p_data);break;
rfc_mx_sm_state_configure
case RFC_MX_EVENT_CONF_IND:rfc_mx_conf_ind(p_mcb, (tL2CAP_CFG_INFO*)p_data);return;
rfc_mx_conf_ind
/packages/modules/Bluetooth/system/stack/rfcomm/rfc_mx_fsm.cc
/********************************************************************************* Function rfc_mx_conf_ind** Description This function handles L2CA_ConfigInd message from the* L2CAP. Send the L2CA_ConfigRsp message.*******************************************************************************/
static void rfc_mx_conf_ind(tRFC_MCB* p_mcb, tL2CAP_CFG_INFO* p_cfg) {/* Save peer L2CAP MTU if present *//* RFCOMM adds 3-4 bytes in the beginning and 1 bytes FCS */if (p_cfg->mtu_present) {p_mcb->peer_l2cap_mtu = p_cfg->mtu - RFCOMM_MIN_OFFSET - 1;} else {p_mcb->peer_l2cap_mtu = L2CAP_DEFAULT_MTU - RFCOMM_MIN_OFFSET - 1;}
}
处理从 L2CAP 层接收到的配置指示(L2CA_ConfigInd)消息。根据 L2CAP 配置信息更新 RFCOMM 管理控制块(MCB)中的相关字段。
rfc_mx_sm_execute(RFC_MX_EVENT_CONF_CNF)
case RFC_MX_STATE_CONFIGURE:rfc_mx_sm_state_configure(p_mcb, event, p_data);break;
rfc_mx_sm_state_configure
packages/modules/Bluetooth/system/stack/rfcomm/rfc_mx_fsm.cccase RFC_MX_EVENT_CONF_CNF:rfc_mx_conf_cnf(p_mcb, (uintptr_t)p_data);return;
蓝牙RFCOMM(Radio FrequencyCOMMunication)模块负责处理L2CAP配置确认消息。RFCOMM是一种用于串行端口仿真的蓝牙协议,允许设备通过蓝牙连接模拟RS-232串口通信。
rfc_mx_conf_cnf
/packages/modules/Bluetooth/system/stack/rfcomm/rfc_mx_fsm.cc
/********************************************************************************* Function rfc_mx_conf_cnf** Description This function handles L2CA_ConfigCnf message from the* L2CAP. If result is not success tell upper layer that* start has not been accepted. If initiator send SABME* on DLCI 0. T1 is still running.*******************************************************************************/
static void rfc_mx_conf_cnf(tRFC_MCB* p_mcb, uint16_t result) {if (p_mcb->state == RFC_MX_STATE_CONFIGURE) { // 是否处于配置状态if (p_mcb->is_initiator) {p_mcb->state = RFC_MX_STATE_SABME_WAIT_UA;rfc_send_sabme(p_mcb, RFCOMM_MX_DLCI);rfc_timer_start(p_mcb, RFC_T1_TIMEOUT);} else {p_mcb->state = RFC_MX_STATE_WAIT_SABME;rfc_timer_start(p_mcb, RFCOMM_CONN_TIMEOUT); /* - increased from T2=20 to CONN=120to allow the user more than 10 sec to type in thepin which can be e.g. 16 digits */}}
}
处理从L2CAP层接收到的配置确认(L2CA_ConfigCnf)消息。根据配置的结果,相应地通知上层应用或者发起下一步的SABME(Set Asynchronous Balanced Mode Extended)帧的发送。
如果是发起者,将MCB的状态更新为RFC_MX_STATE_SABME_WAIT_UA,表示正在等待对方设备对SABME帧的确认(UA,Unnumbered Acknowledgement)。然后,通过rfc_send_sabme函数在DLCI(Data Link Connection Identifier,数据链路连接标识符)0上发送SABME帧,并通过rfc_timer_start函数启动T1定时器,等待对方的响应。
rfc_send_sabme
packages/modules/Bluetooth/system/stack/rfcomm/rfc_ts_frames.cc
/********************************************************************************* Function rfc_send_sabme** Description This function sends SABME frame.*******************************************************************************/
void rfc_send_sabme(tRFC_MCB* p_mcb, uint8_t dlci) {uint8_t* p_data;uint8_t cr = RFCOMM_CR(p_mcb->is_initiator, true);BT_HDR* p_buf = (BT_HDR*)osi_malloc(RFCOMM_CMD_BUF_SIZE);p_buf->offset = L2CAP_MIN_OFFSET; // L2CAP层要求的最小偏移量p_data = (uint8_t*)(p_buf + 1) + L2CAP_MIN_OFFSET; // 数据指针初始化/* SABME frame, command, PF = 1, dlci */*p_data++ = RFCOMM_EA | cr | (dlci << RFCOMM_SHIFT_DLCI);*p_data++ = RFCOMM_SABME | RFCOMM_PF;*p_data++ = RFCOMM_EA | 0;*p_data =RFCOMM_SABME_FCS((uint8_t*)(p_buf + 1) + L2CAP_MIN_OFFSET, cr, dlci);p_buf->len = 4; // 表示SABME帧的长度(包括地址、控制、信息和FCS字段)rfc_check_send_cmd(p_mcb, p_buf); // 发送命令
}
发送SABME帧。SABME帧在RFCOMM协议中用于初始化连接或重新初始化一个已经存在的连接。
构建SABME帧:
-
第一个字节包含了EA(End of Address field,地址字段结束标志)、CR(Command/Response,命令/响应位)和DLCI(左移RFCOMM_SHIFT_DLCI位)。EA位设置为1表示地址字段结束,CR位根据p_mcb->is_initiator的值设置(如果是发起者则设置为1),DLCI是数据链路连接标识符。
-
第二个字节是控制字段,包含了SABME命令和PF(Poll Final,轮询最终位)位。PF位设置为1表示这是最后一个要发送的帧。
-
第三个字节是信息字段的开始,只包含一个EA位(设置为1表示信息字段结束),没有实际的信息数据。
-
第四个字节是FCS(Frame Check Sequence,帧校验序列),用于确保帧的完整性。RFCOMM_SABME_FCS函数根据前面的数据计算FCS值。
rfc_check_send_cmd
/packages/modules/Bluetooth/system/stack/rfcomm/rfc_utils.cc
/********************************************************************************* Function rfc_check_send_cmd** Description This function is called to send an RFCOMM command message* or to handle the RFCOMM command message queue.** Returns void*******************************************************************************/
void rfc_check_send_cmd(tRFC_MCB* p_mcb, BT_HDR* p_buf) {/* if passed a buffer queue it */if (p_buf != NULL) { // 表示有一个新的RFCOMM命令消息需要发送if (p_mcb->cmd_q == NULL) {log::error("empty queue: p_mcb = {} p_mcb->lcid = {} cached p_mcb = {}",fmt::ptr(p_mcb), p_mcb->lcid,fmt::ptr(rfc_find_lcid_mcb(p_mcb->lcid)));}fixed_queue_enqueue(p_mcb->cmd_q, p_buf);}/* handle queue if L2CAP not congested */// 当L2CAP层不拥塞时(p_mcb->l2cap_congested为false),尝试从命令队列中取出消息并发送while (!p_mcb->l2cap_congested) {BT_HDR* p = (BT_HDR*)fixed_queue_try_dequeue(p_mcb->cmd_q);if (p == NULL) break;L2CA_DataWrite(p_mcb->lcid, p); // 通过L2CAP层发送该消息}
}
发送RFCOMM命令消息或处理RFCOMM命令消息队列。RFCOMM层能够管理其命令消息的发送。当有新消息需要发送时,会被加入队列。然后,检查L2CAP层的拥塞状态,如果不拥塞,尝试从队列中取出消息并通过L2CAP层发送。这种机制确保了RFCOMM层能够有序地发送命令消息,同时避免了在L2CAP层拥塞时发送过多消息导致的问题。
L2CA_DataWrite流程单独分析。
rfc_timer_start
/packages/modules/Bluetooth/system/stack/rfcomm/rfc_utils.cc
/********************************************************************************* Function rfc_timer_start** Description Start RFC Timer*******************************************************************************/
void rfc_timer_start(tRFC_MCB* p_mcb, uint16_t timeout) {log::verbose("- timeout:{} seconds", timeout);uint64_t interval_ms = timeout * 1000;alarm_set_on_mloop(p_mcb->mcb_timer, interval_ms, rfcomm_mcb_timer_timeout,p_mcb);
}
启动 RFCOMM协议栈中的定时器。该定时器在指定的超时时间(20s)后触发回调函数rfcomm_mcb_timer_timeout。这种机制通常用于实现超时重传、状态机转换等需要定时控制的功能。
RFCOMM协议是在蓝牙技术中用于串口仿真的一种协议,它基于GSM 07.10标准但有所简化并增加了特定的扩展。RFCOMM协议允许在两个蓝牙设备之间建立多达60个连接,这些连接用于模拟传统的串口通信。下面直接看RFCOMM Connect(连接机制)与RFCOMM UA(Unnumbered Acknowledgement,未编号确认)Frame(帧) snoop log:
RFCOMM Connect
RFCOMM UA Frame
-
状态转换:完成配置协商后,相关的蓝牙协议栈状态机进行状态转换,宣告 RFCOMM 链路正式建立。此时,设备两端的 RFCOMM 层进入就绪状态,可以开始模拟串口通信。
-
数据传输准备:链路建立好后,通信双方就可以按照串口通信的模式准备传输数据,例如音频流、文本消息等不同类型的数据,后续的数据传输都基于这条已建立的 RFCOMM 链路。
RFCOMM_BufDataInd
/packages/modules/Bluetooth/system/stack/rfcomm/rfc_l2cap_if.cc
/********************************************************************************* Function RFCOMM_BufDataInd** Description This is a callback function called by L2CAP when* data RFCOMM frame is received. Parse the frames, check* the checksum and dispatch event to multiplexer or port* state machine depending on the frame destination.*******************************************************************************/
void RFCOMM_BufDataInd(uint16_t lcid, BT_HDR* p_buf) {tRFC_MCB* p_mcb = rfc_find_lcid_mcb(lcid); // 查找复用器控制块if (!p_mcb) {log::warn("Cannot find RFCOMM multiplexer for lcid {}", loghex(lcid));osi_free(p_buf);return;}tRFC_EVENT event = rfc_parse_data(p_mcb, &rfc_cb.rfc.rx_frame, p_buf); // 解析数据帧/* If the frame did not pass validation just ignore it */if (event == RFC_EVENT_BAD_FRAME) { // 帧未通过验证log::warn("Bad RFCOMM frame from lcid={}, bd_addr={}, p_mcb={}",loghex(lcid), ADDRESS_TO_LOGGABLE_STR(p_mcb->bd_addr),fmt::ptr(p_mcb));osi_free(p_buf);return;}if (rfc_cb.rfc.rx_frame.dlci == RFCOMM_MX_DLCI) { // 发送给复用器的log::verbose("handle multiplexer event {}, p_mcb={}", event,fmt::ptr(p_mcb));/* Take special care of the Multiplexer Control Messages */if (event == RFC_EVENT_UIH) {rfc_process_mx_message(p_mcb, p_buf);return;}/* Other multiplexer events go to state machine */rfc_mx_sm_execute(p_mcb, static_cast<tRFC_MX_EVENT>(event), nullptr);osi_free(p_buf);return;}/* The frame was received on the data channel DLCI, verify that DLC exists */tPORT* p_port = port_find_mcb_dlci_port(p_mcb, rfc_cb.rfc.rx_frame.dlci); // 数据通道帧处理if (p_port == nullptr || !p_port->rfc.p_mcb) {/* If this is a SABME on new port, check if any app is waiting for it */if (event != RFC_EVENT_SABME) {log::warn("no for none-SABME event, lcid={}, bd_addr={}, p_mcb={}",loghex(lcid), ADDRESS_TO_LOGGABLE_STR(p_mcb->bd_addr),fmt::ptr(p_mcb));if ((p_mcb->is_initiator && !rfc_cb.rfc.rx_frame.cr) ||(!p_mcb->is_initiator && rfc_cb.rfc.rx_frame.cr)) {log::error("Disconnecting RFCOMM, lcid={}, bd_addr={}, p_mcb={}",loghex(lcid), ADDRESS_TO_LOGGABLE_STR(p_mcb->bd_addr),fmt::ptr(p_mcb));rfc_send_dm(p_mcb, rfc_cb.rfc.rx_frame.dlci, rfc_cb.rfc.rx_frame.pf);}osi_free(p_buf);return;}p_port = port_find_dlci_port(rfc_cb.rfc.rx_frame.dlci);if (p_port == nullptr) {log::error("Disconnecting RFCOMM, no port for dlci {}, lcid={}, bd_addr={}, ""p_mcb={}",rfc_cb.rfc.rx_frame.dlci, loghex(lcid),ADDRESS_TO_LOGGABLE_STR(p_mcb->bd_addr), fmt::ptr(p_mcb));rfc_send_dm(p_mcb, rfc_cb.rfc.rx_frame.dlci, true);osi_free(p_buf);return;}log::verbose("port_handles[dlci={}]:{}->{}, p_mcb={}",rfc_cb.rfc.rx_frame.dlci,p_mcb->port_handles[rfc_cb.rfc.rx_frame.dlci], p_port->handle,fmt::ptr(p_mcb));p_mcb->port_handles[rfc_cb.rfc.rx_frame.dlci] = p_port->handle;p_port->rfc.p_mcb = p_mcb;}if (event == RFC_EVENT_UIH) {log::verbose("Handling UIH event, buf_len={}, credit={}", p_buf->len,rfc_cb.rfc.rx_frame.credit);if (p_buf->len > 0) {rfc_port_sm_execute(p_port, static_cast<tRFC_PORT_EVENT>(event), p_buf);} else {osi_free(p_buf);}if (rfc_cb.rfc.rx_frame.credit != 0) {rfc_inc_credit(p_port, rfc_cb.rfc.rx_frame.credit);}return;}rfc_port_sm_execute(p_port, static_cast<tRFC_PORT_EVENT>(event), nullptr);osi_free(p_buf);
}
负责处理从L2CAP层接收到的RFCOMM数据帧,解析这些帧,检查校验和,并根据帧的目的地将事件分发到复用器或端口状态机。支持RFCOMM连接的建立、维护和断开,以及数据的可靠传输。
rfc_parse_data
/packages/modules/Bluetooth/system/stack/rfcomm/rfc_ts_frames.cc
/********************************************************************************* Function rfc_parse_data** Description This function processes data packet received from L2CAP*******************************************************************************/
tRFC_EVENT rfc_parse_data(tRFC_MCB* p_mcb, MX_FRAME* p_frame, BT_HDR* p_buf) {uint8_t ead, eal, fcs;uint8_t* p_data = (uint8_t*)(p_buf + 1) + p_buf->offset;uint8_t* p_start = p_data;uint16_t len;if (p_buf->len < RFCOMM_CTRL_FRAME_LEN) {log::error("Bad Length1: {}", p_buf->len);return (RFC_EVENT_BAD_FRAME);}RFCOMM_PARSE_CTRL_FIELD(ead, p_frame->cr, p_frame->dlci, p_data);if (!ead) {log::error("Bad Address(EA must be 1)");return (RFC_EVENT_BAD_FRAME);}RFCOMM_PARSE_TYPE_FIELD(p_frame->type, p_frame->pf, p_data);// 解析控制字段,包括地址(EAD)、命令/响应位(CR)、数据链路连接标识符(DLCI)和指针(p_data)的移动// 如果EAD不为1,则记录错误并返回RFC_EVENT_BAD_FRAME。eal = *(p_data)&RFCOMM_EA;len = *(p_data)++ >> RFCOMM_SHIFT_LENGTH1;if (eal == 0 && p_buf->len > RFCOMM_CTRL_FRAME_LEN) {len += (*(p_data)++ << RFCOMM_SHIFT_LENGTH2);} else if (eal == 0) {log::error("Bad Length when EAL = 0: {}", p_buf->len);return RFC_EVENT_BAD_FRAME;}if (p_buf->len < (3 + !ead + !eal + 1)) {log::error("Bad Length: {}", p_buf->len);return RFC_EVENT_BAD_FRAME;}p_buf->len -= (3 + !ead + !eal + 1); /* Additional 1 for FCS */p_buf->offset += (3 + !ead + !eal);/* handle credit if credit based flow control */if ((p_mcb->flow == PORT_FC_CREDIT) && (p_frame->type == RFCOMM_UIH) &&(p_frame->dlci != RFCOMM_MX_DLCI) && (p_frame->pf == 1)) {if (p_buf->len < sizeof(uint8_t)) {log::error("Bad Length in flow control: {}", p_buf->len);return RFC_EVENT_BAD_FRAME;}p_frame->credit = *p_data++;p_buf->len--;p_buf->offset++;} else {p_frame->credit = 0;}if (p_buf->len != len) {log::error("Bad Length2 {} {}", p_buf->len, len);return (RFC_EVENT_BAD_FRAME);}fcs = *(p_data + len);/* All control frames that we are sending are sent with P=1, expect *//* reply with F=1 *//* According to TS 07.10 spec ivalid frames are discarded without *//* notification to the sender */switch (p_frame->type) {case RFCOMM_SABME:if (RFCOMM_FRAME_IS_RSP(p_mcb->is_initiator, p_frame->cr) ||!p_frame->pf || len || !RFCOMM_VALID_DLCI(p_frame->dlci) ||!rfc_check_fcs(RFCOMM_CTRL_FRAME_LEN, p_start, fcs)) {log::error("Bad SABME");return (RFC_EVENT_BAD_FRAME);} elsereturn (RFC_EVENT_SABME);case RFCOMM_UA:if (RFCOMM_FRAME_IS_CMD(p_mcb->is_initiator, p_frame->cr) ||!p_frame->pf || len || !RFCOMM_VALID_DLCI(p_frame->dlci) ||!rfc_check_fcs(RFCOMM_CTRL_FRAME_LEN, p_start, fcs)) {log::error("Bad UA");return (RFC_EVENT_BAD_FRAME);} elsereturn (RFC_EVENT_UA);case RFCOMM_DM:if (RFCOMM_FRAME_IS_CMD(p_mcb->is_initiator, p_frame->cr) || len ||!RFCOMM_VALID_DLCI(p_frame->dlci) ||!rfc_check_fcs(RFCOMM_CTRL_FRAME_LEN, p_start, fcs)) {log::error("Bad DM");return (RFC_EVENT_BAD_FRAME);} elsereturn (RFC_EVENT_DM);case RFCOMM_DISC:if (RFCOMM_FRAME_IS_RSP(p_mcb->is_initiator, p_frame->cr) ||!p_frame->pf || len || !RFCOMM_VALID_DLCI(p_frame->dlci) ||!rfc_check_fcs(RFCOMM_CTRL_FRAME_LEN, p_start, fcs)) {log::error("Bad DISC");return (RFC_EVENT_BAD_FRAME);} elsereturn (RFC_EVENT_DISC);case RFCOMM_UIH:if (!RFCOMM_VALID_DLCI(p_frame->dlci)) {log::error("Bad UIH - invalid DLCI");return (RFC_EVENT_BAD_FRAME);} else if (!rfc_check_fcs(2, p_start, fcs)) {log::error("Bad UIH - FCS");return (RFC_EVENT_BAD_FRAME);} else if (RFCOMM_FRAME_IS_RSP(p_mcb->is_initiator, p_frame->cr)) {/* we assume that this is ok to allow bad implementations to work */log::error("Bad UIH - response");return (RFC_EVENT_UIH);} else {return (RFC_EVENT_UIH);}}return (RFC_EVENT_BAD_FRAME);
}
解析从L2CAP层接收到的数据包。
rfc_mx_sm_execute(RFC_EVENT_UA)
case RFC_MX_STATE_SABME_WAIT_UA:rfc_mx_sm_sabme_wait_ua(p_mcb, event, p_data);break;
rfc_mx_sm_sabme_wait_ua
packages/modules/Bluetooth/system/stack/rfcomm/rfc_mx_fsm.cc
/********************************************************************************* Function rfc_mx_sm_sabme_wait_ua** Description This function handles events when the multiplexer sent* SABME and is waiting for UA reply.** Returns void*******************************************************************************/
void rfc_mx_sm_sabme_wait_ua(tRFC_MCB* p_mcb, tRFC_MX_EVENT event,UNUSED_ATTR void* p_data) {log::verbose("event {}", event);switch (event) {...case RFC_MX_EVENT_UA: // 表示收到了对方的UA帧回复rfc_timer_stop(p_mcb); // 停止之前可能启动的计时器p_mcb->state = RFC_MX_STATE_CONNECTED;p_mcb->peer_ready = true; // 表示对方已经准备好PORT_StartCnf(p_mcb, RFCOMM_SUCCESS); // 通知上层应用连接成功return;...default:log::error("Received unexpected event:{} in state:{}", event,p_mcb->state);}log::verbose("RFCOMM MX ignored - evt:{} in state:{}", event, p_mcb->state);
}
RFCOMM协议栈中负责处理多路复用器(Multiplexer)状态机的部分。处理在发送SABME帧后等待对方回复UA帧的事件。
PORT_StartCnf
/packages/modules/Bluetooth/system/stack/rfcomm/port_rfc.cc
/********************************************************************************* Function PORT_StartCnf** Description This function is called from the RFCOMM layer when* establishing of the multiplexer channel is completed.* Continue establishing of the connection for all ports that* are in the OPENING state*******************************************************************************/
void PORT_StartCnf(tRFC_MCB* p_mcb, uint16_t result) {bool no_ports_up = true;log::verbose("result {}", result);tPORT* p_port = &rfc_cb.port.port[0];for (int i = 0; i < MAX_RFC_PORTS; i++, p_port++) {if (p_port->rfc.p_mcb == p_mcb) {no_ports_up = false; // 表示至少有一个端口与当前的多路复用器关联if (result == RFCOMM_SUCCESS) {log::verbose("dlci {}", p_port->dlci);RFCOMM_ParameterNegotiationRequest(p_mcb, p_port->dlci, p_port->mtu); // 进行参数协商} else {log::warn("failed result:{}", result);/* Warning: result is also set to 4 when l2cap connectionfails due to l2cap connect cnf (no_resources) */if (result == HCI_ERR_PAGE_TIMEOUT) {p_port->error = PORT_PAGE_TIMEOUT;} else {p_port->error = PORT_START_FAILED;}rfc_release_multiplexer_channel(p_mcb);/* Send event to the application */if (p_port->p_callback && (p_port->ev_mask & PORT_EV_CONNECT_ERR)) {(p_port->p_callback)(PORT_EV_CONNECT_ERR, p_port->handle);}if (p_port->p_mgmt_callback) {p_port->p_mgmt_callback(PORT_START_FAILED, p_port->handle);log_counter_metrics(android::bluetooth::CodePathCounterKeyEnum::RFCOMM_PORT_START_CNF_FAILED,1);}port_release_port(p_port);}}}/* There can be a situation when after starting connection, user closes the *//* port, we can catch it here to close multiplexor channel */if (no_ports_up) {rfc_check_mcb_active(p_mcb);}
}
在RFCOMM层的多路复用器通道建立完成后,继续为所有处于OPENING状态的端口建立连接。
关键字
RFCOMM_CreateConnectionWithSecurity|port_open_continue|RFC_MX_STATE_IDLE|PEER_CONNECT_RSP|L2CA_ConnectReq|l2c_csm_w4_l2cap_connect_rsp|l2c_fcr_process_peer_cfg_req|PEER_CONFIG_REQ|L2CAP_CMD_CONFIG_RSP|PEER_CONFIG_RSP|l2c_csm_config|rfc_mx_sm_state_wait_conn_cnf|RFC_MX_STATE_WAIT_CONN_CNF|rfc_mx_sm_state_configure|RFC_MX_STATE_CONFIGURE
相关文章:
【Bluedroid】HFP连接流程源码分析(二)
接上一篇【Bluedroid】HFP连接流程源码分析(一)-CSDN博客分析。本篇主要围绕RFCOMM Connect 与 RFCOMM UA Frame 的处理流程来展开分析。 RFCOMM Connect RFCOMM(Radio Frequency Communication)作为蓝牙协议栈的关键部分&#…...
基于文件系统分布式锁原理
分布式锁:在一个公共的存储服务上打上一个标记,如Redis的setnx命令,是先到先得方式获得锁,ZooKeeper有点像下面的demo,比较大小的方式判决谁获得锁。 package com.ldj.mybatisflex.demo;import java.util.*; import java.util.co…...
java语法知识(二)
1. class文件可以直接拖动到idea中,显示源码。 2.idea快捷键: sout : System.out.println 输出内容.sout :---》 System.out.println(输出内容); psvm: public static void main() 格式化:ctrl altL 复制粘贴:ctrld 3.注释…...
基于Piquasso的光量子计算机的模拟与编程
一、引言 在科技飞速发展的当下,量子计算作为前沿领域,正以前所未有的态势蓬勃崛起。它凭借独特的量子力学原理,为解决诸多经典计算难以攻克的复杂问题提供了全新路径。从优化物流配送网络,以实现资源高效调配,到药物分子结构的精准模拟,加速新药研发进程;从金融风险的…...
导出文件,能够导出但是文件打不开
背景: 在项目开发中,对于列表的查询,而后会有导出功能,这里导出的是一个excell表格。实现了两种,1.导出的文件,命名是前端传输过去的;2.导出的文件,命名是根据后端返回的文件名获取的…...
【动手学电机驱动】STM32-FOC(4)STM32之UART 串口通信
STM32-FOC(1)STM32 电机控制的软件开发环境 STM32-FOC(2)STM32 导入和创建项目 STM32-FOC(3)STM32 三路互补 PWM 输出 STM32-FOC(4)STM32之UART 串口通信 STM32-FOC(6&am…...
RabbitMQ 高可用方案:原理、构建与运维全解析
文章目录 前言:1 集群方案的原理2 RabbitMQ高可用集群相关概念2.1 设计集群的目的2.2 集群配置方式2.3 节点类型 3 集群架构3.1 为什么使用集群3.2 集群的特点3.3 集群异常处理3.4 普通集群模式3.5 镜像集群模式 前言: 在实际生产中,RabbitM…...
Center Loss 和 ArcFace Loss 笔记
一、Center Loss 1. 定义 Center Loss 旨在最小化类内特征的离散程度,通过约束样本特征与其类别中心之间的距离,提高类内特征的聚合性。 2. 公式 对于样本 xi 和其类别yi,Center Loss 的公式为: xi: 当前样本的特征向量&…...
深度解读微软Speech服务:让语音识别走进现实
大家好,今天我们来探讨一个激动人心的技术话题:微软的语音识别服务如何为我们提供强大的语音识别解决方案,特别是在电话录音中识别出不同的说话人。 场景描绘 想象一下,你有一段电话录音,并需要将其中的多个说话人区分…...
第21篇 基于ARM A9处理器用汇编语言实现中断<三>
Q:怎样编写ARM A9处理器汇编语言代码配置按键端口产生中断? A:使用Intel Monitor Program创建中断程序时,Linker Section Presets下拉菜单中需选择Exceptions。主程序在.vectors代码段为ARM处理器设置异常向量表,在…...
专题 - STM32
基础 基础知识 STM所有产品线(列举型号): STM产品的3内核架构(列举ARM芯片架构): STM32的3开发方式: STM32的5开发工具和套件: 若要在电脑上直接硬件级调试STM32设备,则…...
极客说|Azure AI Agent Service 结合 AutoGen/Semantic Kernel 构建多智能体解决⽅案
作者:卢建晖 - 微软高级云技术布道师 「极客说」 是一档专注 AI 时代开发者分享的专栏,我们邀请来自微软以及技术社区专家,带来最前沿的技术干货与实践经验。在这里,您将看到深度教程、最佳实践和创新解决方案。关注「极客说」&am…...
【C++指南】模板 深度解析
💓 博客主页:倔强的石头的CSDN主页 📝Gitee主页:倔强的石头的gitee主页 ⏩ 文章专栏:《C指南》 期待您的关注 目录 1. 引言 2. 模板的基本概念 3. 函数模板 3.1 定义和语法 3.2 函数模板实例化 3.3 隐式实例化 …...
【traefik】forwadAuth中间件跨namespace请求的问题
前情提要 - fowardAuth鉴权中间件的使用: 【traefik】使用forwardAuth中间件做网关层的全局鉴权 1. 问题 我的 traefik-ingress-controller 所在 namespace: traefik 业务服务所在 namespace: apps 路由与 forwardAuth 中间件配置如下: # 路由 apiV…...
【25考研】西南交通大学软件工程复试攻略!
一、复试内容 复试对考生的既往学业情况、外语听说交流能力、专业素质和科研创新能力,以及综合素质和一贯表现等进行全面考查,主要考核内容包括思想政治素质和道德品质、外语听说能力、专业素质和能力,综合素质及能力。考核由上机考试和面试两部分组成&a…...
在 Safari 浏览器中,快速将页面恢复到 100% 缩放(也就是默认尺寸)Command (⌘) + 0 (零)
在 Safari 浏览器中,没有一个专门的快捷键可以将页面恢复到默认的缩放比例。 但是,你可以使用以下两种方法快速将页面恢复到 100% 缩放(也就是默认尺寸): 方法一:使用快捷键 (最常用) Command (⌘) 0 (零…...
linux的大内核锁与顺序锁
大内核锁 Linux大内核锁(Big Kernel Lock,BKL)是Linux内核中的一种锁机制,用于保护内核资源,以下是关于它的详细介绍: 概念与作用 大内核锁是一种全局的互斥锁,在同一时刻只允许一个进程访问…...
CVE-2025-22777 (CVSS 9.8):WordPress | GiveWP 插件的严重漏洞
漏洞描述 GiveWP 插件中发现了一个严重漏洞,该插件是 WordPress 最广泛使用的在线捐赠和筹款工具之一。该漏洞的编号为 CVE-2025-22777,CVSS 评分为 9.8,表明其严重性。 GiveWP 插件拥有超过 100,000 个活跃安装,为全球无数捐赠平…...
牛客周赛 Round 76题解
小红出题 思路:我们发现,每七天可以获得15元,那么我们可以对7取模,看能有多少7的倍数,然后剩下的就是看是否超过5,超过5就直接15,否则加上天数*3 #include<bits/stdc.h> using namespace…...
【ARM】MDK如何将变量存储到指定内存地址
1、 文档目标 通过MDK的工程配置,将指定的变量存储到指定的内存地址上。 2、 问题场景 在项目工程的开发过程中,对于flash要进行分区,需要规划出一个特定的内存区域来存储变量。 3、软硬件环境 1)、软件版本:MDK 5.…...
解决在arm架构下的欧拉操作系统mysql8.4.2源码安装
目标:在欧拉的22.03 (LTS-SP4)版本操作系统,cpu的架构为ARM,源码安装mysql-8.4.2。 1.查看操作系统 # cat /etc/os-release NAME"openEuler" VERSION"22.03 (LTS-SP4)"# uname -i aarch642.mysql下载地址 mysql的下载…...
SpringAop
SpringAop aop定义核心概念aop基础实现执行流程 aop进阶通知类型切入点表达式的抽取通知的执行顺序切入点表达式execution方式实现annotation注解方式实现示例 笔记链接 aop定义 AOP:Aspect Oriented Programming(面向切面编程、面向方面编程)…...
C++内存泄露排查
内存泄漏是指程序动态分配的内存未能及时释放,导致系统内存逐渐耗尽,最终可能造成程序崩溃或性能下降。在C中,内存泄漏通常发生在使用new或malloc等分配内存的操作时,但没有正确地使用delete或free来释放这块内存。 在日常开发过程…...
Cesium小知识:pointPrimitive collection 详解
Cesium.PointPrimitiveCollection 是 Cesium 中用于高效管理和渲染大量点(points)的一个类。它允许你创建和管理大量的 PointPrimitive 实例,这些实例可以用来表示地理空间中的点数据,如传感器位置、车辆位置、兴趣点等。与直接使用 Cesium.Entity 相比,PointPrimitiveCol…...
从 Conda 到 Pip-tools:Python 依赖管理全景探索20250113
从 Conda 到 Pip-tools:Python 依赖管理全景探索 引言 在 Python 开发中,依赖管理是一个"常见但复杂"的问题:一次简单的版本冲突可能让团队调试数小时;一次不受控的依赖升级可能让生产环境瘫痪。随着项目规模的增加和…...
浅谈云计算01 | 云计算服务的特点
在当今数字化时代,云计算作为一种强大的技术解决方案,正逐渐改变着企业和个人对信息技术的使用方式。本文将详细探讨云计算的五个主要特点,包括按需自助服务、广泛的网络接入、资源池化、快速弹性伸缩以及可计量服务。 一、按需自助服务 云…...
2025年,华为认证HCIA、HCIP、HCIE 该如何选择?
眼看都到 2025 年啦,华为认证还吃香不? 把这问题摆在每个网络工程师跟前,答案可没那么容易说清楚。 到底考不考它值当不值当,重点在于您自己的职业规划,还有对行业走向的领会。 2025 年华为认证仍然值得一考&#…...
使用Selenium进行网页自动化测试
在使用Selenium进行网页自动化测试时,获取网络请求数据(即network数据)并不直接由Selenium库提供。Selenium主要用于与网页内容进行交互(如点击、输入文本、获取页面元素等),但它本身不拦截或记录网络请求。…...
Linux 下 mtrace 的详细介绍
在 Linux 系统中,内存管理是操作系统的一项重要任务,而内存泄漏(Memory Leak)是开发过程中常见且棘手的问题之一。为了帮助开发者追踪和调试内存泄漏问题,mtrace 提供了一种有效的方式来检测和分析内存的分配与释放情况…...
【DB-GPT】开启数据库交互新篇章的技术探索与实践
一、引言:AI原生数据应用开发的挑战与机遇 在数字化转型的浪潮中,企业对于智能化应用的需求日益增长。然而,传统的数据应用开发方式面临着诸多挑战,如技术栈复杂、开发周期长、成本高昂、难以维护等。这些问题限制了智能化应用的…...
深入 Flutter 和 Compose 在 UI 渲染刷新时 Diff 实现对比
众所周知,不管是什么框架,在前端 UI 渲染时,都会有构造出一套相关的渲染树,并且在 UI 更新时,为了尽可能提高性能,一般都只会进行「差异化」更新,而不是对整个 UI Tree 进行刷新,所以…...
Android 网络层相关介绍
关注 Android 默认支持的网络管理行为,默认支持的网络服务功能。 功能术语 术语缩写全称释义DHCPv6Dynamic Host Configuration Protocol for IPv6动态主机配置协议的第六版,用于在IPv6网络中动态分配IP地址和其他网络配置参数。DNS Domain Name System域名系统。LLALink-Loc…...
ThreeJs开发环境安装与首个DEMO
安装开发环境 我这边使用的JetBrain的WebStorm,咨询过很多其他开发从业者,普遍使用vscode的比较多。但是考虑到vscode涉及到不少插件安装和IDE配置,作为傻瓜式入门,我这边采用WebStorm。 下载地址: WebStorm: The J…...
【Vim Masterclass 笔记09】S06L22:Vim 核心操作训练之 —— 文本的搜索、查找与替换操作(第一部分)
文章目录 S06L22 Search, Find, and Replace - Part One1 从光标位置起,正向定位到当前行的首个字符 b2 从光标位置起,反向查找某个字符3 重复上一次字符查找操作4 定位到目标字符的前一个字符5 单字符查找与 Vim 命令的组合6 跨行查找某字符串7 Vim 的增…...
js:根据后端返回数据的最大值进行计算然后设置这个最大值为百分之百,其他的值除这个最大值
问: 现在tabData.value 接收到了后端返回的数据, [{text:人力,percentage:‘90’},{text:物品,percentage:‘20’},{text:物理,percentage:‘50’},{text:服务,percentageÿ…...
线形回归与小批量梯度下降实例
1、准备数据集 import numpy as np import matplotlib.pyplot as pltfrom torch.utils.data import DataLoader from torch.utils.data import TensorDataset######################################################################### #################准备若干个随机的x和…...
【数学】概率论与数理统计(三)
文章目录 [toc] 随机变量的概念随机事件数量化随机变量 离散型随机变量及其概率分布随机变量的分类离散型随机变量离散型随机变量的常见分布两点分布二项分布泊松分布泊松定理证明 泊松分布 超几何分布几何分布 连续型随机变量及其概率分布连续型随机变量零概率事件几乎必然发生…...
如何在 Linux、MacOS 以及 Windows 中打开控制面板
控制面板不仅仅是一系列图标和菜单的集合;它是通往优化个人计算体验的大门。通过它,用户可以轻松调整从外观到性能的各种参数,确保他们的电脑能够完美地适应自己的需求。无论是想要提升系统安全性、管理硬件设备,还是简单地改变桌…...
《AI赋能鸿蒙Next,开启智能关卡设计新时代》
在游戏开发领域,关卡设计是至关重要的一环,它直接影响着玩家的游戏体验和沉浸感。而随着人工智能技术的飞速发展,结合鸿蒙Next系统的强大功能,为游戏的智能关卡设计带来了全新的思路和方法。 利用AI学习玩家行为模式 在鸿蒙Next…...
Safari浏览器上ico图标显示不出来,怎么解决?
Safari浏览器上ico图标显示不出来,怎么解决? 如果Safari浏览器上ico图标显示不出来了,如下图,该图标显示为灰色。 可以关闭Safari浏览器,并清除历史记录,就可以解决啦。 另外,如果多个网站这…...
Java Bean Validation 不适用Spring的情况下自定义validation注解
Java Bean Validation(也称为 JSR 380,为 Bean Validation 2.0 规范)提供了一套基本的注解,用于定义和验证 Java Bean 的属性。例如: NotNull:属性不能为空 Size:字符串、集合或数组的大小有约…...
【算法学习笔记】30:埃氏筛(Sieve of Eratosthenes)和线性筛(Linear Sieve)
测试题目:AcWing 868. 筛质数 埃氏筛(Sieve of Eratosthenes) 如果 i i i是素数,每次把 i i i的倍数都筛掉,存在重复筛选,时间复杂度 n ⋅ l o g ( l o g n ) n \cdot log(logn) n⋅log(logn)。 #includ…...
风控业务——评分模型
本文主要讲述了金融机构风控模型的重要性及其应用。首先,开头概述了风控模型的整体建模流程,包括特征工程和建模方法。接着,本文强调了贷前、贷中、贷后三个阶段中风控模型的应用,如信用评分、行为评分和催收评分。同时还提到了信…...
jupyter notebook练手项目:线性回归——学习时间与成绩的关系
线性回归——学习时间与学习成绩的关系 第1步:导入工具库 pandas——数据分析库,提供了数据结构(如DataFrame和Series)和数据操作方法,方便对数据集进行读取、清洗、转换等操作。 matplotlib——绘图库,p…...
DDD - 微服务设计与领域驱动设计实战(上)_统一建模语言及事件风暴会议
文章目录 Pre概述业务流程需求分析的困境统一语言建模事件风暴会议什么是事件风暴(Event Storming)事件风暴会议 总结 Pre DDD - 软件退化原因及案例分析 DDD - 如何运用 DDD 进行软件设计 DDD - 如何运用 DDD 进行数据库设计 DDD - 服务、实体与值对…...
《自动驾驶与机器人中的SLAM技术》ch7:基于 ESKF 的松耦合 LIO 系统
目录 基于 ESKF 的松耦合 LIO 系统 1 坐标系说明 2 松耦合 LIO 系统的运动和观测方程 3 松耦合 LIO 系统的数据准备 3.1 CloudConvert 类 3.2 MessageSync 类 4 松耦合 LIO 系统的主要流程 4.1 IMU 静止初始化 4.2 ESKF 之 运动过程——使用 IMU 预测 4.3 使用 IMU 预测位姿进…...
day07_Spark SQL
文章目录 day07_Spark SQL课程笔记一、今日课程内容二、Spark SQL函数定义(掌握)1、窗口函数2、自定义函数背景2.1 回顾函数分类标准:SQL最开始是_内置函数&自定义函数_两种 2.2 自定义函数背景 3、Spark原生自定义UDF函数3.1 自定义函数流程&#x…...
【LC】2270. 分割数组的方案数
题目描述: 给你一个下标从 0 开始长度为 n 的整数数组 nums 。 如果以下描述为真,那么 nums 在下标 i 处有一个 合法的分割 : 前 i 1 个元素的和 大于等于 剩下的 n - i - 1 个元素的和。下标 i 的右边 至少有一个 元素,也就是…...
Docker 容器通信的网络模式详解
Docker 的网络模式是容器化技术中非常重要的一部分,它决定了容器之间以及容器与外部世界如何通信。Docker 提供了多种网络模式,每种模式都有其特定的使用场景和优势。本文将深入探讨 Docker 的网络模式,包括桥接模式、主机模式、覆盖网络模式…...
Apache和PHP:构建动态网站的黄金组合
在当今的互联网世界,网站已经成为了企业、个人和机构展示自己、与用户互动的重要平台。而在这些动态网站的背后,Apache和PHP无疑是最受开发者青睐的技术组合之一。这一组合提供了高效、灵活且可扩展的解决方案,帮助您快速搭建出强大的网站&am…...