鸿蒙OpenHarmony源码分析之分布式软总线:trans_service模块(5)/TCP会话管理

📌往期推文全新看点(文中附带最新·鸿蒙全栈学习笔记)

①📖 鸿蒙应用开发与鸿蒙系统开发哪个更有前景?

②📖嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~

③📖 对于大前端开发来说,转鸿蒙开发究竟是福还是祸?

④📖 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?

⑤📖 记录一场鸿蒙开发岗位面试经历~

⑥📖 持续更新中……


一、概述

trans_service模块基于系统内核提供的socket通信,向authmanager模块提供设备认证通道管理和设备认证数据的传输;向业务模块提供session管理和基于session的数据收发功能,并且通过GCM模块的加密功能提供收发报文的加解密保护。 本文将继续介绍鸿蒙系统的会话机制的管理,承接上文 OpenHarmony源码分析之分布式软总线:trans_service模块(4)/TCP会话管理 的内容,本文将介绍鸿蒙系统如何处理客户端发起的请求消息。

二、源码分析

  1. 在上文提到的OnProcessDataAvailable()函数中,首先判断该会话的名称是不是”softbus_Lite_unknown”,若是,则表示该会话是一个新建的会话,收到的消息应是客户端发起的请求消息,因此接下来调用HandleRequestMsg()函数进行处理,具体函数分析如下:
/*
函数功能:处理会话中的请求消息
函数参数:
    session:会话地址
函数返回值:
    成功:返回true
    失败:返回false
详细:解析认证数据包头部
*/
static bool HandleRequestMsg(TcpSession *session)
{
    char data[RECIVED_BUFF_SIZE] = { 0 };
    int size = TcpRecvData(session->fd, data, AUTH_PACKET_HEAD_SIZE, 0);//读取接收缓冲区的数据保存到data数组中
    if (size != AUTH_PACKET_HEAD_SIZE) {//如果读到的数据小于认证数据包头部长度,返回错误
        return false;
    }
    int identifier = GetIntFromBuf(data, 0);//解析identifier字段
    if ((unsigned int)identifier != PKG_HEADER_IDENTIFIER) {//如果头部标识符不是PKG_HEADER_IDENTIFIER,则返回false
        return false;
    }
    int dataLen = GetIntFromBuf(data, AUTH_PACKET_HEAD_SIZE - sizeof(int));//解析dataLen字段
    if (dataLen + AUTH_PACKET_HEAD_SIZE >= RECIVED_BUFF_SIZE) {//越界
        return false;
    }
    int total = size;//记录读到的总量
    int remain = dataLen;//剩余未读
    while (remain > 0) {//循环读取套接字接收缓冲区,直到读满dataLen字节
        size = TcpRecvData(session->fd, data + total, remain, 0);
        remain -= size;
        total += size;
    }
    cJSON *receiveObj = TransFirstPkg2Json(data, dataLen + AUTH_PACKET_HEAD_SIZE);//将第一个数据包解密后转换为cjson格式的结构体
    if (receiveObj == NULL) {
        return false;
    }
    int ret = AssignValue2Session(session, receiveObj);//将接收到的json对象的值赋给当前会话,如会话名字sessionName以及会话密钥sessionKey
    cJSON_Delete(receiveObj);
    if (ret != true) {
        return false;
    }
    SessionListenerMap *sessionListener = GetSessionListenerByName(session->sessionName, strlen(session->sessionName));//根据会话名字在会话管理器中查找会话监听者
    if (sessionListener == NULL) {
        return false;
    }
    if (!ResponseToClient(session)) {//响应回复客户端的请求消息
        SOFTBUS_PRINT("[TRANS] HandleRequestMsg ResponseToClient fail\n");
        return false;
    }
    if (sessionListener->listener == NULL) {
        return false;
    }
    if (sessionListener->listener->onSessionOpened == NULL) {
        return false;
    }
    if (sessionListener->listener->onSessionOpened(session->fd) != 0) {
        return false;
    }
    return true;
}
  1. 接着看函数HandleRequestMsg()的具体内容,首先接收数据包头部并解析,然后接收数据包负载部分,得到负载之后,在TransFirstPkg2Json()函数中将该负载解密然后转换为cjson格式的结构体。具体实现及分析如下:
/*
函数功能:将第一个数据包解密后转换为cjson格式的结构体
函数参数:
    buffer:数据包起始地址
    bufferSize:数据包总大小
函数返回值:
    成功:返回cJSON格式的结构体
    失败:返回NULL
详细:
*/
static cJSON *TransFirstPkg2Json(const char *buffer, int bufferSize)
{
    if (bufferSize < AUTH_PACKET_HEAD_SIZE) {//数据包大小小于头部长度
        SOFTBUS_PRINT("[TRANS] bufferSize < AUTH_PACKET_HEAD_SIZE\n");
        return NULL;
    }
    int offset = AUTH_PACKET_HEAD_SIZE - sizeof(int);//为了解析获取dataLen字段
    int dataLen = GetIntFromBuf(buffer, offset) - SESSION_KEY_INDEX_SIZE;//除去会话密钥索引的数据长度
    if (dataLen <= 0 || dataLen > (RECIVED_BUFF_SIZE - AUTH_PACKET_HEAD_SIZE)) {//越界检查
        return NULL;
    }
    int index = GetKeyIndex(buffer, AUTH_PACKET_HEAD_SIZE, MESSAGE_INDEX_LEN);//获取数据包负载中的会话密钥索引(index)
    SessionKey *sessionKey = AuthGetSessionKeyByIndex(index);//通过索引(index)获取会话密钥
    if (sessionKey == NULL) {
        SOFTBUS_PRINT("[TRANS] TransFirstPkg2Json GetSessionKey fail\n");
        return NULL;
    }
    char *firstDataJson = calloc(1, dataLen);//申请dataLen字节数的内存空间
    if (firstDataJson == NULL) {
        return NULL;
    }
    unsigned char* cipherTxt = (unsigned char*)(buffer + AUTH_PACKET_HEAD_SIZE + SESSION_KEY_INDEX_SIZE);//得到密文的首地址
    AesGcmCipherKey cipherKey = {0};//aes_gcm密钥
    cipherKey.keybits = GCM_KEY_BITS_LEN_128;//密钥长度为128位
    int ret = memcpy_s(cipherKey.key, SESSION_KEY_LENGTH, sessionKey->key, AUTH_SESSION_KEY_LEN);//将会话密钥赋值给加密密钥
    ret += memcpy_s(cipherKey.iv, IV_LEN, cipherTxt, IV_LEN);//将密文的前IV_LEN个字节的值作为aes_gcm加密算法的初始iv值
    if (ret != 0) {
        free(firstDataJson);
        return NULL;
    }
    int plainLen = DecryptTransData(&cipherKey, cipherTxt, dataLen, (unsigned char*)firstDataJson, dataLen);//解密传输数据,解密成功则返回实际明文长度,并将明文存储在firstDataJson空间中
    if (plainLen <= 0) {
        free(firstDataJson);
        return NULL;
    }
    cJSON *receiveObj = cJSON_Parse(firstDataJson);//json格式解析,将json字符串转换为cjson结构体
    free(firstDataJson);
    return receiveObj;
}
  1. 然后将接收到的json对象的值赋给当前会话,如会话名字sessionName以及会话密钥sessionKey,具体在AssignValue2Session()函数中实现,分析如下:
/*
函数功能:为当前会话赋值sessionName和sessionKey,其值来自于对端发送的json数据中
函数参数:
    session:TCP会话结构体
    receiveObj:cJSON结构体对象
函数返回值:
    成功:返回cJSON格式的结构体
    失败:返回NULL
详细:
*/
static bool AssignValue2Session(TcpSession *session, cJSON *receiveObj)
{
    if (receiveObj == NULL) {
        return false;
    }
    char *recvBus = GetJsonString(receiveObj, "BUS_NAME");//获取"BUS_NAME"(key)字段的String类型的值(value)
    if (recvBus == NULL) {
        return false;
    }
    if (strncpy_s(session->sessionName, NAME_LENGTH, recvBus, strlen(recvBus)) != 0) {//将获取到的总线名BUS_NAME作为当前会话名sessionName
        return false;
    }
    char *sessionKeyEncoded = GetJsonString(receiveObj, "SESSION_KEY");//获取"SESSION_KEY"(key)字段的String类型的值(value)
    if (sessionKeyEncoded == NULL) {
        return false;
    }
    size_t olen = 0;
    int ret = mbedtls_base64_decode((unsigned char *)session->sessionKey, SESSION_KEY_LENGTH,
        &olen, (unsigned char *)sessionKeyEncoded, strlen(sessionKeyEncoded));//将base64编码的数据解码,然后赋值给当前的会话密钥session->sessionKey
    if (ret != 0) {
        SOFTBUS_PRINT("[TRANS] AssignValue2Session mbedtls_base64_decode error: %d\n", ret);
        return false;
    }
    SOFTBUS_PRINT("[TRANS] AssignValue2Session busname=%s, fd=%d\n", session->sessionName, session->fd);
    return true;
}
  1. 最后回复给客户端,在ResponseToClient()函数中实现,具体分析如下:
/*
函数功能:响应回复客户端的请求消息
函数参数:
    session:TCP会话结构体
函数返回值:
    成功:返回true
    失败:返回false
详细:
*/
static bool ResponseToClient(TcpSession *session)
{
    cJSON *jsonObj = cJSON_CreateObject();//产生一个cJSON对象
    if (jsonObj == NULL) {
        return false;
    }
    GetReplyMsg(jsonObj, session);//构造回复消息,初始化cJSON对象
    char *msg = cJSON_PrintUnformatted(jsonObj);//将cJSON对象输出为无格式的字符串
    if (msg == NULL) {
        cJSON_Delete(jsonObj);
        return false;
    }
    int bufLen = 0;
    unsigned char *buf = PackBytes(msg, &bufLen);//按字节构造认证协议数据包,返回数据包缓冲区首地址
    if (buf == NULL) {
        SOFTBUS_PRINT("[TRANS] ResponseToClient PackBytes fail\n");
        free(msg);
        cJSON_Delete(jsonObj);
        return false;
    }
    int dataLen = TcpSendData(session->fd, (char*)buf, bufLen, 0);//发送数据给对端
    free(msg);
    cJSON_Delete(jsonObj);
    free(buf);
    if (dataLen <= 0) {
        SOFTBUS_PRINT("[TRANS] ResponseToClient TcpSendData fail\n");
        return false;
    }
    return true;
}

其中构造回复消息的函数为GetReplyMsg(),分析如下:

/*
函数功能:构造回复消息,初始化cJSON对象
函数参数:
    jsonObj:cJSON对象
    session:当前TCP会话
函数返回值:无
详细:
*/
static void GetReplyMsg(cJSON *jsonObj, const TcpSession *session)
{
    cJSON_AddNumberToObject(jsonObj, "CODE", 1);//向该cJSON对象中添加数值类型的"CODE"字段,值为1
    cJSON_AddNumberToObject(jsonObj, "API_VERSION", DEFAULT_API_VERSION);//向该cJSON对象中添加数值类型的"API_VERSION"字段,值为DEFAULT_API_VERSION
    DeviceInfo *local = BusGetLocalDeviceInfo();//获取本地设备信息
    if (local == NULL) {
        return;
    }
    char* deviceId = local->deviceId;
    cJSON_AddStringToObject(jsonObj, "DEVICE_ID", deviceId);//向该cJSON对象中添加数值类型的"DEVICE_ID"字段,值为deviceId
    cJSON_AddNumberToObject(jsonObj, "UID", -1);//向该cJSON对象中添加数值类型的"UID"字段,值为-1
    cJSON_AddNumberToObject(jsonObj, "PID", -1);//向该cJSON对象中添加数值类型的"PID"字段,值为-1
    cJSON_AddStringToObject(jsonObj, "PKG_NAME", session->sessionName);
    cJSON_AddStringToObject(jsonObj, "CLIENT_BUS_NAME", session->sessionName);
    cJSON_AddStringToObject(jsonObj, "AUTH_STATE", "");//认证状态为空
    cJSON_AddNumberToObject(jsonObj, "CHANNEL_TYPE", 1);//CHANNEL_TYPE为1
}

至此,对于新会话的客户端请求数据的处理以及响应回复过程已结束。

