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数据的解析,开发者可以在开发过程中,根据项目的实际情况,设计自己的帧格式。
