openharmony 软总线机制详解-Softbus Discovery模块(四)—CoAP协议与nStackX机制

发布于:2025-08-30 ⋅ 阅读:(17) ⋅ 点赞:(0)

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, &regInfo) != NSTACKX_EOK) {
    DFINDER_LOGE(TAG, "Failed to configure local device info!");
    SemDestroy(&regInfo.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消息能够通过正确的网络接口发送到组播地址,支持多网卡环境下的设备发现。

完整的流程