openharmony 软总线机制详解-Softbus Discovery模块(一)-CSDN博客
openharmony 软总线机制详解-Softbus Discovery模块(二)-CSDN博客
openharmony 软总线机制详解-Softbus Discovery模块(三)—Discovery Manager-CSDN博客
本文将继续基于openharmony 4.1源码深入探讨——Softbus Discovery模块 CoAP协议与nStackX机制。基础相关的内容请先阅读之前的三篇文章。
nStackX协议栈的初始化机制
在分析函数调用关系之前,需要了解nStackX协议栈的初始化过程。当软总线服务启动时,DiscNstackxInit函数负责协议栈的初始化:
// 协议栈初始化入口 (disc_nstackx_adapter.c:575-588)
int32_t DiscNstackxInit(void)
{
if (InitLocalInfo() != SOFTBUS_OK) {
return SOFTBUS_DISCOVER_COAP_INIT_FAIL;
}
NSTACKX_DFinderRegisterLog(NstackxLogInnerImpl);
if (NSTACKX_Init(&g_nstackxCallBack) != SOFTBUS_OK) {
DeinitLocalInfo();
return SOFTBUS_DISCOVER_COAP_INIT_FAIL;
}
SoftBusRegDiscVarDump((char *)NSTACKX_LOCAL_DEV_INFO, &NstackxLocalDevInfoDump);
return SOFTBUS_OK;
}
初始化过程包含三个关键步骤:初始化本地设备信息、注册日志处理函数、启动协议栈核心。
协议栈核心初始化流程
NSTACKX_Init函数调用NstackxInitEx执行核心初始化:
// 核心初始化逻辑 (nstackx_common.c)
static int32_t NstackxInitEx(const NSTACKX_Parameter *parameter, bool isNotifyPerDevice)
{
if (g_nstackInitState != NSTACKX_INIT_STATE_START) {
return NSTACKX_EOK;
}
// 创建epoll事件监听器
g_epollfd = CreateEpollDesc();
if (!IsEpollDescValid(g_epollfd)) {
g_nstackInitState = NSTACKX_INIT_STATE_START;
return NSTACKX_EFAILED;
}
// 执行内部模块初始化
int32_t ret = NstackxInitInner(parameter != NULL ? parameter->maxDeviceNum : NSTACKX_MAX_DEVICE_NUM);
if (ret != NSTACKX_EOK) {
goto L_ERR_INIT;
}
g_nstackInitState = NSTACKX_INIT_STATE_DONE;
return NSTACKX_EOK;
}
模块依次初始化顺序
NstackxInitInner函数按照依赖关系依次初始化各个核心模块:
// 模块初始化顺序 (nstackx_common.c)
static int32_t InternalInit(EpollDesc epollfd, uint32_t maxDeviceNum)
{
// 1. 事件处理模块初始化
int32_t ret = EventModuleInit(&g_eventNodeChain, g_epollfd);
if (ret != NSTACKX_EOK) {
return ret;
}
// 2. 设备管理模块初始化
ret = DeviceModuleInit(epollfd, maxDeviceNum);
if (ret != NSTACKX_EOK) {
return ret;
}
// 3. CoAP发现模块初始化
ret = CoapDiscoverInit(epollfd);
if (ret != NSTACKX_EOK) {
return ret;
}
return ret;
}
这个初始化序列反映了模块间的依赖关系:事件处理模块为其他模块提供异步处理能力,设备管理模块负责设备信息存储,CoAP发现模块实现协议处理逻辑。
主循环线程启动
初始化完成后,启动主循环线程处理异步事件:
// 主循环线程启动 (nstackx_common.c)
static int32_t NstackxInitInner(uint32_t maxDeviceNum)
{
int32_t ret = InternalInit(g_epollfd, maxDeviceNum);
if (ret != NSTACKX_EOK) {
return ret;
}
g_terminateFlag = NSTACKX_FALSE;
g_validTidFlag = NSTACKX_FALSE;
ret = PthreadCreate(&g_tid, NULL, NstackMainLoop, NULL);
if (ret != 0) {
return ret;
}
return NSTACKX_EOK;
}
主循环线程NstackMainLoop在独立线程中运行,负责处理所有异步事件和网络I/O操作。
这部分详细的调用栈在openharmony 软总线机制详解-Softbus Discovery模块(二)-CSDN博客文章中有进行深入讲解
从应用PublishService到DiscCoapRegisterCapability的调用链路
DiscCoapRegisterCapability
DiscCoapRegisterCapability函数是CoAP协议层与nStackX协议栈之间的关键桥梁,位于core/discovery/coap/nstackx_coap/src/disc_nstackx_adapter.c中:
// 能力注册适配器 (disc_nstackx_adapter.c:200-210)
int32_t DiscCoapRegisterCapability(uint32_t capabilityBitmapNum, uint32_t capabilityBitmap[])
{
DISC_CHECK_AND_RETURN_RET_LOGE(capabilityBitmapNum != 0, SOFTBUS_INVALID_PARAM,
DISC_COAP, "capabilityBitmapNum=0");
if (NSTACKX_RegisterCapability(capabilityBitmapNum, capabilityBitmap) != SOFTBUS_OK) {
DISC_LOGE(DISC_COAP, "NSTACKX Register Capability failed");
return SOFTBUS_DISCOVER_COAP_REGISTER_CAP_FAIL;
}
return SOFTBUS_OK;
}
这个函数的作用是将CoAP层合并后的能力位图传递给nStackX协议栈,调用NSTACKX_RegisterCapability更新本地设备的能力信息。
NSTACKX_RegisterDevice
NSTACKX_RegisterDevice的调用发生在设备信息需要完整更新的场景中。当网络状态发生变化时,DiscCoapUpdateLocalIp函数会被调用:
// 设备信息完整注册 (disc_nstackx_adapter.c:438-447)
void DiscCoapUpdateLocalIp(LinkStatus status)
{
int64_t accountId = 0;
int32_t ret = LnnGetLocalNum64Info(NUM_KEY_ACCOUNT_LONG, &accountId);
DISC_CHECK_AND_RETURN_LOGE(ret == SOFTBUS_OK, DISC_COAP, "get local account failed");
ret = NSTACKX_RegisterDeviceAn(g_localDeviceInfo, (uint64_t)accountId);
DISC_CHECK_AND_RETURN_LOGE(ret == SOFTBUS_OK, DISC_COAP, "register local device info to dfinder failed");
DiscCoapUpdateDevName();
}
这里调用的NSTACKX_RegisterDeviceAn是NSTACKX_RegisterDevice的扩展版本,用于注册完整的本地设备信息,包括设备ID、设备名称、IP地址、能力位图等。
NSTACKX_RegisterDevice的内部实现机制
在components/nstackx/nstackx_ctrl/core/nstackx_common.c中,NSTACKX_RegisterDevice的实现采用异步处理模式:
// nStackX设备注册实现 (nstackx_common.c:916-922)
int32_t NSTACKX_RegisterDevice(const NSTACKX_LocalDeviceInfo *localDeviceInfo)
{
Coverity_Tainted_Set((void *)localDeviceInfo);
DFINDER_LOGI(TAG, "begin to NSTACKX_RegisterDevice!");
return RegisterDeviceWithDeviceHash(localDeviceInfo, NSTACKX_FALSE, 0);
}
RegisterDeviceWithDeviceHash函数通过PostEvent将注册任务投递到事件队列:
// 异步事件投递 (nstackx_common.c:1001-1005)
if (PostEvent(&g_eventNodeChain, g_epollfd, RegisterDeviceV2, ®Info) != NSTACKX_EOK) {
DFINDER_LOGE(TAG, "Failed to configure local device info!");
SemDestroy(®Info.wait);
return NSTACKX_EBUSY;
}
PostEvent将RegisterDeviceV2作为处理函数投递到事件队列中,由nStackX的主循环线程异步处理,避免阻塞调用线程。
nStackX事件处理系统的核心机制
PostEvent函数通过管道机制实现事件的异步投递,主循环线程通过epoll监听管道的可读事件:
// 主循环调度器 (nstackx_common.c)
static void *NstackMainLoop(void *arg)
{
while (g_terminateFlag == NSTACKX_FALSE) {
uint32_t timeout = RegisterCoAPEpollTask(g_epollfd);
int32_t ret = EpollLoop(g_epollfd, timeout);
if (ret == NSTACKX_EFAILED) {
DFINDER_LOGE(TAG, "epoll loop failed");
break;
}
DeRegisterCoAPEpollTask();
}
return NULL;
}
NstackMainLoop是协议栈的核心调度器,在独立线程中运行,负责处理所有异步事件和网络I/O操作。当PostEvent投递的事件到达时,主循环调用相应的处理函数完成设备注册、消息发送等操作。
两个关键函数的调用时机对比
通过源码分析,我们可以明确DiscCoapRegisterCapability和NSTACKX_RegisterDevice的不同调用时机:
函数 |
调用时机 |
调用路径 |
作用 |
---|---|---|---|
DiscCoapRegisterCapability |
发布服务时能力位图有更新 |
Publish() → DiscCoapRegisterCapability() → NSTACKX_RegisterCapability() |
更新设备能力位图 |
NSTACKX_RegisterDeviceAn | 网络状态变化时 |
DiscCoapUpdateLocalIp() → NSTACKX_RegisterDeviceAn() → RegisterDeviceWithDeviceHash() |
注册完整设备信息 |
DiscCoapStartDiscovery的网络发现启动机制
当Publish函数完成能力注册后,如果是主动发布模式,会调用DiscCoapStartDiscovery启动实际的网络发现过程。这个函数位于core/discovery/coap/nstackx_coap/src/disc_nstackx_adapter.c中:
// 启动网络发现 (core/discovery/coap/nstackx_coap/src/disc_nstackx_adapter.c)
int32_t DiscCoapStartDiscovery(DiscCoapOption *option)
{
DISC_CHECK_AND_RETURN_RET_LOGE(option != NULL, SOFTBUS_INVALID_PARAM, DISC_COAP, "option is null");
// 构建nStackX发现参数
NSTACKX_DiscoverySettings discSet = {0};
if (BuildDiscoverySettings(option, &discSet) != SOFTBUS_OK) {
DISC_LOGE(DISC_COAP, "build discovery settings failed");
return SOFTBUS_DISCOVER_COAP_START_DISCOVER_FAIL;
}
// 调用nStackX启动设备发现
if (NSTACKX_StartDeviceDiscovery(&discSet) != SOFTBUS_OK) {
DISC_LOGE(DISC_COAP, "start device discovery failed");
FreeDiscSet(&discSet);
return (option->mode == ACTIVE_PUBLISH) ? SOFTBUS_DISCOVER_COAP_START_PUBLISH_FAIL :
SOFTBUS_DISCOVER_COAP_START_DISCOVER_FAIL;
}
FreeDiscSet(&discSet);
return SOFTBUS_OK;
}
DiscCoapStartDiscovery函数首先调用BuildDiscoverySettings构建nStackX需要的发现参数,然后调用NSTACKX_StartDeviceDiscovery启动底层的设备发现机制。这个调用会触发nStackX协议栈开始发送CoAP广播消息到网络中。
NSTACKX_StartDeviceDiscovery的内部处理流程
NSTACKX_StartDeviceDiscovery函数会将发现请求转换为内部事件,通过PostEvent投递到事件队列中。在nStackX的主循环中,这个事件会被处理并最终触发CoAP消息的构建和发送。
CoAP消息的构建过程涉及JSON格式的设备信息序列化。在components/nstackx/nstackx_ctrl/core/coap_discover/coap_discover.c中,PrepareServiceDiscover函数负责构建设备发现消息:
// CoAP消息构建 (components/nstackx/nstackx_ctrl/core/coap_discover/coap_discover.c)
static char *PrepareServiceDiscover(const char *localIpStr, uint8_t isBroadcast, uint8_t businessType)
{
cJSON *data = cJSON_CreateObject();
if (data == NULL) {
return NULL;
}
// 添加设备基本信息
if (!AddDeviceJsonData(data, localIpStr, businessType)) {
cJSON_Delete(data);
return NULL;
}
// 添加能力位图信息
if (!AddCapabilityJsonData(data)) {
cJSON_Delete(data);
return NULL;
}
// 如果是广播请求,添加CoAP URI
if (isBroadcast) {
if (!AddCoapUriJsonData(data, localIpStr)) {
cJSON_Delete(data);
return NULL;
}
}
char *jsonString = cJSON_Print(data);
cJSON_Delete(data);
return jsonString;
}
PrepareServiceDiscover函数构建包含设备ID、设备名称、IP地址、能力位图等信息的JSON消息。这个JSON消息会被封装到CoAP PDU中,通过UDP协议发送到网络的组播地址。
CoAP消息的网络传输机制
构建好的JSON消息通过CoapSendRequest函数发送到网络中。这个函数实现了完整的CoAP协议处理流程:
// CoAP消息发送 (components/nstackx/nstackx_ctrl/core/coap_discover/coap_discover.c)
static int32_t CoapSendRequest(CoapCtxType *ctx, uint8_t coapType, const char *url, char *data, size_t dataLen)
{
coap_session_t *session = NULL;
coap_address_t dst = {0};
coap_uri_t coapUri;
coap_pdu_t *pdu = NULL;
// 解析目标URI
if (CoapUriParse(url, &coapUri) != NSTACKX_EOK) {
return NSTACKX_EFAILED;
}
// 解析目标地址
int32_t res = CoapResolveAddress(&coapUri.host, &dst.addr.sa);
if (res < 0) {
return NSTACKX_EFAILED;
}
// 设置目标端口
dst.size = (uint32_t)res;
dst.addr.sin.sin_port = htons(COAP_DEFAULT_PORT); // 5683端口
// 获取CoAP会话
session = CoapGetSession(ctx->ctx, GetLocalIfaceIpStr(ctx->iface), COAP_SRV_DEFAULT_PORT, &dst);
if (session == NULL) {
return NSTACKX_EFAILED;
}
// 构建PDU并发送
pdu = CoapPackToPdu(data, dataLen, &coapUri, session);
if (pdu == NULL) {
return NSTACKX_EFAILED;
}
int32_t tid = coap_send(session, pdu);
if (tid == COAP_INVALID_TID) {
return NSTACKX_EFAILED;
}
return NSTACKX_EOK;
}
CoapSendRequest函数实现了从URI解析、地址解析、会话建立到PDU构建和发送的完整流程。消息最终通过UDP协议发送到组播地址224.0.0.x的5683端口,网络中的其他设备可以接收到这些发现消息。
CoAP消息接收与处理机制
当网络中的CoAP消息到达时,libcoap库会调用注册的回调函数进行处理。HndPostServiceDiscover函数是处理设备发现消息的核心回调:
// CoAP消息接收处理 (components/nstackx/nstackx_ctrl/core/coap_discover/coap_discover.c)
static void HndPostServiceDiscover(coap_resource_t *resource, coap_session_t *session,
const coap_pdu_t *request, const coap_string_t *query, coap_pdu_t *response)
{
if (request == NULL || response == NULL) {
return;
}
// 处理设备发现消息
if (HndPostServiceDiscoverInner(session, request, response) != NSTACKX_EOK) {
IncStatistics(STATS_HANDLE_DEVICE_DISCOVER_MSG_FAILED);
}
}
HndPostServiceDiscoverInner函数实现具体的消息处理逻辑,包括频率控制、数据提取、JSON解析等步骤:
// 消息处理内部逻辑
static int32_t HndPostServiceDiscoverInner(coap_session_t *session, const coap_pdu_t *request, coap_pdu_t *response)
{
size_t size;
const uint8_t *buf = NULL;
// 频率控制
IncreaseRecvDiscoverNum();
if (g_recvDiscoverMsgNum > COAP_DISVOCER_MAX_RATE) {
return NSTACKX_EFAILED;
}
// 提取CoAP消息载荷
if (coap_get_data(request, &size, &buf) == 0 || size == 0) {
return NSTACKX_EFAILED;
}
// 解析JSON格式的设备信息
DeviceInfo deviceInfo = {0};
char *remoteUrl = NULL;
if (GetServiceDiscoverInfo(buf, size, &deviceInfo, &remoteUrl) != NSTACKX_EOK) {
return NSTACKX_EFAILED;
}
// 处理发现的设备信息
ProcessDeviceInfo(&deviceInfo, remoteUrl);
return NSTACKX_EOK;
}
消息处理过程包括频率控制以防止消息洪泛、载荷数据提取、JSON解析以及设备信息的后续处理。解析出的设备信息会被添加到设备列表中,并通过回调函数通知上层应用。
设备信息的结构化存储与管理
nStackX协议栈通过DeviceInfo结构体实现设备信息的标准化存储。这个结构体定义在components/nstackx/nstackx_ctrl/interface/nstackx_device.h中:
// 设备信息结构体定义
typedef struct {
char deviceId[NSTACKX_MAX_DEVICE_ID_LEN]; // 设备唯一标识
char deviceName[NSTACKX_MAX_DEVICE_NAME_LEN]; // 设备名称
uint8_t deviceType; // 设备类型
char version[NSTACKX_MAX_HICOM_VERSION]; // 版本信息
uint8_t mode; // 工作模式
char networkName[NSTACKX_MAX_INTERFACE_NAME_LEN]; // 网络接口名称
char networkIpAddr[NSTACKX_MAX_IP_STRING_LEN]; // IP地址
uint32_t capabilityBitmapNum; // 能力位图数量
uint32_t capabilityBitmap[NSTACKX_MAX_CAPABILITY_NUM]; // 能力位图数组
char reservedInfo[NSTACKX_MAX_RESERVED_INFO_LEN]; // 保留信息
BusinessData businessData; // 业务数据
} DeviceInfo;
这个结构体包含了设备发现和连接所需的信息,包括设备标识、网络信息、能力描述等。标准化的数据结构用于不同模块间的数据传递。
设备列表的管理通过预分配的设备池实现。在components/nstackx/nstackx_ctrl/core/nstackx_device.c中,RemoteDeviceListInit函数初始化设备池:
// 设备池初始化 (components/nstackx/nstackx_ctrl/core/nstackx_device.c)
static DeviceInfo *g_deviceList = NULL;
static uint32_t g_maxDeviceNum = NSTACKX_DEFAULT_DEVICE_NUM;
int32_t RemoteDeviceListInit(void)
{
if (g_deviceList != NULL) {
return NSTACKX_EFAILED;
}
g_deviceList = calloc(g_maxDeviceNum, sizeof(DeviceInfo));
if (g_deviceList == NULL) {
return NSTACKX_ENOMEM;
}
for (uint32_t i = 0; i < g_maxDeviceNum; i++) {
ResetDeviceInfo(&g_deviceList[i]);
}
return NSTACKX_EOK;
}
这种预分配策略避免了运行时的内存分配开销。通过设备池的复用机制,减少了内存碎片的产生。
能力匹配与过滤机制的实现
当接收到设备发现消息时,nStackX会根据设置的过滤条件进行能力匹配。MatchDeviceFilter函数实现基于能力位图的设备过滤:
// 能力过滤机制 (components/nstackx/nstackx_ctrl/core/nstackx_device.c)
bool MatchDeviceFilter(const DeviceInfo *deviceInfo)
{
// 如果没有设置过滤器,接收所有设备
if (g_filterCapabilityBitmapNum == 0) {
return true;
}
// 逐位检查设备能力与过滤器的匹配
for (uint32_t i = 0; ((i < g_filterCapabilityBitmapNum) && (i < deviceInfo->capabilityBitmapNum)); i++) {
uint32_t ret = (g_filterCapabilityBitmap[i] & (deviceInfo->capabilityBitmap[i]));
if (ret != 0) {
return true; // 有任何一位匹配就通过过滤器
}
}
return false; // 没有匹配的能力,过滤掉
}
函数通过位运算实现能力匹配,只有当设备的能力位图与过滤器有交集时,设备才会被上报给上层应用。这种机制用于减少无效的设备发现事件。
过滤器的设置通过DiscCoapSetFilterCapability函数实现,这个函数在订阅场景中被调用:
// 设置能力过滤器 (core/discovery/coap/nstackx_coap/src/disc_nstackx_adapter.c)
int32_t DiscCoapSetFilterCapability(uint32_t capabilityBitmapNum, uint32_t capabilityBitmap[])
{
DISC_CHECK_AND_RETURN_RET_LOGE(capabilityBitmapNum != 0, SOFTBUS_INVALID_PARAM,
DISC_COAP, "capabilityBitmapNum=0");
if (NSTACKX_SetFilterCapability(capabilityBitmapNum, capabilityBitmap) != SOFTBUS_OK) {
DISC_LOGE(DISC_COAP, "NSTACKX Set Filter Capability failed");
return SOFTBUS_DISCOVER_COAP_SET_FILTER_CAP_FAIL;
}
return SOFTBUS_OK;
}
这个函数将订阅者感兴趣的能力位图设置为过滤条件,只有具备相应能力的设备才会被发现和上报。
设备发现回调与上层通知机制
当发现符合条件的设备时,nStackX通过回调机制通知上层应用。在core/discovery/coap/nstackx_coap/src/disc_nstackx_adapter.c中,OnDeviceFound函数处理设备发现事件:
// 设备发现回调 (core/discovery/coap/nstackx_coap/src/disc_nstackx_adapter.c:140-180)
static void OnDeviceFound(const NSTACKX_DeviceInfo *deviceList, uint32_t deviceCount)
{
if (deviceList == NULL || deviceCount == 0) {
return;
}
for (uint32_t i = 0; i < deviceCount; i++) {
const NSTACKX_DeviceInfo *device = &deviceList[i];
// 转换设备信息格式
DiscDeviceInfo *discDeviceInfo = (DiscDeviceInfo *)SoftBusCalloc(sizeof(DiscDeviceInfo));
if (discDeviceInfo == NULL) {
continue;
}
// 填充设备信息
if (strcpy_s(discDeviceInfo->devId, sizeof(discDeviceInfo->devId), device->deviceId) != EOK ||
strcpy_s(discDeviceInfo->devName, sizeof(discDeviceInfo->devName), device->deviceName) != EOK) {
SoftBusFree(discDeviceInfo);
continue;
}
discDeviceInfo->devType = (DeviceType)device->deviceType;
discDeviceInfo->capabilityBitmapNum = device->capabilityBitmapNum;
// 复制能力位图
if (memcpy_s(discDeviceInfo->capabilityBitmap, sizeof(discDeviceInfo->capabilityBitmap),
device->capabilityBitmap, device->capabilityBitmapNum * sizeof(uint32_t)) != EOK) {
SoftBusFree(discDeviceInfo);
continue;
}
// 通知上层应用
if (g_discCoapInnerCb != NULL && g_discCoapInnerCb->OnDeviceFound != NULL) {
g_discCoapInnerCb->OnDeviceFound(discDeviceInfo);
}
SoftBusFree(discDeviceInfo);
}
}
OnDeviceFound函数将nStackX的设备信息格式转换为SoftBus的标准格式,然后通过注册的回调函数通知上层应用。这个回调链路最终会到达应用程序注册的IDiscoveryCallback,完成整个设备发现流程。
网络接口管理机制
协议栈初始化完成后,需要管理网络接口以支持CoAP消息的发送和接收。网络接口管理包括接口枚举、状态检查和套接字绑定:
// 网络接口枚举与绑定 (sys_interface.c)
int32_t GetInterfaceList(struct ifconf *ifc, struct ifreq *buf, uint32_t size)
{
int32_t fd = socket(AF_INET, SOCK_DGRAM, 0);
// 通过ioctl获取网络接口配置信息
ifc->ifc_len = (int32_t)size;
ifc->ifc_buf = (char*)buf;
if (ioctl(fd, SIOCGIFCONF, (char*)ifc) < 0) {
CloseSocketInner(fd);
return NSTACKX_EFAILED;
}
return fd;
}
网络接口管理确保CoAP消息能够通过正确的网络接口发送到组播地址,支持多网卡环境下的设备发现。