openharmony 软总线机制详解-Softbus Discovery模块(一)-CSDN博客中,我们已经对Softbus Discovery模块的静态架构和核心流程进行了宏观分析。为了动态地理解整个流程,我们设定一个具体场景:

  • 发布方 (Publisher): 一台智能电视的应用(com.smarttv.cast)发起一个发布请求,向网络宣告其具备“castPlus”投屏能力。

  • 发现方 (Discoverer): 一部手机上的投屏应用(com.phone.cast)发起一个发现请求,在网络中寻找具备“castPlus”投屏能力的设备。

电视的旅程始于一个函数调用:

int PublishService(const char *packageName, const PublishInfo *info, const IPublishCallback *cb)

这个调用封装了它的身份信息,如来自哪个应用(com.smarttv.cast),以及它所具备的能力。一个回调函数也在此处被注册,用于接收后续任务执行的结果。

手机通过调用另一个函数:

int StartDiscovery(const char *packageName, const SubscribeInfo *info, const IDiscoveryCallback *cb)

这个调用明确了它的寻找目标,即具备特定投屏能力的服务。同样,它也注册了一个回调函数,等待着发现目标那一刻的通知。这两个请求,将各自穿越一套复杂的系统架构,最终在网络中相遇。

它们的旅程需要经过一个分层式的系统结构,每一层都有其特定的功能。

在启程前,每个请求都携带了各自的信息包。电视的发布请求携带的是PublishInfo结构体。

typedef struct {
    int publishId;              // 任务编号:8888
    DiscoverMode mode;          // 工作方式:主动发布(主动广播)
    ExchangeMedium medium;      // 交通工具:CoAP
    ExchangeFreq freq;          // 工作频率:高频
    const char *capability;     // 技能标签:"castPlus"
    unsigned char *capabilityData;  // 详细技能:"4K_HDR_TV_v2.1"
    unsigned int dataLen;       // 技能描述的长度
    bool ranging;               // 支持距离测量
} PublishInfo;

手机的发现请求则携带SubscribeInfo结构体。

typedef struct {
    int subscribeId;            // 任务编号:9999
    DiscoverMode mode;          // 工作方式:主动发现(主动搜索)
    ExchangeMedium medium;      // 交通工具:CoAP
    ExchangeFreq freq;          // 搜索频率:高频
    bool isSameAccount;         // 只找同一账户的设备:true
    bool isWakeRemote;          // 唤醒休眠设备:false
    const char *capability;     // 要找的技能:"castPlus"
    unsigned char *capabilityData;  // 具体要求:"support_4K"
    unsigned int dataLen;       // 要求描述的长度
} SubscribeInfo;

其中,mode字段定义了任务的行为模式。在发布场景中,“主动发布”(DISCOVER_MODE_ACTIVE)指设备会持续向网络广播自身信息;“被动发布”(DISCOVER_MODE_PASSIVE)则指设备仅在被询问时回应。在发现场景中,“主动发现”指设备主动发出询问广播,“被动发现”则仅监听网络中的广播。在此次交互中,电视采用主动发布,手机采用主动发现。

电视的发布请求经过SDK层的检查后,在管理层被转化为内部数据结构,并遵循“先登记、后执行、失败则回滚”的流程,最终调用CoAP协议栈的Publish接口,启动网络广播。

请求从应用层发出,进入SDK层进行参数校验。通过后,经由IPC(进程间通信)机制抵达管理层。管理层负责维护所有任务的状态,并为此创建内部数据结构,如DiscInfoDiscItem

// 个人档案
typedef struct {
    ListNode node;
    int32_t id;                    // 身份证号
    DiscoverMode mode;             // 工作模式
    ExchangeMedium medium;         // 交通方式
    InnerOption option;            // 详细选项
    ListNode capNode;              // 能力节点
    DiscItem *item;                // 所属团队
    DiscoveryStatistics statistics; // 统计信息
} DiscInfo;

// 团队档案
typedef struct {
    ListNode node;
    char packageName[PKG_NAME_SIZE_MAX];  // 团队名称
    uint32_t infoNum;                     // 团队成员数量
    ListNode InfoList;                    // 成员列表
    // 联系方式...
} DiscItem;

