RDT的切包和组包
更新日期:2025/2/13
概述
RDTAPIs是P2P sdk里面的一个提供可靠传输的模块,提供通用的数据传输(read和write)接口,发送端和接收端都有缓存区用以缓存发送中或者接收中的数据。
因为RDT模块在发送数据时,不会完全按照一帧一帧地传,实际为可能一帧被分成多个分片,或者多帧组成一个分片来传,所以接收端在读取数据的时候,需要对数据进行重组。
RDT的帧设计
RDT的一帧可以设计为5个部分,分别为帧起始标志,帧类型,当前帧大小,帧数据,帧结束位:
frmBegin[4]|frmInfo[5]|data[...]|frmEnd[2]
设计帧头frmInfo为:type+dataSize
type[1]|dataSize[4]
开发者可以根据实际场景,修改frmInfo,只要确保两端使用同样的frmInfo即可。
RDT的帧读取方法
- RDT帧的读取原则:
- 先读取frmBegin,4个字节。
- 接着读取一个frmInfo,5个字节。
- 从frmInfo中解析出type以及dataSize,然后进行数据读取:
读取数据头:
/** * 读取帧信息数据(通过RDT通道) * @param frmInfo 接收帧信息的缓冲区 * @param frmInfoBufferSize 缓冲区大小(需 >= frmInfoSize,防止溢出) * @param frmInfoSize 预期读取的帧信息总大小 * @return 0=读取成功,负数=错误码(RDT_Read返回的错误) */ int readFrameInfo(char* frmInfo, size_t frmInfoBufferSize, int frmInfoSize) { // 参数合法性校验:缓冲区大小不足或指针为空 if (frmInfo == NULL || frmInfoBufferSize < (size_t)frmInfoSize) { return -1; // 可替换为具体错误码(如自定义的BUFFER_SIZE_INSUFFICIENT) } int readSize = 0; // 单次读取字节数 int rest = frmInfoSize; // 剩余需读取的字节数 memset(frmInfo, 0, frmInfoBufferSize); // 清空接收缓冲区 // 循环读取直到获取完整帧信息 while (rest > 0) { // 读取数据:从剩余偏移位置开始,读取剩余长度,超时1000ms readSize = RDT_Read( rdt_channel_id, frmInfo + (frmInfoSize - rest), // 当前写入位置 = 起始位置 + 已读长度 rest, 1000 ); // 处理读取结果 if (readSize == RDT_ER_TIMEOUT) { // 超时:继续尝试读取(不返回错误,直到读取完成或其他错误) continue; } else if (readSize < 0) { // 发生错误(非超时):返回错误码 return readSize; } // 累加已读长度 rest -= readSize; } // 全部读取完成 return 0; }
读取数据:
//读取帧数据,确保buffer足够大,否则进行resize buffer。
/** * 通过RDT通道读取一帧完整数据 * @param dataBuf 接收数据的缓冲区 * @param dataBufferSize 缓冲区大小(需确保 >= dataSize,防止止溢出) * @param dataSize 预期读取的帧数据总大小 * @return 实际读取的字节数(0~dataSize),负数表示错误(RDT_Read返回的错误码) */ int readOneFrame(char* dataBuf, size_t dataBufferSize, int dataSize) { // 参数合法性校验 if (dataBuf == NULL || dataBufferSize < (size_t)dataSize) { return -1; // 可替换为具体错误码(如BUFFER_SIZE_INSUFFICIENT) } int readSize = 0; // 单次读取字节数 int rest = dataSize; // 剩余需读取的字节数 int totalRead = 0; // 累计读取字节数 memset(dataBuf, 0, dataBufferSize); // 清空接收缓冲区 // 循环读取直到获取完整帧数据或发生错误 while (rest > 0) { // 读取数据:从当前偏移位置开始,读取剩余长度,超时1000ms readSize = RDT_Read( rdt_channel_id, dataBuf + (dataSize - rest), // 当前写入位置 = 起始位置 + 已读长度 rest, 1000 ); // 处理读取结果 if (readSize == RDT_ER_TIMEOUT) { // 超时:继续尝试读取 continue; } else if (readSize < 0) { // 发生错误(非超时):返回错误码 return readSize; } // 更新累计读取量和剩余量 totalRead += readSize; rest -= readSize; } // 返回实际读取的总字节数(正常情况应等于dataSize) return totalRead; }
读取帧结束标志:
/** * 读取帧结束标志(固定2字节) * @return 0=读取成功,负数=错误码(RDT_Read返回的错误) */ int readFrameEndFlag() { const int END_FLAG_SIZE = 2; // 帧结束标志固定大小(2字节) int readSize = 0; // 单次读取字节数 int rest = END_FLAG_SIZE; // 剩余需读取的字节数 char buffer[8] = {0}; // 接收缓冲区(预留8字节避免溢出) // 循环读取直到获取完整的结束标志 while (rest > 0) { // 读取数据:从剩余偏移位置开始,读取剩余长度,超时1000ms readSize = RDT_Read( rdt_channel_id, buffer + (END_FLAG_SIZE - rest), // 当前写入位置 rest, 1000 ); // 处理读取结果 if (readSize == RDT_ER_TIMEOUT) { // 超时:继续尝试读取 continue; } else if (readSize < 0) { // 发生错误(非超时):返回错误码 return readSize; } // 更新剩余读取长度 rest -= readSize; } // 读取成功(如需验证标志值,可在此处添加校验逻辑) // 示例:if (buffer[0] != 0xAA || buffer[1] != 0x55) return -ERROR_INVALID_END_FLAG; return 0; }
所以循环读取一个完整帧的方法:
// 帧信息固定大小(5字节) #define FRAME_INFO_SIZE 5 // 主循环:持续读取并处理完整数据帧 while (1) { // 1. 读取帧信息(5字节) char frmInfoBuffer[8] = {0}; // 缓冲区预留8字节,确保大于FRAME_INFO_SIZE int ret = readFrameInfo(frmInfoBuffer, sizeof(frmInfoBuffer), FRAME_INFO_SIZE); if (ret < 0) { printf("读取帧信息失败,错误码:%d\n", ret); break; } // 2. 解析帧信息:获取数据类型和数据大小 int dataType = 0; int dataSize = 0; parseFrameInfo(frmInfoBuffer, &dataType, &dataSize); // 校验数据大小合理性(防止缓冲区溢出) if (dataSize <= 0 || dataSize > 100 * 1024) { printf("无效的数据大小:%d,跳过当前帧\n", dataSize); continue; // 数据大小异常,跳过处理 } // 3. 读取完整数据帧 char dataBuffer[100 * 1024] = {0}; // 100KB数据缓冲区 ret = readOneFrame(dataBuffer, sizeof(dataBuffer), dataSize); if (ret < 0) { printf("读取数据帧失败,错误码:%d\n", ret); break; } // 验证是否读取完整(readOneFrame返回实际读取长度) if (ret != dataSize) { printf("数据帧不完整(预期:%d,实际:%d),跳过处理\n", dataSize, ret); continue; } // 4. 处理完整数据帧 handleUserData(dataBuffer, dataSize, dataType); // 优化:传入数据大小和类型,增强处理能力 // 5. 读取帧结束标志(验证帧完整性) ret = readFrameEndFlag(); if (ret < 0) { printf("读取帧结束标志失败,错误码:%d\n", ret); break; } }
到此为止,已经完成了对RDT数据的解析,开发者可以在开发过程中,根据项目的实际情况,设计自己的帧格式。