简体中文

基于RDTAPIs的文件下载

基于RDT APIs的文件下载

主要内容

  1. IO交互
  2. 通道的创建和销毁
  3. 数据的传输
  4. 结束标志的判断

IO交互

基于RDT APIs的文件下载流程图

文件下载过程中的IO交互主要分为以下几个步骤:

  1. APP通过 avSendIOCtrl(IOCTRL_FILEMANAGER_FILE_LIST_REQ) 查询指定时间内的文件列表
  2. 设备通过 avSendIOCtrl(IOCTRL_FILEMANAGER_FILE_LIST_RESP) 回复文件列表
  3. 用户选择文件后,APP通过 avSendIOCtrl(IOCTRL_FILEMANAGER_FILE_DOWNLOAD_REQ) 通知设备
  4. 设备根据资源情况,通过 avSendIOCtrl(IOCTRL_FILEMANAGER_FILE_DOWNLOAD_RESP) 告知APP使用的通道数和API类型
  5. 双方创建通道并进行数据传输
  6. 传输完成后关闭通道

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);
}

结束标志的判断

在文件传输过程中,结束标志的判断非常重要。根据前面的代码实现,结束标志的处理主要体现在以下几个方面:

  1. 包尾标识:每个数据包的结尾都有"GC"标识,用于确认数据包的完整性
  2. 文件结束标志:当传输最后一个文件时,会设置endflag为1
  3. 缓存区检查:发送完成后,通过RDT_Status_Check检查发送缓存区是否为空
  4. 读取大小判断:当读取的数据大小小于缓冲区大小时,认为是文件的最后一包

这些机制共同确保了文件传输的可靠性和完整性,避免了数据丢失或传输不完整的问题。


即刻开启您的物联网之旅

联系解决方案专家
Kalay App
资讯安全白皮书
全球专利布局
解决方案
新闻动态
公司动态
行业资讯
媒体报道
永续发展
经营者的话
社会参与
环境永续
公司治理

+86 755 27702549

7×24小时服务热线

法律声明 隐私权条款

关注“TUTK”

TUTK服务尽在掌握

© 2022 物联智慧科技(深圳)有限公司版权所有粤ICP备14023641号
在线咨询
扫一扫

TUTK服务尽在掌握

全国免费服务热线
+86 755 27702549

返回顶部