openharmony 软总线机制详解-Softbus Discovery模块(二)
本文详细解析了OpenHarmony软总线中SoftbusDiscovery模块的动态工作流程。通过智能电视投屏场景示例,展示了发布方(电视)和发现方(手机)的交互过程:电视通过PublishService函数发布"castPlus"投屏能力,手机通过StartDiscovery函数主动搜索该能力。文章深入分析了请求在不同系统层的处理路径,包括参数校验、数据结构转换、协议层分发
在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(进程间通信)机制抵达管理层。管理层负责维护所有任务的状态,并为此创建内部数据结构,如DiscInfo
和DiscItem
。
// 个人档案
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);
// 手机应用可以开始连接这台电视进行投屏了!
}
至此,两个分别从电视和手机出发的请求,在网络中完成了它们的相遇。
更多推荐
所有评论(0)