继上一章节分析完PublishService接口后,本章节我们开始分析StartDiscovery接口。
StartDiscovery接口根据g_discCoapFuncInterface的接口可以知道,当为主动发现时,调用CoapStartAdvertise函数,当为被动发现时,调用CoapSubscribe函数完成对应的处理。下面我们分别介绍这两种实现:
CoapStartAdvertiseCoapStartAdvertise的源码如下所示:
static int32_t CoapStartAdvertise(const SubscribeOption *option)
{
if (option == NULL || g_subscribeMgr == NULL) {
return SOFTBUS_INVALID_PARAM;
}
if (pthread_mutex_lock(&(g_subscribeMgr->lock)) != 0) {
LOG_ERR("pthread mutex lock failed.");
return SOFTBUS_LOCK_ERR;
}
if (RegisterAllCapBitmap(CAPABILITY_NUM, option->capabilityBitmap, g_subscribeMgr, MAX_CAP_NUM) != SOFTBUS_OK) {
(void)pthread_mutex_unlock(&(g_subscribeMgr->lock));
LOG_ERR("merge discovery capability failed.");
return SOFTBUS_DISCOVER_COAP_MERGE_CAP_FAIL;
}
if (g_subscribeMgr->isUpdate) {
if (DiscCoapSetFilterCapability(CAPABILITY_NUM, g_subscribeMgr->allCap) != SOFTBUS_OK) {
(void)pthread_mutex_unlock(&(g_subscribeMgr->lock));
LOG_ERR("set all filter capability to dfinder failed.");
return SOFTBUS_DISCOVER_COAP_SET_FILTER_CAP_FAIL;
}
}
if (DiscCoapStopDiscovery() != SOFTBUS_OK) {
(void)pthread_mutex_unlock(&(g_subscribeMgr->lock));
LOG_ERR("coap stop discovery failed.");
return SOFTBUS_DISCOVER_COAP_STOP_DISCOVER_FAIL;
}
if (DiscCoapStartDiscovery(ACTIVE_DISCOVERY) != SOFTBUS_OK) {
(void)pthread_mutex_unlock(&(g_subscribeMgr->lock));
LOG_ERR("coap start advertise failed.");
return SOFTBUS_DISCOVER_COAP_START_DISCOVER_FAIL;
}
(void)pthread_mutex_unlock(&(g_subscribeMgr->lock));
LOG_INFO("coap start active discovery.");
return SOFTBUS_OK;
}
该函数主要做了四件事:
- 调用RegisterAllCapBitmap将能力注册到g_subscribeMgr中。
- 调用DiscCoapSetFilterCapability将能力保存到本地的g_filterCapabilityBitmap中。
- 调用DiscCoapStopDiscovery停止上一轮的发现处理。
- 调用DiscCoapStartDiscovery完成发现处理。
RegisterAllCapBitmap函数和DiscCoapSetFilterCapability函数比较简单,因此不展开介绍。我们主要对DiscCoapStopDiscovery函数和DiscCoapStartDiscovery函数展开。
DiscCoapStopDiscoveryDiscCoapStopDiscovery调用NSTACKX_StopDeviceFind函数,该函数使用PostEvent提交一个事件,其handle为DeviceDiscoverStopInner函数,该函数最终调用CoapServiceDiscoverStopInner函数完成处理,其源码如下:
void CoapServiceDiscoverStopInner(void)
{
TimerSetTimeout(g_discoverTimer, 0, NSTACKX_FALSE);
CoapServiceDiscoverStop();
LOGI(TAG, "device discover stopped");
}
该函数首先调用TimerSetTimeout关闭掉上一轮发现设置的定时器,然后调用CoapServiceDiscoverStop函数,CoapServiceDiscoverStop的源码如下:
static void CoapServiceDiscoverStop(void)
{
g_discoverCount = 0;
g_forceUpdate = NSTACKX_FALSE;
SetModeInfo(DISCOVER_MODE);
ClearDevices(GetDeviceDBBackup());
LOGW(TAG, "clear device list backup");
g_userRequest = NSTACKX_FALSE;
}
该函数也比较简单,即清除掉各种标志和设备列表备份中的记录。
DiscCoapStartDiscovery停止上一轮的发现处理之后,便调用DiscCoapStartDiscovery函数进行发现处理,该函数我们在上一章节介绍CoapPublish时介绍过,该函数在StartDiscovery调用的情况下,根据传入的参数,调用NSTACKX_StartDeviceFind函数。NSTACKX_StartDeviceFind函数使用PostEvent提交一个事件,其处理的handle为DeviceDiscoverInner函数,其源码如下:
static void DeviceDiscoverInner(void *argument)
{
(void)argument;
CoapServiceDiscoverInner(INNER_DISCOVERY);
if (!IsWifiApConnected()) {
NotifyDeviceFound(NULL, 0);
}
}
该函数首先调用CoapServiceDiscoverInner进行发现的处理,然后判断如果当前设备的Wifi和Ble都没有打开的话,则通知用户发现的设备为空。
CoapServiceDiscoverInner的源码如下:
void CoapServiceDiscoverInner(uint8_t userRequest)
{
uint32_t discoverInterval;
if (!IsWifiApConnected() || g_context == NULL) {
return;
}
if (userRequest) {
g_userRequest = NSTACKX_TRUE;
g_forceUpdate = NSTACKX_TRUE;
}
if (g_coapDiscoverTargetCount > 0 && g_discoverCount >= g_coapDiscoverTargetCount) {
g_discoverCount = 0;
SetModeInfo(DISCOVER_MODE);
ClearDevices(GetDeviceDBBackup());
LOGW(TAG, "clear device list backup");
TimerSetTimeout(g_discoverTimer, 0, NSTACKX_FALSE);
}
if (g_discoverCount) {
return;
} else {
if (BackupDeviceDB() != NSTACKX_EOK) {
LOGE(TAG, "backup device list fail");
return;
}
ClearDevices(GetDeviceDB());
LOGW(TAG, "clear device list");
g_coapDiscoverTargetCount = g_coapMaxDiscoverCount;
}
SetModeInfo(DISCOVER_MODE);
if (CoapPostServiceDiscover() != NSTACKX_EOK) {
LOGE(TAG, "failed to post service discover request");
return;
}
discoverInterval = GetDiscoverInterval(g_discoverCount);
if (TimerSetTimeout(g_discoverTimer, discoverInterval, NSTACKX_FALSE) != NSTACKX_EOK) {
LOGE(TAG, "failed to set timer for service discover");
return;
}
++g_discoverCount;
LOGI(TAG, "the first time for device discover.");
return;
}
因为g_discoverCount在CoapServiceDiscoverStop函数中置0,因此CoapServiceDiscoverInner首先调用BackupDeviceDB,将g_deviceList中的设备内容备份到g_deviceListBackup中;然后清除掉当前g_deviceList中发现的设备;最后调用CoapPostServiceDiscover完成发现处理,并设置定时器超时门限。
CoapPostServiceDiscover的源码我们在上一章节CoapPublish中介绍过,即组一条广播报文将消息发送出去,因此本章不展开。
上一章节的CoapPublish中,调用CoapServiceDiscoverInnerAn函数时,也会在最后设置一个定时器和对应的超时门限,那么这个定时器的作用是什么呢?
我们可以看一下CoapDiscoverInit中,创建g_discoverTimer定时器的处理。CoapDiscoverInit调用TimerStart函数为g_discoverTimer创建了一个定时器,其对应的超时处理函数为CoapServiceDiscoverTimerHandle,源码如下:
static void CoapServiceDiscoverTimerHandle(void *argument)
{
uint32_t discoverInterval;
(void)argument;
if (g_discoverCount >= g_coapDiscoverTargetCount || !IsWifiApConnected()) {
CoapServiceDiscoverStop();
return;
}
if (CoapPostServiceDiscover() != NSTACKX_EOK) {
LOGE(TAG, "failed to post service discover request");
goto L_ERR_SERVICE_DISCOVER;
}
LOGI(TAG, "the %d times for device discover.", g_discoverCount + 1);
discoverInterval = GetDiscoverInterval(g_discoverCount);
++g_discoverCount;
if (TimerSetTimeout(g_discoverTimer, discoverInterval, NSTACKX_FALSE) != NSTACKX_EOK) {
LOGE(TAG, "failed to set timer for service discover");
goto L_ERR_SERVICE_DISCOVER;
}
return;
L_ERR_SERVICE_DISCOVER:
LOGE(TAG, "abort service discover, have tried %u request", g_discoverCount);
g_discoverCount = 0;
return;
}
该函数处理也很简单,即判断g_discoverCount是否超过了g_coapDiscoverTargetCount(该值被设为默认值12),如果没超过则继续调用CoapPostServiceDiscover对外广播,若已超过则直接返回。
因此我们知道,发现的处理会对外广播最多12次COAP报文,并且会根据当前的次数调用GetDiscoverInterval调整时间,前两次间隔100ms,第3次和第4次间隔300ms,后面每次间隔500ms。
CoapSubscribeCoapSubscribe的源码如下所示:
static int32_t CoapSubscribe(const SubscribeOption *option)
{
if (option == NULL || g_subscribeMgr == NULL) {
return SOFTBUS_INVALID_PARAM;
}
if (pthread_mutex_lock(&(g_subscribeMgr->lock)) != 0) {
LOG_ERR("pthread mutex lock failed.");
return SOFTBUS_LOCK_ERR;
}
if (RegisterAllCapBitmap(CAPABILITY_NUM, option->capabilityBitmap, g_subscribeMgr, MAX_CAP_NUM) != SOFTBUS_OK) {
(void)pthread_mutex_unlock(&(g_subscribeMgr->lock));
LOG_ERR("merge discovery capability failed.");
return SOFTBUS_DISCOVER_COAP_MERGE_CAP_FAIL;
}
if (g_subscribeMgr->isUpdate) {
if (DiscCoapSetFilterCapability(CAPABILITY_NUM, g_subscribeMgr->allCap) != SOFTBUS_OK) {
(void)pthread_mutex_unlock(&(g_subscribeMgr->lock));
LOG_ERR("set all filter capability to dfinder failed.");
return SOFTBUS_DISCOVER_COAP_SET_FILTER_CAP_FAIL;
}
}
(void)pthread_mutex_unlock(&(g_subscribeMgr->lock));
LOG_INFO("coap start passive discovery.");
return SOFTBUS_OK;
}
因为CoapSubscribe为被动发现场景,因此CoapSubscribe不需要对外广播广文。CoapSubscribe的函数只干了两件事情:1.调用RegisterAllCapBitmap将能力注册到g_subscribeMgr中;2.调用DiscCoapSetFilterCapability将能力保存到本地的g_filterCapabilityBitmap中。
OnDeviceFound接口我们之前在CoapPublish和StartDiscovery接口中发现,主动发布和主动发现都会向外部广播报文,而被动发布和被动发现只会将能力集保存在本地。那么本地的设备是如何发现外部设备的呢?我们可以顺着OnDeviceFound接口找到答案。
OnDeviceFound的触发链当对端设备收到了Coap报文后,根据报文携带的URI和method,使用对应的handler进行处理。在软总线服务加载的时候,关于发现的handler注册如下:
coap_resource_t *r = NULL;
r = coap_resource_init(coap_make_str_const(COAP_DEVICE_DISCOVER_URI), g_resourceFlags);
if (r == NULL) {
return;
}
coap_register_handler(r, COAP_REQUEST_POST, HndPostServiceDiscover);
coap_resource_set_get_observable(r, NSTACKX_TRUE);
coap_add_resource(ctx, r);
因此对端设备调用HndPostServiceDiscover函数完成报文对应的处理。其源码如下:
static void HndPostServiceDiscover(coap_context_t *ctx, struct coap_resource_t *resource, coap_session_t *session,
coap_pdu_t *request, coap_binary_t *token, coap_string_t *query, coap_pdu_t *response)
{
(void)ctx;
(void)resource;
(void)session;
(void)token;
(void)query;
if (request == NULL || response == NULL) {
return;
}
char *remoteUrl = NULL;
DeviceInfo deviceInfo;
if (HndPostServiceDiscoverInner(request, &remoteUrl, &deviceInfo) != NSTACKX_EOK) {
free(remoteUrl);
return;
}
if (GetModeInfo() == PUBLISH_MODE_UPLINE || GetModeInfo() == PUBLISH_MODE_OFFLINE) {
LOGD(TAG, "local is not DISCOVER_MODE");
free(remoteUrl);
return;
}
if (UpdateDeviceDb(&deviceInfo, g_forceUpdate) != NSTACKX_EOK) {
free(remoteUrl);
return;
}
if (g_forceUpdate) {
g_forceUpdate = NSTACKX_FALSE;
}
if (deviceInfo.mode == PUBLISH_MODE_PROACTIVE) {
LOGD(TAG, "peer is PUBLISH_MODE_PROACTIVE");
free(remoteUrl);
return;
}
if (remoteUrl != NULL) {
CoapResponseService(remoteUrl);
free(remoteUrl);
} else {
response->code = COAP_RESPONSE_CODE(COAP_RESPONSE_201);
}
}
该函数的工作主要如下:
- 调用HndPostServiceDiscoverInner将Coap报文的data数据转为deviceInfo
- 调用UpdateDeviceDb函数更新本地维护的g_deviceList
- 如果对端的报文是发布模式,则直接退出。如果是发现模式,则调用CoapResponseService函数向对端返回响应报文。
这里我们主要对第2点的处理展开,UpdateDeviceDb的源码如下:
int32_t UpdateDeviceDb(const DeviceInfo *deviceInfo, uint8_t forceUpdate)
{
DeviceInfo *internalDevice = NULL;
int8_t updated = NSTACKX_FALSE;
if (deviceInfo == NULL) {
return NSTACKX_EINVAL;
}
internalDevice = GetDeviceInfoById(deviceInfo->deviceId, g_deviceList);
if (internalDevice == NULL) {
internalDevice = CreateNewDevice(deviceInfo);
if (internalDevice == NULL) {
return NSTACKX_ENOMEM;
}
updated = NSTACKX_TRUE;
} else {
if (UpdateDeviceInfo(internalDevice, deviceInfo, &updated) != NSTACKX_EOK) {
return NSTACKX_EFAILED;
}
}
internalDevice->update = updated;
if (updated || forceUpdate) {
DeviceListChangeHandle();
}
return NSTACKX_EOK;
}
UpdateDeviceDb首先根据对端设备的deviceId,首先查找本地的g_deviceList。如果没有该设备则先创建一个设备,如果有该设备则更新该设备信息。然后调用DeviceListChangeHandle通知设备列表发生变化。
DeviceListChangeHandle的源码如下:
static void DeviceListChangeHandle(void)
{
NSTACKX_DeviceInfo deviceList[NSTACKX_MAX_DEVICE_NUM];
uint32_t count = NSTACKX_MAX_DEVICE_NUM;
(void)memset_s(deviceList, sizeof(deviceList), 0, sizeof(deviceList));
GetDeviceList(deviceList, &count, true);
NotifyDeviceListChanged(deviceList, count);
if (CoapDiscoverRequestOngoing()) {
NotifyDeviceFound(deviceList, count);
}
}
该函数主要工作如下:
- 调用GetDeviceList函数将g_deviceList列表中能力与本地能力不匹配的设备过滤掉。
- 调用NotifyDeviceListChanged通知本地设备列表发生变化。
- 如果本地正在进行发现处理,则调用NotifyDeviceFound通知本地找到了新设备。
我们主要针对第2步展开,第3步NotifyDeviceFound当前默认执行空操作。NotifyDeviceListChanged执行g_parameter中的onDeviceListChanged钩子函数进行处理,该函数为OnDeviceFound。
至此OnDeviceFound的调用链我们追踪清楚了,下面我们主要分析OnDeviceFound的实现。
OnDeviceFound的实现OnDeviceFound的源码如下:
static void OnDeviceFound(const NSTACKX_DeviceInfo *deviceList, uint32_t deviceCount)
{
if (deviceCount == 0) {
return;
}
for (uint32_t i = 0; i < deviceCount; i++) {
const NSTACKX_DeviceInfo *nstackxDeviceInfo = deviceList + i;
if (nstackxDeviceInfo == NULL) {
return;
}
if (((nstackxDeviceInfo->update) & 0x1) == 0) {
LOG_INFO("duplicate device is not reported.");
continue;
}
DeviceInfo discDeviceInfo;
(void)memset_s(&discDeviceInfo, sizeof(DeviceInfo), 0, sizeof(DeviceInfo));
if (memcpy_s(discDeviceInfo.devName, sizeof(discDeviceInfo.devName),
nstackxDeviceInfo->deviceName, sizeof(nstackxDeviceInfo->deviceName)) != EOK ||
memcpy_s(discDeviceInfo.capabilityBitmap, sizeof(discDeviceInfo.capabilityBitmap),
nstackxDeviceInfo->capabilityBitmap, sizeof(nstackxDeviceInfo->capabilityBitmap))) {
LOG_ERR("memcpy_s failed.");
return;
}
discDeviceInfo.addrNum = 1;
discDeviceInfo.devType = nstackxDeviceInfo->deviceType;
discDeviceInfo.capabilityBitmapNum = nstackxDeviceInfo->capabilityBitmapNum;
discDeviceInfo.addr[0].type = CONNECT_ADDR_WLAN;
if (ParseDeviceUdid(nstackxDeviceInfo, &discDeviceInfo) != SOFTBUS_OK) {
LOG_ERR("parse device udid failed.");
return;
}
if (ParseReservedInfo(nstackxDeviceInfo, &discDeviceInfo) != SOFTBUS_OK) {
LOG_ERR("parse reserve information failed.");
return;
}
if (g_discCoapInnerCb != NULL) {
g_discCoapInnerCb->OnDeviceFound(&discDeviceInfo);
}
}
}
该函数跳过设备列表中不需要更新的设备,然后对需要更新的设备解析其json文件,将其转换为DeviceInfo类型,然后一次调用g_discCoapInnerCb的OnDeviceFound回调函数。该回调函数为DiscOnDeviceFound,其源码如下:
void DiscOnDeviceFound(const DeviceInfo *device)
{
uint32_t tmp;
DiscInfo *infoNode = NULL;
for (tmp = 0; tmp < CAPABILITY_MAX_BITNUM; tmp++) {
if (IsBitmapSet((uint32_t *)&(device->capabilityBitmap[0]), tmp) == false) {
continue;
}
LIST_FOR_EACH_ENTRY(infoNode, &(g_capabilityList[tmp]), DiscInfo, capNode) {
LOG_INFO("find callback:id = %d", infoNode->id);
InnerDeviceFound(infoNode, device);
}
}
return;
}
即根据该设备的capabilityBitmap,在每个能力对应的g_capabilityList下,对每个节点调用InnerDeviceFound函数进行处理,InnerDeviceFound的源码如下:
static void InnerDeviceFound(const DiscInfo *infoNode, const DeviceInfo *device)
{
uint32_t tmp;
bool isInnerInfo = false;
for (tmp = 0; tmp < MODULE_MAX; tmp++) {
if (strcmp(infoNode->item->packageName, g_discModuleMap[tmp]) != 0) {
continue;
}
isInnerInfo = true;
}
if (isInnerInfo == false) {
infoNode->item->callback.subscribeCb.OnServerDeviceFound(infoNode->item->packageName, device);
return;
}
if (infoNode->item->callback.innerCb.OnDeviceFound == NULL) {
LOG_ERR("OnDeviceFound not regist");
return;
}
infoNode->item->callback.innerCb.OnDeviceFound(device);
}
该函数判断InfoNode是否为内部的节点,如果是则调用OnDeviceFound回调,否则调用OnServerDeviceFound回调。
当前内部进行发现处理时,其OnDeviceFound回调为空。外部进行发现处理时,OnServerDeviceFound回调为SoftBusClientStub的OnDeviceFound函数。
总结至此,发现模块我们就剖析完了。简而言之:
- publishService和StartDiscovery都有主动和被动的区别。主动的接口都会对外部广播报文,而被动的接口设置完本地的能力集后就什么都没干了。
- 主动的publishService和StartDiscovery都会向外部广播最大12次报文,当对端收到报文后会根据报文内容更新其设备列表,并调用OnDeviceFound通知上层应用。
- publishService和StartDiscovery的区别在于:对端收到的如果是publishService的报文,并不会返回响应,而如果收到的是StartDiscovery的报文的话,则会将自己的设备信息单播给源端。



