OSS提供的分片上传(Multipart Upload)功能,将要上传的较大文件(Object)分成多个数据块(Part)来分别上传,上传完成后再调用CompleteMultipartUpload接口将这些Part组合成一个Object来达到断点续传的效果。

分片上传流程

分片上传(Multipart Upload)分为以下三个步骤:

  1. 初始化一个分片上传事件。

    调用InitiateMultipartUpload方法返回OSS创建的全局唯一的uploadId。

  2. 上传分片。

    调用UploadPart方法上传分片数据。

    说明
    • 对于同一个UploadId,分片号(PartNumber)标识了该分片在整个文件内的相对位置。如果使用同一个分片号上传了新的数据,那么OSS上这个分片已有的数据将会被覆盖。
    • OSS将收到的分片数据的MD5值放在ETag头内返回给用户。
    • OSS计算上传数据的MD5值,并与SDK计算的MD5值比较,如果不一致则返回InvalidDigest错误码。
  3. 完成分片上传。

    所有分片上传完成后,调用CompleteMultipartUpload方法将所有分片合并成完整的文件。

分片上传完整示例

以下通过一个完整的示例对分片上传的流程进行逐步解析:

#include <alibabacloud/oss/OssClient.h>

int64_t getFileSize(const std::string& file)
{
    std::fstream f(file, std::ios::in | std::ios::binary);
    f.seekg(0, f.end);
    int64_t size = f.tellg();
    f.close();
    return size;
}

using namespace AlibabaCloud::OSS;

int main(void)
{
    /* 初始化OSS账号信息 */
    std::string AccessKeyId = "yourAccessKeyId";
    std::string AccessKeySecret = "yourAccessKeySecret";
    std::string Endpoint = "yourEndpoint";
    std::string BucketName = "yourBucketName";
    std::string ObjectName = "yourObjectName";

    /* 初始化网络等资源 */
    InitializeSdk();

    ClientConfiguration conf;
    OssClient client(Endpoint, AccessKeyId, AccessKeySecret, conf);
    InitiateMultipartUploadRequest initUploadRequest(BucketName, ObjectName);
    /*(可选)请参见如下示例设置存储类型 */
    //initUploadRequest.MetaData().addHeader("x-oss-storage-class", "Standard");

    /* 初始化分片上传事件 */
    auto uploadIdResult = client.InitiateMultipartUpload(initUploadRequest);
    auto uploadId = uploadIdResult.result().UploadId();
    std::string fileToUpload = "yourLocalFilename";
    int64_t partSize = 100 * 1024;
    PartList partETagList;
    auto fileSize = getFileSize(fileToUpload);
    int partCount = static_cast<int>(fileSize / partSize);
    /* 计算分片个数 */
    if (fileSize % partSize != 0) {
        partCount++;
    }

    /* 对每一个分片进行上传 */
    for (int i = 1; i <= partCount; i++) {
        auto skipBytes = partSize * (i - 1);
        auto size = (partSize < fileSize - skipBytes) ? partSize : (fileSize - skipBytes);
        std::shared_ptr<std::iostream> content = std::make_shared<std::fstream>(fileToUpload, std::ios::in|std::ios::binary);
        content->seekg(skipBytes, std::ios::beg);

        UploadPartRequest uploadPartRequest(BucketName, ObjectName, content);
        uploadPartRequest.setContentLength(size);
        uploadPartRequest.setUploadId(uploadId);
        uploadPartRequest.setPartNumber(i);
        auto uploadPartOutcome = client.UploadPart(uploadPartRequest);
        if (uploadPartOutcome.isSuccess()) {
            Part part(i, uploadPartOutcome.result().ETag());
            partETagList.push_back(part);
        }
        else {
            std::cout << "uploadPart fail" <<
            ",code:" << uploadPartOutcome.error().Code() <<
            ",message:" << uploadPartOutcome.error().Message() <<
            ",requestId:" << uploadPartOutcome.error().RequestId() << std::endl;
        }

    }

    /* 完成分片上传 */
    CompleteMultipartUploadRequest request(BucketName, ObjectName);
    request.setUploadId(uploadId);
    request.setPartList(partETagList);
    /*(可选)请参见如下示例设置读写权限ACL */
    //request.setAcl(CannedAccessControlList::Private);

    auto outcome = client.CompleteMultipartUpload(request);

    if (!outcome.isSuccess()) {
        /* 异常处理 */
        std::cout << "CompleteMultipartUpload fail" <<
        ",code:" << outcome.error().Code() <<
        ",message:" << outcome.error().Message() <<
        ",requestId:" << outcome.error().RequestId() << std::endl;
        ShutdownSdk();
        return -1;
    }

    /* 释放网络等资源 */
    ShutdownSdk();
    return 0;
}

获取已经上传的分片过程中调用CompleteMultipartUpload接口时,需提供每个分片的ETag值。您可以通过以下两种方式获取ETag值:

  • 上传每个分片时,返回结果中会包含这个分片的ETag值,供您直接保存使用。上述示例采用的是此种方式。
  • 通过调用ListParts方法获取已经上传的分片的ETag值。

