赞
踩
这里需要注意两点:
经过上述的讲解,下面的源码解读会容易许多:
函数较长但是大部分都是参数检查和错误处理
/* 函数功能:通过该函数来发现外部设备 函数参数:moduleName:上层模式名;info:包含发布服务的各种信息;cb:publish的回调函数 函数返回:<0失败;=0成功 */ int PublishService(const char *moduleName, const struct PublishInfo *info, const struct IPublishCallback *cb) { //权限检查 if (SoftBusCheckPermission(SOFTBUS_PERMISSION) != 0 || info == NULL || cb == NULL) { SOFTBUS_PRINT("[DISCOVERY] PublishService invalid para(info or cb)\n"); return ERROR_INVALID; } if (moduleName == NULL || strlen(moduleName) >= MAX_PACKAGE_NAME || info->publishId <= 0 || info->dataLen > MAX_CAPABILITY_DATA_LEN) { SOFTBUS_PRINT("[DISCOVERY] PublishService invliad para\n"); PublishCallback(info->publishId, PUBLISH_FAIL_REASON_PARAMETER_INVALID, NULL, cb); return ERROR_INVALID; } //是否为COAP协议(即WiFi) if (info->medium != COAP) { PublishCallback(info->publishId, PUBLISH_FAIL_REASON_NOT_SUPPORT_MEDIUM, NULL, cb); return ERROR_INVALID; } if (g_discoveryMutex == NULL) { g_discoveryMutex = MutexInit(); if (g_discoveryMutex == NULL) { PublishCallback(info->publishId, PUBLISH_FAIL_REASON_UNKNOWN, NULL, cb); return ERROR_FAIL; } } //互斥锁的检查 MutexLock(g_discoveryMutex); if (InitService() != ERROR_SUCCESS) { SOFTBUS_PRINT("[DISCOVERY] PublishService InitService fail\n"); PublishCallback(info->publishId, PUBLISH_FAIL_REASON_UNKNOWN, NULL, cb); MutexUnlock(g_discoveryMutex); return ERROR_FAIL; } //添加新的g_publishModule并返回对应的结构体指针 PublishModule *findModule = AddPublishModule(moduleName, info); if (findModule == NULL) { SOFTBUS_PRINT("[DISCOVERY] PublishService AddPublishModule fail\n"); PublishCallback(info->publishId, PUBLISH_FAIL_REASON_UNKNOWN, NULL, cb); MutexUnlock(g_discoveryMutex); return ERROR_FAIL; } int ret = ERROR_SUCCESS; //不存在则将默认的0存入g_localDeviceInfo.serviceData[]中 if (info->capability == NULL || info->capabilityData == NULL) { (void)CoapRegisterDefualtService(); //存在则将其携带的capabilityMap和serviceData写入g_localDeviceInfo } else { ret = DoRegistService(info->medium); } //释放互斥锁 MutexUnlock(g_discoveryMutex); if (ret != ERROR_SUCCESS) { PublishCallback(info->publishId, PUBLISH_FAIL_REASON_UNKNOWN, findModule, cb); return ERROR_FAIL; } else { PublishCallback(info->publishId, ERROR_SUCCESS, findModule, cb); return ERROR_SUCCESS; } }
函数流程:
1. 首先调用SoftBusCheckPermission进行权限检查,判断当前是否能够启动服务
2. 对于传入参数的检查。检查moduleName的大小和info中规定的介质是否为COAP
3. 获取全局互斥锁g_discoveryMutex
4. 调用InitService完成服务的初始化
5. 调用AddPublishModule添加一个新的g_publishModule并返回对应的结构体指针
6. 对于info中的capability相关信息的注册(存储)
可以知道主要的初始化功能由InitService完成,其余的步骤起检查和存储作用
初始化的主要函数
/* 函数功能:初始化服务 函数参数:void 函数返回:ERROR_SUCCESS;ERROR_FAIL;ERROR_NOMEMORY */ int InitService(void) { if (g_isServiceInit != 0) { return ERROR_SUCCESS; } //初始化g_deviceInfo——将deviceId进行了加密存储 if (InitCommonManager() != 0) { SOFTBUS_PRINT("[DISCOVERY] InitService InitCommonManager fail\n"); DeinitService(); return ERROR_FAIL; } g_publishModule = calloc(1, sizeof(PublishModule) * MAX_MODULE_COUNT); if (g_publishModule == NULL) { DeinitService(); return ERROR_NOMEMORY; } g_capabilityData = calloc(1, MAX_SERVICE_DATA_LEN); if (g_capabilityData == NULL) { DeinitService(); return ERROR_NOMEMORY; } //调用WifiEventTrigger函数 //并将其登记入g_wifiCallback RegisterWifiCallback(WifiEventTrigger); //coap协议服务初始化 int ret = CoapInit(); if (ret != ERROR_SUCCESS) { SOFTBUS_PRINT("[DISCOVERY] InitService CoapInit fail\n"); DeinitService(); return ret; } //将消息写入队列中 CoapWriteMsgQueue(UPDATE_IP_EVENT); //根据DeviceInfo更新nstackInfo并将deviceHash和localDeviceInfo更新到g_localDeviceInfo中 ret = CoapRegisterDeviceInfo(); if (ret != ERROR_SUCCESS) { SOFTBUS_PRINT("[DISCOVERY] InitService CoapRegisterDeviceInfo fail\n"); DeinitService(); return ret; } g_isServiceInit = 1; SOFTBUS_PRINT("[DISCOVERY] InitService ok\n"); return ERROR_SUCCESS; }
可以主要从五个函数入手:InitCommonManager、RegisterWifiCallback、CoapInit、CoapWriteMsgQueue和CoapRegisterDeviceInfo
由该函数来完成初始化的功能
//初始化本地设备信息(g_deviceinfo) int InitLocalDeviceInfo(void) { char deviceId[DEVICEID_MAX_NUM] = {0}; if (g_deviceInfo != NULL) { memset_s(g_deviceInfo, sizeof(DeviceInfo), 0, sizeof(DeviceInfo)); } else { g_deviceInfo = (DeviceInfo *)calloc(1, sizeof(DeviceInfo)); if (g_deviceInfo == NULL) { return ERROR_FAIL; } } //初始化端口和是否信任状态为-1 g_deviceInfo->devicePort = -1; g_deviceInfo->isAccountTrusted = 1; unsigned int ret; //获取加密后的deviceid ret = GetDeviceIdFromFile(deviceId, MAX_VALUE_SIZE); if (ret != ERROR_SUCCESS) { SOFTBUS_PRINT("[DISCOVERY] Get device fail\n"); return ERROR_FAIL; } //不同内核存储的信息不同 #if defined(__LITEOS_M__) || defined(__LITEOS_RISCV__) g_deviceInfo->deviceType = L0; ret = (unsigned int)strcpy_s(g_deviceInfo->deviceName, sizeof(g_deviceInfo->deviceName), L0_DEVICE_NAME); #else g_deviceInfo->deviceType = L1; ret = (unsigned int)strcpy_s(g_deviceInfo->deviceName, sizeof(g_deviceInfo->deviceName), L1_DEVICE_NAME); #endif ret |= (unsigned int)strcpy_s(g_deviceInfo->deviceId, sizeof(g_deviceInfo->deviceId), deviceId); ret |= (unsigned int)strcpy_s(g_deviceInfo->version, sizeof(g_deviceInfo->version), "1.0.0"); if (ret != 0) { return ERROR_FAIL; } SOFTBUS_PRINT("[DISCOVERY] InitLocalDeviceInfo ok\n"); return ERROR_SUCCESS; }
我们可以看看DeviceInfo结构体中都包含了哪些信息:
其中deviceType有如下定义:
该初始化函数初始化了以下属性:
devicePort:-1
isAccountTrusted:1
deviceName:L1_DEVICE_NAME|L0_DEVICE_NAME
deviceId:deviceId(加密过并存储在本地文件中)
version:" 1.0.0"
函数主要是初始化工作
有个函数比较有意思:GetDeviceIdFromFile
deviceId作为设备的唯一标识符十分重要,所有要保证其独一无二性,这里使用了伪随机生成的方法生成deviceId
函数流程:
1. 调用ReadDeviceId将存储在本地的deviceId取出,如果本地存在则直接返回该deviceId
2. 本地不存在deviceId,则需要随机初始化一个deviceId:调用hks_generate_random生成一个随机密钥——>调用GetRandomStr根据规则生成deviceId——>调用WriteDeviceId将deviceId写入本地以便下次调用
注意这里的加密算法——也就是为随机生成算法是不可逆的,所以一次生成,不可解密,也不需解密
其调用:
RegisterWifiCallback(WifiEventTrigger);
其内部:
这里就调用了前几篇花费大力气讲解的WiFiEventTrigger并将其加入WiFi发现的回调函数中——也就是说当发现一个外部设备通过WiFi想要接入时,就会回调WiFiEventTrigger函数进行对接
具体的WiFiEventTrigger的讲解这里不再赘述,可以参考我的前几篇博客:
再谈WiFiEventTrigger——开启监听
再谈WiFiEventTrigger——数据处理与发送
再谈WiFiEventTrigger——会话开始
内部实现为NSTACKX_Init——CoapInitDiscovery
函数流程:
1. 判断当前g_nstackInitState的状态是否为start状态,不是则说明初始化完毕直接返回EOK
2. 是start状态则改为ONGOING状态并调用cJSON_InitHooks进行初始化并给cJSON提供空间分配和释放的能力
3. 调用CoapInitDiscovery初始化基于COAP协议的发现服务
4. 将state改为DONE状态返回EOK
所以初始化的三个状态:START——ONGOING——DONE
下面接着看CoapInitDiscovery
由四个函数共同完成COAP初始化的任务:
这里当内核为A核是需要额外调用一个CreateQueryIpThread进行IP队列线程的创建
函数流程:
1. 检查g_serverFd是否大于等于0,是则服务端存在直接返回、不存在则初始化sockAddr以及sin_port
2. 调用CoapCreateUdpServer进行UDP服务端的创建
3. 调用COAP_SoftBusInitMsgId初始化g_msgId为0
需要注意的是在函数CoapCreateUdpServer中创建的流程基本和TCP一致都是先socket再bind,但是在socket创建的时候有一个参数不同:
UDP:
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
TCP:(来自函数OpenTcpServer)
int fd = socket(AF_INET, SOCK_STREAM, 0);
这就是区分TCP和UDP的典型参数之一了,我们看看它分别规定了啥
上述参数规定的区别恰好就是TCP与UDP通信的差别!
首先贴上源代码注释:
函数的主要作用为WiFi事件创建了一个消息队列wifiQue并且当定义了(LITEOS_M)或(LITEOS_RISCV)时为其配置了WiFi状态变换是回调处理函数g_coapEventHandler
这里的线程创建同样因为内核的差异所以使用了不同的os_adapter进行适配,同样是通过CreateThread和一个running函数CoapWifiEventThread来完成一个线程资源的分配和线程处理函数的调度
来看看CoapWifiEventThread的内部逻辑
可以看到其调用ReadMsgQue将g_wifiQueueId消息队列中的数据读入handle中并调用handler进行处理
而这里的handler就是下面CoapWriteMsgQueue中的handler——也就是前面的主角wifiEventTrigger函数
创建过程不再赘述,这里同样重点看下线程的实际运行函数CoapReadHandle
同样是select机制监测可读有请求的fd,检查刚刚创建的g_serverFd是否在其中,是则对其调用函数HandleReadEvent进行处理
由于调用较深,这里仅放上重要的源代码片段,更注重过程和逻辑
HandleReadEvent
涉及三个重要函数(顺序向下执行)
CoapSocketRecv:调用recvfrom从g_serverFd所在的服务端接收数据入recvBuffer中
COAP_SoftBusDecode:解析报文。主要判断以下几个属性:
pkt->protocol = COAP_UDP
pkt->header.ver = COAP_VERSION
pkt->header.tokenLen <= MAX_TOK_LEN & > 0
并解析其可选配置option和payload
PostServiceDiscover:针对解析后的decodePacket进行相应
PostServiceDiscover
GetServiceDiscoverInfo:解析数据包pkt的payload,
将设备信息存于deviceInfo中,并获取remoteUrl
inet_ntop:将数字地址转换为适合显示的文本字符串
if (remoteUrl != NULL)
CoapResponseService:向解析得到的remoteUrl和IP发送消息回复
CoapResponseService
1. 首先封装了结构体CoapRequest.remoteUrl .remoteIp 调用PrepareServiceDiscover得到char* payload包含: a. deviceInfo中的八个信息: deviceId devicename type hicomversion mode deviceHash serviceData capabilityBitmap b. g_interfaceList中的wlanIp wlanIp 2. 然后得到读写的缓冲区COAP_ReadWriteBuffer 3. 调用BuildSendPkt构造应答的packet包括option、remoteIp、payload等信息的封装得到sndPktBuff 4. 将得到的sndPktBuff和其长度dataLen封装入coapRequest 5. 调用CoapSendRequest将coapRequest发送
这里到关键的发送环节结合源码分析
CoapSendRequest
函数流程:
1. 首先设置了sockAddr包括remoteIp、port、family
2. 调用CoapCreateUdpClient传入sockAddr创建一个UDP客户端
3. 调用GetCoapClientSocket得到本地的客户端Fd
4. 调用CoapSocketSend将coapRequest发送到对应的目的地址
这里有趣的是,在发送回应信息的时候创建了一个客户端——也就是说在进行UDP通信的时候一个终端设备在某些特定的时候充当服务端,有的时候充当客户端——在这里回复的时候就是创建了一个客户端来进行sendTo
另外在CoapCreateUdpClient的代码中每次调用都会新生成一个socket并进行bind,然后调用CloseSocket将原来g_clientFd对应的socket关闭,然后将新的sockfd赋给g_clientFd——也就是说每次回复信息是都需要新创建一个客户端进行回复报文的发送,发送完毕后就再下一次消息回复时被销毁,从而能够创建新的客户端进行回复
也符合UDP尽力、无保障的传输特点——发送完之后当前客户端就销毁
但是UDP的服务端socket却是一直保留的用于接收remoteURL和remoteIp
注意这里的handler所指的函数CoapHandleWifiEvent实际上就是刚刚在RegisterWifiCallback函数中得到wifiEventTrigger的g_wifiCallback
函数流程:
1. 将g_wifiCallback赋给handler并将其state更新为参数所带的UPDATE_IP_EVENT
2. 调用函数WriteMsgQue将handler发送到g_wifiQueueId所在的消息队列中
通过上述操作,消息队列中就存在了处理函数——也就是wifiEventTrigger每当发现设备WiFi接入时,消息队列会调用该函数进行连接或数据处理
同样是信息在各个结构体的流转和更新,这里不再赘述
至此服务发布——基于COAP协议的UDP通信机制的全部内容到此结束!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。