主要内容:
- 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使用哪些通道以及使用哪种APIs来进行数据传输。
- APP和设备端分别创建对应的通道。
- 数据传输。
- 数据传输后关闭通道。
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://note.youdao.com/s/JOFyOca5
- 设备端
说明:RDT的数据传输是可靠的传输,每次调用RDT_Write,都将数据写入本地的发送缓存区,所以等所有的数据都使用RDT_Write写入后,需要检查发送缓存区是否为空,为空,则说明数据已经送完。
写入数据相关(伪)代码:
- APP端
#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); }
接收端也需要按照对应的格式也切包和解析数据,如果切包有误,则可能导致数据解析异常。
读取通道数据和解析方法(伪)代码:
//创建通道参考本章第四部分,此处假设已经创建成功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://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); }