列举已上传的分片

调用ListParts方法列举出指定UploadID下所有已上传成功的分片。
  • 列举所有已上传的分片
    说明 默认情况下,ListParts只能一次列举1000个分片。当分片数量大于1000时,请分页列举所有已上传分片。
    以下代码用于列举所有已上传的分片:
    #include <alibabacloud/oss/OssClient.h>
    using namespace AlibabaCloud::OSS;
    
    int main(void)
    {
        /* 初始化OSS账号信息 */
        std::string AccessKeyId = "yourAccessKeyId";
        std::string AccessKeySecret = "yourAccessKeySecret";
        std::string Endpoint = "yourEndpoint";
        std::string BucketName = "yourBucketName";
        std::string ObjectName = "yourObjectName";
    
        /* 初始化网络等资源 */
        InitializeSdk();
    
        ClientConfiguration conf;
        OssClient client(Endpoint, AccessKeyId, AccessKeySecret, conf);
    
        /* 列举已上传分片,默认列举1000个分片 */
        ListPartsRequest listuploadrequest(BucketName, ObjectName);
        listuploadrequest.setUploadId(uploadId);
        do {
            auto listuploadresult = client.ListParts(listuploadrequest);
            if (!listUploadResult.isSuccess()) {
                /* 异常处理 */
                std::cout << "ListParts fail" <<
                ",code:" << listuploadresult.error().Code() <<
                ",message:" << listuploadresult.error().Message() <<
                ",requestId:" << listuploadresult.error().RequestId() << std::endl;
                break;
            }
            else {
                for (const auto& part : listuploadresult.result().PartList()) {
                    std::cout << "part"<<
                    ",name:" << part.PartNumber() <<
                    ",size:" << part.Size() <<
                    ",etag:" << part.ETag() <<
                    ",lastmodify time:" << part.LastModified() << std::endl;
                }
            }
            listuploadrequest.setPartNumberMarker(listuploadresult.result().NextPartNumberMarker());
        } while (listuploadresult.result().IsTruncated());
    
        /* 释放网络等资源 */
        ShutdownSdk();
        return 0;
    }
  • 分页列举所有已上传的分片
    以下代码用于指定每页分片的数量,并分页列举所有分片:
    #include <alibabacloud/oss/OssClient.h>
    using namespace AlibabaCloud::OSS;
    
    int main(void)
    {
        /* 初始化OSS账号信息 */
        std::string AccessKeyId = "yourAccessKeyId";
        std::string AccessKeySecret = "yourAccessKeySecret";
        std::string Endpoint = "yourEndpoint";
        std::string BucketName = "yourBucketName";
        std::string ObjectName = "yourObjectName";
    
        /* 初始化网络等资源 */
        InitializeSdk();
    
        ClientConfiguration conf;
        OssClient client(Endpoint, AccessKeyId, AccessKeySecret, conf);
    
        /* 分页列举全部上传的分片 */
        /* 设置每页列举最大上传分片数目 */
        ListPartsRequest listuploadrequest(BucketName, ObjectName);
        listuploadrequest.setMaxParts(50);
        listuploadrequest.setUploadId(uploadId);
        do {
            listuploadresult = client.ListParts(listuploadrequest);
            if (!listUploadResult.isSuccess()) {
                /* 异常处理 */
                std::cout << "ListParts fail" <<
                ",code:" << listuploadresult.error().Code() <<
                ",message:" << listuploadresult.error().Message() <<
                ",requestId:" << listuploadresult.error().RequestId() << std::endl;
                break;
            }
            else {
                for (const auto& part : listuploadresult.result().PartList()) {
                    std::cout << "part"<<
                    ",name:" << part.PartNumber() <<
                    ",size:" << part.Size() <<
                    ",etag:" << part.ETag() <<
                    ",lastmodify time:" << part.LastModified() << std::endl;
                }
            }  
            listuploadrequest.setPartNumberMarker(listuploadresult.result().NextPartNumberMarker());     
        } while (listuploadresult.result().IsTruncated());
    
        /* 释放网络等资源 */
        ShutdownSdk();
        return 0;
    }

列举分片上传事件

