基于RDTAPIs的文件下载
主要内容:
1.IO交互
2.通道的创建和销毁
3.数据的传输
4.结束标志 的判断
IO交互:

1.APP会通过avSendIOCtrl(IOCTRL_FILEMANAGER_FILE_LIST_REQ)查询指定时间内的文件。设备通过avSendIOCtrl(IOCTRL_FILEMANAGER_FILE_LIST_RESP)回复对应的文件列表。
2.当用户选中部分文件下载的时候,则APP端会avSendIOCtrl(IOCTRL_FILEMANAGER_FILE_DOWNLOAD_REQ)告知设备要下载哪些文件。设备则根据自身的硬件和软件资源,可以自定义使用的通道数,通过avSendIOCtrl(IOCTRL_FILEMANAGER_FILE_DOWNLOAD_RESP)告知APP使用哪些通道以及使用哪种APIs来进行数据传输。
3.APP和设备端分别创建对应的通道。
4.数据传输。
5.数据传输后关闭通道。
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,所以存在粘包的情况,需要另外设计切包的机制。每个数据包分为包头、数据和包尾,具体参考:https://share.note.youdao.com/s/JOFyOca5
设备端
说明: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); //预留了777个字节给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 || buffSize < 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 || buffSize < 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);
}