管理层根据请求指定的传输媒介(如CoAP或BLE),将任务分派给协议层。协议层通过一套标准化的功能接口DiscoveryFuncInterface来定义行为。

typedef struct {
    int32_t (*Publish)(const PublishOption *option);           // 发布服务
    int32_t (*StartScan)(const PublishOption *option);         // 开始扫描
    int32_t (*Unpublish)(const PublishOption *option);         // 取消发布
    int32_t (*StopScan)(const PublishOption *option);          // 停止扫描
    int32_t (*StartAdvertise)(const SubscribeOption *option);  // 开始广告
    int32_t (*Subscribe)(const SubscribeOption *option);       // 订阅服务
    int32_t (*Unsubscribe)(const SubscribeOption *option);     // 取消订阅
    int32_t (*StopAdvertise)(const SubscribeOption *option);   // 停止广告
    void (*LinkStatusChanged)(LinkStatus status);              // 状态变化
    void (*UpdateLocalDeviceInfo)(InfoTypeChanged type);       // 信息更新
} DiscoveryFuncInterface;

最终,协议层的实现会调用底层适配器(如nStackX组件)来执行网络通信。

双重角色:广播与监听

广播机制的实现路径

设备的广播行为由一个定时器驱动。上层调用NSTACKX_StartDeviceDiscovery后,系统内部会启动一个周期性任务。

广播调用栈:

NSTACKX_StartDeviceDiscovery 
    ↓
DeviceDiscoverInnerConfigurable (事件处理)
    ↓
CoapServiceDiscoverInnerConfigurable (CoAP层配置)
    ↓
CoapServiceDiscoverFirstTime (立即发送第一次广播)
    ↓
CoapServiceDiscoverTimerHandle (定时器周期广播)
    ↓
CoapPostServiceDiscover (通过UDP Socket实际发送组播消息)

CoapServiceDiscoverTimerHandle`是这个机制的核心。它作为定时器回调函数,每次被触发时会调用`CoapPostServiceDiscover`发送一次UDP组播包,并设定下一次触发的时间。

// 定时器回调:周期性发送广播消息
static void CoapServiceDiscoverTimerHandle(void *argument)
{
    // 检查是否还需要继续广播
    if (g_discoverCount >= g_coapDiscoverTargetCount || !IsCoapContextReady()) {
        CoapServiceDiscoverStop();
        return;
    }

    // 发送广播消息
    if (CoapPostServiceDiscover() != NSTACKX_EOK) {
        DFINDER_LOGE(TAG, "failed to post service discover request");
        return;
    }

    // 重新设置定时器,准备下次广播
    discoverInterval = GetDiscoverInterval(g_discoverCount);
    ++g_discoverCount;
    TimerSetTimeout(g_discoverTimer, discoverInterval, NSTACKX_FALSE);
}

监听机制的实现路径

设备的监听能力在系统服务初始化阶段便已建立。该过程会创建一个UDP服务器,并利用epoll机制异步监听网络端口的传入数据。

监听初始化调用栈:

InitSoftBusServer
	↓
InitServicesAndModules
	↓
DiscServerInit
	↓
DiscMgrInit
    ↓
DiscCoapInit (Discovery模块初始化)
    ↓
DiscNstackxInit (nstackx组件初始化)
    ↓
NSTACKX_Init (nstackx框架初始化)
    ↓
InternalInit (内部初始化)
    ↓
DeviceModuleInit (设备模块初始化)
    ↓
LocalDeviceInit (本地设备初始化)
    ↓
CreateLocalIface (为每个网络接口创建实例)
    ↓
LocalIfaceInit (接口初始化)
    ↓
CoapServerInit (创建UDP服务器)
    ↓
CoapCreateUdpServer (创建UDP socket并绑定端口)
    ↓
RegisterEpollTask (将socket注册到epoll,监听读事件)
    ↓
CoAPEpollReadHandle (当有数据到达时,epoll触发此函数进行处理)


CoapServerInit`函数负责创建UDP套接字,并将其注册到`epoll`实例中,同时指定`CoAPEpollReadHandle`作为数据到达时的处理函数。