调用ListMultipartUploads方法列举出所有执行中的分片上传事件,即已初始化但尚未完成或已取消的分片上传事件。

  • 列举全部分片上传事件
    说明 默认情况下,ListMultipartUploads只能一次列举1000个分片上传事件。当分片数量大于1000时,请分页列举所有上传事件。

    以下代码用于列举所有已上传事件:

    #include <alibabacloud/oss/OssClient.h>
    using namespace AlibabaCloud::OSS;
    
    int main(void)
    {
        /* 初始化OSS账号信息 */
        std::string AccessKeyId = "yourAccessKeyId";
        std::string AccessKeySecret = "yourAccessKeySecret";
        std::string Endpoint = "yourEndpoint";
        std::string BucketName = "yourBucketName";
    
        /* 初始化网络等资源 */
        InitializeSdk();
    
        ClientConfiguration conf;
        OssClient client(Endpoint, AccessKeyId, AccessKeySecret, conf);
    
        /* 列举已上传事件,默认列举1000个分片 */
        ListMultipartUploadsRequest listmultiuploadrequest(BucketName);
        do {
            auto listresult = client.ListMultipartUploads(listmultiuploadrequest);
            if (!listresult.isSuccess()) {
                /* 异常处理 */
                std::cout << "ListMultipartUploads fail" <<
                ",code:" << listresult.error().Code() <<
                ",message:" << listresult.error().Message() <<
                ",requestId:" << listresult.error().RequestId() << std::endl;
                break;
            }
            else {
                for (const auto& part : listresult.result().MultipartUploadList()) {
                    std::cout << "part"<<
                    ",name:" << part.Key <<
                    ",uploadid:" << part.UploadId <<
                    ",initiated time:" << part.Initiated << std::endl;
                }
            }
            listmultiuploadrequest.setKeyMarker(listresult.result().NextKeyMarker()); 
            listmultiuploadrequest.setUploadIdMarker(listresult.result().NextUploadIdMarker()); 
        } while (listresult.result().IsTruncated());
    
        /* 释放网络等资源 */
        ShutdownSdk();
        return 0;
    }
  • 分页列举所有上传事件
    以下代码用于分页列举所有上传事件:
    #include <alibabacloud/oss/OssClient.h>
    using namespace AlibabaCloud::OSS;
    
    int main(void)
    {
        /* 初始化OSS账号信息 */
        std::string AccessKeyId = "yourAccessKeyId";
        std::string AccessKeySecret = "yourAccessKeySecret";
        std::string Endpoint = "yourEndpoint";
        std::string BucketName = "yourBucketName";
    
        /* 初始化网络等资源 */
        InitializeSdk();
    
        ClientConfiguration conf;
        OssClient client(Endpoint, AccessKeyId, AccessKeySecret, conf);
    
        /* 分页列举全部上传事件 */
        /* 设置每页列举最大上传事件数目 */
        ListMultipartUploadsRequest  listmultiuploadrequest(BucketName);
        listmultiuploadrequest.setMaxUploads(50);
        do {
            listresult = client.ListMultipartUploads(listmultiuploadrequest);
            if (!listresult.isSuccess()) {
                /* 异常处理 */
                std::cout << "ListMultipartUploads fail" <<
                ",code:" << listresult.error().Code() <<
                ",message:" << listresult.error().Message() <<
                ",requestId:" << listresult.error().RequestId() << std::endl;
                break;
            }
            else {
                for (const auto& part : listresult.result().MultipartUploadList()) {
                    std::cout << "part"<<
                    ",name:" << part.Key <<
                    ",uploadid:" << part.UploadId <<
                    ",initiated time:" << part.Initiated << std::endl;
                }
            }  
            listmultiuploadrequest.setKeyMarker(listresult.result().NextKeyMarker()); 
            listmultiuploadrequest.setUploadIdMarker(listresult.result().NextUploadIdMarker()); 
        } while (listresult.result().IsTruncated());
    
        /* 释放网络等资源 */
        ShutdownSdk();
        return 0;
    }

取消分片上传事件

您可以调用client.AbortMultipartUpload方法来取消分片上传事件。当一个分片上传事件被取消后,无法再使用同一个uploadId执行任何操作,已经上传的分片数据会被删除。

以下代码用于取消分片上传事件:
#include <alibabacloud/oss/OssClient.h>
using namespace AlibabaCloud::OSS;

int main(void)
{
    /* 初始化OSS账号信息 */
    std::string AccessKeyId = "yourAccessKeyId";
    std::string AccessKeySecret = "yourAccessKeySecret";
    std::string Endpoint = "yourEndpoint";
    std::string BucketName = "yourBucketName";
    std::string ObjectName = "yourObjectName";

    /* 初始化网络等资源 */
    InitializeSdk();

    ClientConfiguration conf;
    OssClient client(Endpoint, AccessKeyId, AccessKeySecret, conf);
    InitiateMultipartUploadRequest initUploadRequest(BucketName, ObjectName);

    /* 初始化分片上传事件 */
    auto uploadIdResult = client.InitiateMultipartUpload(initUploadRequest);
    auto uploadId = uploadIdResult.result().UploadId();

    /* 取消分片上传 */
    AbortMultipartUploadRequest  abortUploadRequest(BucketName, ObjectName, uploadId);
    auto abortUploadIdResult = client.AbortMultipartUpload(abortUploadRequest);

    if (!abortUploadIdResult.isSuccess()) {
        /* 异常处理 */
        std::cout << "AbortMultipartUpload fail" <<
        ",code:" << outcome.error().Code() <<
        ",message:" << outcome.error().Message() <<
        ",requestId:" << outcome.error().RequestId() << std::endl;
        ShutdownSdk();
        return -1;
    }

    /* 释放网络等资源 */
    ShutdownSdk();
    return 0;
}