简体中文

基于AVAPIs的文件下载

基于AVAPIs的文件下载流程 | TUTK P2P SDK 开发指南

[开发指南] 基于AVAPIs的文件下载流程

Version: v3.3+ | 适用场景: AVAPIs文件下载(设备→APP) | 核心模块: IO交互/通道管理/数据传输

基于AVAPIs的文件下载.png

图 1:基于AVAPIs的文件下载整体流程示意图

一、IO交互流程

文件下载的核心交互通过 avSendIOCtrl/avRecvIOCtrl 接口完成,分为文件列表查询、下载请求、响应三个关键阶段,最终触发通道创建和数据传输。
  • 阶段1:文件列表查询
    • APP 调用 avSendIOCtrl(IOCTRL_FILEMANAGER_FILE_LIST_REQ),传入查询条件(时间范围、文件类型等,对应 stFileListReq 结构体);
    • 设备端通过 avRecvIOCtrl 接收请求,查询符合条件的文件列表,通过 avSendIOCtrl(IOCTRL_FILEMANAGER_FILE_LIST_RESP) 回复(对应 stFileListResp 结构体)。
  • 阶段2:下载请求发起
    • 用户在APP端选中待下载文件,APP 调用 avSendIOCtrl(IOCTRL_FILEMANAGER_FILE_DOWNLOAD_REQ),传入待下载文件列表(对应 stFileDownloadReq 结构体)。
  • 阶段3:下载响应确认
    • 设备端接收下载请求后,根据硬件性能(如CPU、带宽)和软件资源,自定义分配传输通道数;
    • 设备端通过 avSendIOCtrl(IOCTRL_FILEMANAGER_FILE_DOWNLOAD_RESP) 回复APP,包含分配的 IOTC通道ID 和传输协议类型(此处为AVAPIs),对应 stFileDownloadResp 结构体。
  • 阶段4:通道创建与数据传输
    • APP 和设备端根据响应中的 IOTC通道ID,分别创建对应的AV通道;
    • 通道创建成功后,设备端开始发送文件数据,APP端接收并存储数据。
  • 阶段5:通道销毁
    • 所有文件下载完成(检测到 endFlag=1),APP 和设备端分别销毁AV通道,释放资源。

二、AV通道的创建和销毁

通道创建需基于已建立的P2P会话(SID)和分配的IOTC通道ID,APP端与设备端的创建逻辑对称,且必须开启 resend=1(重传模式)确保数据可靠传输。

2.1 APP端(客户端)通道操作

APP端作为文件接收方,通过 avClientStartEx 创建AV客户端通道,avClientStop 销毁通道。
int createDataChannelOfClientForDownload(int sid,int iotc_channel_id)
{
    AVClientStartInConfig in;
    AVClientStartOutConfig out;

    clearMemory(&in,sizeof(in));
    clearMemory(&out,sizeof(out));

    in.cb = sizeof(in);
    in.iotc_session_id = sid;          // P2P会话ID(已建立的会话)
    in.iotc_channel_id = iotc_channel_id; // 设备分配的IOTC通道ID
    in.timeout_sec = 30;               // 连接超时时间(30秒)
    in.resend = 1; //此处务必要开启resend模式(确保数据可靠传输)
    in.security_mode = AV_SECURITY_AUTO; // 自动选择安全模式

    if(isSDKV4){
        in.auth_type = AV_AUTH_NEBULA;  // SDK v4+ 认证类型(NEBULA)
        in.account_or_identity = "";    // 身份标识(v4+ 无需填写)
        in.password_or_token = "";      // 令牌(v4+ 无需填写)
    } else {
        in.auth_type = AV_AUTH_PASSWORD;// SDK v3 认证类型(密码认证)
        in.account_or_identity = "camera account"; // 设备账号
        in.password_or_token = "camera password";  // 设备密码
    }

    in.sync_recv_data = 0; // 0=异步接收数据(推荐)
    in.dtls_cipher_suites = nullptr; // DTLS加密套件(默认NULL)

    out.cb = sizeof(out);

    return avClientStartEx(&in,&out); // 创建通道,返回AV通道索引(avIndex)
}
关键说明:resend必须设为1,否则丢包时无法重传导致文件损坏;SDK版本需区分认证类型,避免认证失败。
void destoryDataChannelOfClient(int avIndex)
{
    if(avIndex < 0)
        return ;
        
    avClientStop(avIndex); // 销毁AV通道,释放资源
}

2.2 设备端(服务端)通道操作