<think>嗯,用户现在遇到了编译错误,提示找不到boost/assign.hpp这个头文件。我需要帮他们解决这个问题。首先,我应该回忆一下常见的导致这种错误的原因。通常来说,这种错误是因为编译器找不到对应的头文件路径,也就是Boost库没有被正确安装或者CMake配置中没有包含Boost的头文件路径。 用户使用的是MinGW,系统是Windows环境,所以可能需要指导他们如何安装Boost库,并配置CMakeLists.txt。用户之前的问题是关于pybind11的配置,现在转向Boost库的问题,可能他们的项目同时依赖这两个库。我需要分步骤引导他们解决这个问题。 首先,我应该确认用户是否已经安装了Boost库。如果没有安装,他们需要先安装Boost。在Windows下,使用MinGW安装Boost可能有几种方法:比如使用预编译的二进制包,或者从源代码编译。不过对于新手来说,可能更简单的方法是通过MSYS2或者vcpkg这样的包管理器安装,但用户可能没有这些工具,所以需要给出通用的安装方法。 其次,安装完成后,用户需要在CMakeLists.txt中添加Boost的查找和链接。这时候需要使用find_package来定位Boost,并包含头文件路径和链接库。用户可能没有在CMake中正确配置这些步骤,导致编译时找不到头文件。 另外,用户可能没有设置BOOST_ROOT环境变量,或者CMake没有正确找到Boost的安装路径。这时候需要指导用户如何设置这些变量,或者在CMake命令中指定Boost的路径。 还要注意Boost的版本是否兼容,以及用户是否安装了正确的组件。例如,有些Boost库是头文件库,只需要包含头文件,而有些则需要链接动态库。assign.hpp属于Boost.Assign库,这个库是头文件库,所以用户可能只需要正确包含头文件路径,而不需要链接库。但需要确认这一点,避免用户遗漏其他依赖。 可能的解决方案步骤包括: 1. 安装Boost库,确保头文件路径正确。 2. 在CMakeLists.txt中使用find_package(Boost REQUIRED)来查找Boost。 3. 包含Boost的头文件目录,使用include_directories或target_include_directories。 4. 如果使用了需要链接的Boost库,还需要target_link_libraries。 另外,用户可能在安装Boost时选择了错误的架构(比如32位和64位的MinGW不匹配),需要确认安装的Boost版本是否与编译器兼容。例如,如果用户用的是MinGW-w64 64位,那么Boost也需要编译成64位的版本。 用户的具体错误信息显示在trans_map.cpp中包含了boost/assign.hpp,而编译器找不到这个文件。这说明Boost的头文件路径没有被正确包含到编译器的搜索路径中。因此,解决的关键在于确保CMake能够找到Boost的安装位置,并将该路径传递给编译器。 可能需要指导用户如何手动指定Boost的路径,比如通过设置BOOST_ROOT变量或者在CMake命令中传递参数,例如-DBOOST_ROOT=D:/boost。 总结一下,我需要逐步引导用户检查Boost的安装情况,确认CMake配置是否正确,并根据他们的具体情况提供不同的解决方案,比如使用vcpkg安装、手动安装并配置路径,或者修改CMakeLists文件。同时,要提醒用户注意路径中的空格问题,因为用户的路径中有“Map convert tool”这样的目录名,可能需要用引号包裹路径,或者在CMake中正确处理空格。</think>--- ### 问题分析 您遇到的编译错误核心问题是 **Boost 库的头文件未被正确找到**,具体表现为: ```bash fatal error: boost/assign.hpp: No such file or directory ``` 这说明: 1. Boost 库未安装 2. 或 CMake 未正确配置 Boost 库路径 --- ### 解决方案步骤 #### 步骤1:安装 Boost 库 根据您的编译环境(Windows + MinGW),推荐以下安装方式: ##### 方法1:通过 MSYS2 安装(推荐) ```bash # 在 MSYS2 终端中执行 pacman -S mingw-w64-x86_64-boost ``` 安装路径通常为:`/mingw64/include/boost` ##### 方法2:手动安装 1. 下载预编译包:从 [Boost 官网](https://www.boost.org/users/download/) 选择 `Prebuilt Windows binaries` 2. 选择与 MinGW 匹配的版本(如 `boost_1_84_0-msvc-14.3-64.exe`) 3. 安装到无空格路径(如 `D:\libs\boost_1_84_0`) --- #### 步骤2:配置 CMakeLists.txt 修改您的 `CMakeLists.txt`,添加 Boost 依赖: ```cmake cmake_minimum_required(VERSION 3.5) project(YourProject) # 关键配置:指定 Boost 路径(根据实际安装路径修改) set(BOOST_ROOT "D:/libs/boost_1_84_0") # 或通过环境变量设置 # 查找 Boost 库 find_package(Boost 1.84.0 REQUIRED COMPONENTS system filesystem) # 按需添加组件 # 包含头文件路径 include_directories(${Boost_INCLUDE_DIRS}) # 链接到目标 add_executable(trans_map src/trans_map.cpp) target_link_libraries(trans_map PRIVATE ${Boost_LIBRARIES}) ``` --- #### 步骤3:验证路径配置 通过 CMake 命令显式指定路径(如果步骤2未生效): ```bash cmake -DBOOST_ROOT="D:/libs/boost_1_84_0" -B build ``` --- ### 关键注意事项 1. **路径规范**: - Windows 路径使用正斜杠 `/` 或双反斜杠 `\\` - 避免路径包含中文或空格(您的当前路径 `Map convert tool` 含空格,建议改为 `MapConvertTool`) 2. **版本匹配**: - MinGW 编译器位数(32/64位)必须与 Boost 版本一致 - 通过 `g++ -v` 查看编译器信息 3. **最小化依赖**: - `boost/assign.hpp` 是纯头文件库,只需包含路径,无需链接二进制库 --- ### 验证安装成功的标志 1. 在编译输出中应出现: ```bash -- Found Boost: D:/libs/boost_1_84_0 (found suitable version "1.84.0") ``` 2. 头文件可被正确找到: ```bash ls D:/libs/boost_1_84_0/boost/assign.hpp ``` --- ### 常见问题排查表 | 现象 | 解决方案 | |-------|---------| | `undefined reference to boost::...` | 添加 `COMPONENTS` 并确保已编译对应库 | | `Invalid CMake command: find_package` | 检查 CMake 版本是否 ≥3.5 | | 路径包含空格 | 使用短路径(如 `D:/PROGRA~1/boost`)或迁移项目 | | MinGW 工具链不匹配 | 通过 `pacman -S mingw-w64-x86_64-toolchain` 安装完整工具链 | 建议优先使用 MSYS2 管理依赖,可避免大多数路径问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值