基于RDTAPIs的文件下载
主要内容
IO交互

文件下载过程中的IO交互主要分为以下几个步骤:
- APP通过
avSendIOCtrl(IOCTRL_FILEMANAGER_FILE_LIST_REQ)查询指定时间内的文件列表 - 设备通过
avSendIOCtrl(IOCTRL_FILEMANAGER_FILE_LIST_RESP)回复文件列表 - 用户选择文件后,APP通过
avSendIOCtrl(IOCTRL_FILEMANAGER_FILE_DOWNLOAD_REQ)通知设备 - 设备根据资源情况,通过
avSendIOCtrl(IOCTRL_FILEMANAGER_FILE_DOWNLOAD_RESP)告知APP使用的通道数和API类型 - 双方创建通道并进行数据传输
- 传输完成后关闭通道
RDT通道的创建和销毁
创建(APP端/设备端通用)
int createChannelForDownload(int sid, int iotc_channel_id)
{
return RDT_Create(sid, 5000, iotcChannelId);
}销毁(APP端/设备端通用)
void destoryChannelOfDownload(int rdt_id)
{
if (rdt_id < 0)
return;
RDT_Abort(rdt_id);
}数据的传输
RDT协议本身提供的接口只有Read和Write,所以存在粘包的情况,需要另外设计切包的机制。每个数据包分为包头、数据和包尾,具体格式参考:数据包格式定义
设备端
说明:RDT的数据传输是可靠的传输,每次调用RDT_Write,都将数据写入本地的发送缓存区,所以等所有的数据都使用RDT_Write写入后,需要检查发送缓存区是否为空,为空,则说明数据已经送完。
写入数据相关(伪)代码:
#define RDT_HEADER_FRAMEBEGIN_ADDR 0
#define RDT_HEADER_FILENAME_ADDR 4
#define RDT_HEADER_FILESIZE_ADDR 68
#define RDT_HEADER_FRAMESIZE_ADDR 72
#define RDT_HEADER_ENDFLAG_ADDR 76
#define RDT_FRAME_BUFFER_ADDR 77
#define RDT_FRAME_BUFFER_SIZE 10243
#define RDT_FRAME_HEADER_SIZE 77
#define RDT_PAYLOAD_WRITE_POSTION 77
#define RDT_FRAME_TAIL_SIZE 2
union typeXChange {
int int_data;
char char_data[4];
};
//把size写入buffer对应位置。
bool swapInt2CharBuffer(const int int_data, char* buffer, size_t bufferSize)
{
if (!buffer || bufferSize != 4)
return false;
typeXChange tmp;
tmp.int_data = int_data;
memcpy(buffer, tmp.char_data, 4);
return true;
}
//在buffer起始处写入header
void createPacketHeader(char* buffer_write_postion, size_t bufferSize,
char* fileName, size_t fileSize, size_t payloadSize)
{
memset(buffer_write_postion, 0, RDT_FRAME_HEADER_SIZE);
*buffer_write_postion = 'I';
*(buffer_write_postion + 1) = 'O';
*(buffer_write_postion + 2) = 'T';
*(buffer_write_postion + 3) = 'C';
strncpy(buffer_write_postion + RDT_HEADER_FILENAME_ADDR,
fileName, strlen(fileName));
swapInt2CharBuffer(fileSize,
buffer_write_postion + RDT_HEADER_FILESIZE_ADDR, 4);
swapInt2CharBuffer(payloadSize,
buffer_write_postion + RDT_HEADER_FRAMESIZE_ADDR, 4);
}
//在buffer结尾2个字节写入tail
void createPacketTail(char* buffer_write_postion, size_t bufferSize)
{
*buffer_write_postion = 'G';
*(buffer_write_postion + 1) = 'C';
}
//发送单个文件的伪代码
void sendOneFile2Client(int rdt_id, file f, bool isLastFile)
{
int ret = 0;
size_t fileSize = f.size();
string fileName = f.name();
size_t rdt_file_size = fileSize;
unsigned char rdt_endflag = 0;
int buffer_for_payload_size = RDT_FRAME_BUFFER_SIZE -
RDT_FRAME_HEADER_SIZE -
RDT_FRAME_TAIL_SIZE; //计算可以给数据最大的存储空间
f.open();
while (1) {
char buffer[RDT_FRAME_BUFFER_SIZE] = {0};
int readSize = file.read(buffer + RDT_PAYLOAD_WRITE_POSTION,
buffer_for_payload_size); //预留了77个字节给header
if (readSize < buffer_for_payload_size) { //此时表示已经无法读满一个完整的payload size,估计已经到了文件最后一包。
if (isLastFile) { //如果是最后一个文件,设定endflag为1。
rdt_endflag = 1;
}
if (readSize <= 0) {
readSize = 1;
break;
}
} else {
filesize -= readSize;
}
createPacketHeader(buffer, RDT_FRAME_HEADER_SIZE,
fileName, rdt_file_size, readSize);
createPacketTail(buffer + RDT_FRAME_HEADER_SIZE + readSize,
RDT_FRAME_TAIL_SIZE);
ret = RDT_Write(rdt_id, buffer, readSize);
if (ret < 0) {
goto LAB_SEND_RETURN_ERROR;
}
if (rdt_endflag) {
RDT_Flush(rdt_id);
break;
}
}
f.close();
LAB_SEND_RETURN_OK:
return 0;
LAB_SEND_RETURN_ERROR:
return ret;
}
//发送线程,伪代码
void sendFileList(void* arg)
{
//此处先创建rdt_id,具体参考本章节前面创建通道的方法
int rdt_id = createChannelForDownload(sid, iotc_channel_id);
if (rdt_id < 0) {
return;
}
//通道创建成功后,再进行文件传输。
int fileCount = fileList.count();
int fileIndex = 1;
int ret = 0;
foreach(file file, fileList) {
bool isLastFile = (fileCount == fileIndex);
ret = sendOneFile2Client(rdt_id, file, isLastFile);
if (ret < 0) {
printf("%s send error %d\n", filename, ret);
break;
}
}
//正常发送完,需要检查缓存区的数据是否有被送出去。
if (ret == 0) {
do {
st_RDT_Status status;
ret = RDT_Status_Check(rdt_id, &status);
if (ret < 0)
break; //异常
if (status.BufSizeInSendQueue == 0) {
break; //已经发送完最后一帧
} else {
msleep(100);
}
} while (1);
}
//关闭通道
destoryChannelOfDownload(rdt_id);
}APP端
接收端也需要按照对应的格式进行切包和解析数据,如果切包有误,则可能导致数据解析异常。
读取通道数据和解析方法(伪)代码:
//创建通道参考本章第四部分,此处假设已经创建成功RDT通道,拿到rdt_id
//读数据头
#define HEADER_SIZE 77
int readRDTHeader(int rdt_id, char* headerBuffer, int bufferSize)
{
if (rdt_id < 0 || !headerBuffer || bufferSize < HEADER_SIZE)
return -1;
memset(headerBuffer, 0, bufferSize);
int restSize = HEADER_SIZE;
int headerIndex = 0; //此处需要读取一个完整的header再去做解析
do {
int size = RDT_Read(headerBuffer + headerIndex, restSize, 1000);
if (size > 0) {
headerIndex += size;
restSize -= size;
} else if (size != RDT_ER_TIMEOUT) {
return size;
}
} while (restSize > 0);
return 0;
}
//读取对应长度的二进制文件
int readBinaryDataAndSaveOneFrame(file f, int rdt_id,
char* dataBuffer, int bufferSize,
int dataLength)
{
if (rdt_id < 0 || !dataBuffer || bufferSize < 0 || dataLength < 0)
return -1;
int restDataSize = dataLength;
int readSize = bufferSize < restDataSize ? bufferSize : restDataSize;
do {
int size = RDT_Read(dataBuffer, readSize, 1000);
if (size > 0) {
restDataSize -= size;
} else if (size != RDT_ER_TIMEOUT) {
return size;
}
//保存到本地文件
f.write(dataBuffer, size);
} while (restDataSize > 0);
return 0;
}
//读尾,2个字节,为"GC"
int readTail()
{
int restSize = 2;
char buffer[4] = {0};
int bufferIndex;
do {
int size = RDT_Read(dataBuffer + bufferIndex, restSize, 1000);
if (size > 0) {
restSize -= size;
bufferIndex += size;
} else if (size != RDT_ER_TIMEOUT) {
return size;
}
} while (restSize > 0);
if (buffer[0] != 'G' || buffer[1] != 'C') {
return -1;
} else {
return 0;
}
}
//接收数据伪代码
#define RDT_BUFFER_SIZE 20480
void recvFilesFromChannel(int rdt_id)
{
int endFlag = 0;
int ret = 0;
file f;
string fileName, lastFileName;
while (!endFlag) {
char buffer[RDT_BUFFER_SIZE] = {0};
if ((ret = readRDTHeader(rdt_id, buffer, RDT_BUFFER_SIZE)) < 0) {
break;
}
//此处进行数据头解析出fileName,dataLength,endFlag;
//帧头定义可以参考:https://share.note.youdao.com/s/JOFyOca5
if (!f.isOpen()) {
f.open(fileName);
lastFileName = fileName;
} else {
//关闭上个文件,打开一个新文件;
if (fileName != lastFileName) {
f.close();
f.open(fileName);
lastFileName = fileName;
}
}
if ((ret = readBinaryDataAndSaveOneFrame(f, rdt_id, buffer,
RDT_BUFFER_SIZE, dataLength)) < 0) {
break;
}
if ((ret = readTail()) < 0) {
break;
}
}
if (f.isOpen) {
f.close();
}
}
//接收线程
void createRDTChannelAndSaveFiles(void* arg)
{
//创建RDT通道,iotcChannelId来源于IOCTRL_FILEMANAGER_FILE_DOWNLOAD_RESP拿到的channel
int rdt_id = createChannelForDownload(sid, iotc_channel_id);
if (rdt_id < 0) {
return;
}
//接收数据
recvFilesFromChannel(rdt_id);
//关闭通道
destoryChannelOfDownload(rdt_id);
}结束标志的判断
在文件传输过程中,结束标志的判断非常重要。根据前面的代码实现,结束标志的处理主要体现在以下几个方面:
- 包尾标识:每个数据包的结尾都有"GC"标识,用于确认数据包的完整性
- 文件结束标志:当传输最后一个文件时,会设置endflag为1
- 缓存区检查:发送完成后,通过RDT_Status_Check检查发送缓存区是否为空
- 读取大小判断:当读取的数据大小小于缓冲区大小时,认为是文件的最后一包
这些机制共同确保了文件传输的可靠性和完整性,避免了数据丢失或传输不完整的问题。