设备端作为文件发送方,通过 avServStartEx 创建AV服务端通道,avServStop 销毁通道。
int createDataChannelOfDeviceForDownload(int sid,int iotc_channel_id)
{
    AVServStartInConfig avStartInConfig;
    AVServStartOutConfig avStartOutConfig;
    
    clearMemory(&avStartInConfig,sizeof(AVServStartInConfig));
    clearMemory(&avStartOutConfig,sizeof(avStartOutConfig));

    avStartInConfig.cb               = sizeof(AVServStartInConfig);
    avStartInConfig.iotc_session_id  = sid; // P2P会话ID
    avStartInConfig.iotc_channel_id  = iotc_channel_id; // 分配的IOTC通道ID
    avStartInConfig.timeout_sec      = 30; // 超时时间(30秒)
    avStartInConfig.password_auth    = ExPasswordAuthCallBackFn; // 密码认证回调(v3版本用)
    avStartInConfig.server_type      = 0; // 服务端类型(默认0)
    avStartInConfig.resend           = 1; // 必须开启重传模式
    avStartInConfig.security_mode = AV_SECURITY_DTLS; // 启用DTLS加密(安全传输)
    //avStartInConfig.json_request = ExJsonRequest; // JSON请求回调(可选)
    
    avStartOutConfig.cb = sizeof(AVServStartOutConfig);
    return avServStartEx(&avStartInConfig, &avStartOutConfig); // 创建通道,返回AV通道索引
}
关键说明:security_mode设为AV_SECURITY_DTLS启用加密传输,保护文件数据安全;password_auth回调用于v3版本的密码校验。
void destoryDataChannelOfDevice(int avIndex)
{
    if(avIndex < 0)
        return ;

    avServStop(avIndex); // 销毁AV通道,释放资源
}

三、数据传输流程

数据传输依赖 avSendFrameData(设备端发送)和 avRecvFrameData2(APP端接收)接口,文件信息通过 FRAMEINFO_FOR_UPLOAD_DOWNLOAD_t 结构体携带(定义参考《文件传输通用定义》),传输单元为“文件信息+二进制数据”。

3.1 设备端(发送方)数据发送

设备端按文件分帧读取二进制数据,搭配文件信息帧头发送,支持多文件连续传输,处理丢包重传(错误码-20006)。
#define MAX_BUFFER_SIZE 10243 // 单次发送最大缓冲区大小(可根据实际调整)

// 从文件读取二进制数据(工具函数)
int readBinaryDataFromFile(file f,char* buffer,size_t bufferSize)
{
    return f.read(buffer,bufferSize);
}

// 发送文件列表(核心函数)
void sendFileList(void* arg)
{
    int avIndex = createDataChannelOfDeviceForDownload(sid,iotc_channel_id);
    if(avIndex < 0){
        printf("createDataChannelOfDeviceForDownload failed,error code:%d\n",avIndex);
        return ;
    }
    
    foreach(auto f,files){ // 遍历待发送文件列表
        // 打开文件
        f.open();
        // 初始化文件信息帧头
        FRAMEINFO_FOR_UPLOAD_DOWNLOAD_t frmInfo = {0};
        strcpy(frmInfo.fileName, f.name().c_str()); // 文件名(含扩展名)
        frmInfo.fileSize = f.size(); // 文件总大小(字节)
        
        while(1){
            char buffer[MAX_BUFFER_SIZE] = {0};
            // 读取文件数据
            int readSize = readBinaryDataFromFile(f,buffer,MAX_BUFFER_SIZE);
            if(readSize < MAX_BUFFER_SIZE){
                if(readSize <= 0)
                    break; // 读取完毕,退出循环
                // 若为文件列表中最后一个文件,设置结束标志
                if(f.isLastFileOfList()){
                    frmInfo.endFlag = 1;                                      
                }
            } 
            
            frmInfo.frmSize = readSize; // 当前帧数据大小
            int ret = 0;
            
            // 发送数据:遇-20006(丢包)则重传
            do{
                ret = avSendFrameData(
                    avIndex,                  // AV通道索引
                    buffer,                   // 二进制数据缓冲区
                    readSize,                 // 数据长度
                    (const void*)&frmInfo,    // 文件信息帧头
                    sizeof(FRAMEINFO_FOR_UPLOAD_DOWNLOAD_t) // 帧头长度
                );
                if(ret < 0){
                    if(ret == -20006){ // 丢包错误,等待后重传
                        msleep(20);
                    } else { // 其他错误,退出发送
                        break;
                    }
                }
            } while(ret == -20006);

            // 异常退出:关闭文件并销毁通道
            if(ret < 0){
                printf("avSendFrameData error :%d\n",ret);
                f.close();
                goto LAB_END;
            }
            
            // 最后一帧发送完成,退出当前文件循环
            if(frmInfo.endFlag){
                break;
            }
        }
        f.close(); // 关闭当前文件
    }

    // 等待重传缓冲区清空(避免数据残留)
    int i_count = 0;
    while(i_count++ < 10*300){ // 最多等待30秒
        float userate = avResendBufUsageRate(avIndex); // 获取重传缓冲区使用率
        if(userate > 0.0){
            msleep(100);
        } else {
            break; // 缓冲区为空,可安全销毁通道
        }
    }
  
LAB_END:
    destoryDataChannelOfDevice(avIndex); // 销毁通道
}

3.2 APP端(接收方)数据接收