// CoAP服务器初始化
CoapCtxType *CoapServerInit(const struct in_addr *ip, void *iface)
{
    // 创建UDP服务器socket
    int32_t listenFd = CoapCreateUdpServer(COAP_SRV_DEFAULT_ADDR, COAP_SRV_DEFAULT_PORT);

    // 注册epoll事件,监听网络消息
    uint32_t events = EPOLLIN | EPOLLERR;
    g_coapCtx.task.readHandle = CoAPEpollReadHandle;  // 设置读事件处理函数
    RegisterEpollTask(&g_coapCtx.task, events);      // 注册到epoll

    return &g_coapCtx;
}

因此,电视(主动发布方)通过定时器广播自身信息,同时也通过epoll监听网络询问。手机(主动发现方)通过定时器广播询问信息,同时也通过epoll监听网络中的服务宣告。

设备发现成功的消息传递链

当一方的监听器接收到另一方的广播包时,一个从底层到顶层的回调链被触发。

第一环:nstackx组件通知

底层nstackx组件解析收到的广播包后,调用其内部注册的回调函数onDeviceFound。

void NotifyDeviceFound(const NSTACKX_DeviceInfo *deviceList, uint32_t deviceCount)
{
    if (g_parameter.onDeviceFound != NULL) {
        DFINDER_LOGI(TAG, "notify callback: device found");
        g_parameter.onDeviceFound(deviceList, deviceCount);  // 调用注册的回调
    }
}

第二环:Discovery CoAP层处理

协议层的CoAP模块接收到回调,将设备信息结构转换为Discovery模块内部的DeviceInfo格式,并继续调用上层注册的回调。

static void OnDeviceFound(const NSTACKX_DeviceInfo *deviceList, uint32_t deviceCount)
{
    // 将nstackx的设备信息转换为Discovery模块的DeviceInfo
    DeviceInfo *discDeviceInfo = (DeviceInfo *)SoftBusCalloc(sizeof(DeviceInfo));
    // ... 转换过程 ...

    // 调用Discovery模块的回调
    if (g_discCoapInnerCb != NULL && g_discCoapInnerCb->OnDeviceFound != NULL) {
        g_discCoapInnerCb->OnDeviceFound(discDeviceInfo, &addtions);
    }
}

第三环:Discovery管理器层处理

管理层的DiscDeviceInfoFound函数接收到通知,通过比对设备能力与发现任务的目标能力,找到匹配的任务。

void DiscDeviceInfoFound(const DeviceInfo *device, const InnerDeviceInfoAddtions *additions)
{
    // 遍历所有注册的发现任务
    for (uint32_t tmp = 0; tmp < CAPABILITY_MAX_BITNUM; tmp++) {
        if (IsBitmapSet((uint32_t *)device->capabilityBitmap, tmp) == false) {
            continue;
        }

        // 找到匹配的发现任务
        DiscInfo *infoNode = NULL;
        LIST_FOR_EACH_ENTRY(infoNode, &(g_capabilityList[tmp]), DiscInfo, capNode) {
            // 调用具体的设备发现回调
            InnerDeviceFound(infoNode, device, additions);
        }
    }
}

第四环:应用层回调

管理层找到匹配任务后,调用InnerDeviceFound,该函数最终触发上层应用注册的回调函数。

static void InnerDeviceFound(DiscInfo *infoNode, const DeviceInfo *device, const InnerDeviceInfoAddtions *additions)
{
    if (IsInnerModule(infoNode) == false) {
        // 调用应用层注册的回调函数
        infoNode->item->callback.serverCb.OnServerDeviceFound(infoNode->item->packageName, device, additions);
        return;
    }
}

最终,手机应用在其OnDeviceFound回调函数中接收到被发现的设备信息,从而可以进行下一步的连接和投屏操作。

// 手机应用注册的回调函数被调用
void OnDeviceFound(const DeviceInfo *device)
{
    printf("发现了一台电视!\n");
    printf("设备名称:%s\n", device->devName);
    printf("设备能力:%s\n", device->capabilityData);
    printf("设备地址:%s\n", device->addr);

    // 手机应用可以开始连接这台电视进行投屏了!
}

至此,两个分别从电视和手机出发的请求,在网络中完成了它们的相遇。

Logo

更多推荐