APP端循环接收数据,解析帧头信息,处理文件切换(新文件打开/旧文件关闭),验证结束标志,完成后关闭文件和通道。
#define MAX_BUFFER_SIZE 300*1024 // 接收缓冲区大小(建议大于发送端缓冲区)

void recvDataAndSaveToLocalFile(void *arg)
{
    // 创建接收通道
    int avIndex = createDataChannelOfClientForDownload(sid,iotc_channel_id);
    if(avIndex < 0){
        printf("createDataChannelOfClientForDownload failed\n");
        return;
    }

    char buffer[MAX_BUFFER_SIZE];
    QString fileName;
    uint fileSize;
    uint dataSize;
    int endFlag;
    FRAMEINFO_FOR_UPLOAD_DOWNLOAD_t frmInfo;
    int tmpInt1, tmpInt2, tmpInt3, tmpInt4;
    uint frmNo;
    QFile f; // 本地文件对象(Qt示例,其他平台可替换为对应文件操作)
    
    // 循环读取数据
    while(1){
        int ret = avRecvFrameData2(
            avIndex,                  // AV通道索引
            buffer,                   // 接收缓冲区
            MAX_BUFFER_SIZE,          // 缓冲区最大长度
            &tmpInt1,                 // 预留参数(忽略)
            &tmpInt2,                 // 预留参数(忽略)
            (char*)&frmInfo,          // 接收文件信息帧头
            sizeof(FRAMEINFO_FOR_UPLOAD_DOWNLOAD_t), // 帧头长度
            &tmpInt3,                 // 预留参数(忽略)
            &frmNo                    // 帧序号(忽略)
        );
        
        if(ret < 0){
            if(ret == AV_ER_DATA_NOREADY){ // 暂无数据,短暂等待
                msleep(5);
                continue;
            } else if(ret == AV_ER_INCOMPLETE_FRAME || ret == AV_ER_LOSED_THIS_FRAME){
                printf("lost data, frameNo:%d\n", frmNo); // 帧不完整/丢失,继续接收
                continue;
            } else { // 其他错误(如通道断开),退出循环
                printf("avRecvFrameData2 return error:%d\n",ret);
                break;
            }
        }
        
        // 处理文件切换:未打开文件→打开新文件;文件名变化→关闭旧文件,打开新文件
        if(!f.isOpen()){
            fileName = frmInfo.fileName;
            f.setFileName(fileName);
            if(!f.open(QIODevice::WriteOnly)){ // 以只写模式打开文件
                printf("open file failed:%s\n", fileName.toUtf8().data());
                break;
            }
        } else {
            if(fileName != frmInfo.fileName){ // 检测到新文件
                f.close(); // 关闭旧文件
                fileName = frmInfo.fileName;
                f.setFileName(fileName);
                if(!f.open(QIODevice::WriteOnly)){
                    printf("open file failed:%s\n", fileName.toUtf8().data());
                    break;
                }
            }
        }
        
        // 写入本地文件(ret为实际接收的数据长度)
        f.write(buffer, ret);
        
        // 检测结束标志:最后一个文件的最后一包,退出循环
        if(frmInfo.endFlag){
            printf("file download complete, fileName:%s\n", fileName.toUtf8().data());
            break;
        }
    }
  
    // 关闭本地文件
    if(f.isOpen()){
        f.close();
    }
  
    // 销毁通道,释放资源
    destoryDataChannelOfClient(avIndex);
}

四、结束标志的判断

结束标志通过 FRAMEINFO_FOR_UPLOAD_DOWNLOAD_t 结构体的 endFlag 字段判断,规则如下:
  • endFlag = 0:非最后一个文件的数据包,或最后一个文件的非最后一包数据;
  • endFlag = 1:仅当所有待下载文件中,最后一个文件的最后一包数据 才设为1,用于标识整个下载流程结束。
注意:多文件连续传输时,前序文件的最后一包 endFlag 仍为0,仅最后一个文件的最后一包设为1,避免APP端误判提前关闭通道。

关键注意事项

1. 重传模式强制开启:resend=1 是文件传输可靠性的核心,未开启会导致丢包后文件损坏,不可省略;

2. 通道创建顺序:必须先完成IO交互(设备返回IOTC通道ID),再创建AV通道,避免通道绑定失败;

3. 缓冲区大小规划:发送端与接收端的缓冲区大小需匹配(接收端建议更大),避免数据溢出或接收不完整;

4. 错误处理:重点处理 -20006(丢包重传)、AV_ER_DATA_NOREADY(暂无数据)等常见错误,提升稳定性;

5. 资源释放:下载完成或异常退出时,需确保“文件关闭→通道销毁”的顺序,避免文件占用或资源泄漏;

6. 版本兼容性:SDK v3与v4的认证类型不同(AV_AUTH_PASSWORD vs AV_AUTH_NEBULA),需根据实际版本适配。

即刻开启您的物联网之旅

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

+86 755 27702549

7×24小时服务热线

法律声明 隐私权条款

关注“TUTK”

TUTK服务尽在掌握

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

TUTK服务尽在掌握

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

返回顶部