65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Microsoft Azure 存储客户端库 (C++)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (9投票s)

2014 年 6 月 17 日

CPOL

7分钟阅读

viewsIcon

28795

这是 Microsoft Azure 存储客户端库 (C++) 的概述, 附带基本代码片段

概述

Microsoft Azure 存储 C++ 客户端库是构建在 C++ REST SDK 之上的一个库,允许您从 C++ 应用程序访问 Azure 存储。您需要通过 NuGet 安装它。在安装过程中,我无法在 Visual Studio 2013 的 NuGet 包 UI 中找到它。我必须通过程序包管理器控制台安装它。我猜测这是因为该库是预发行版本。最新版本是 0.3.0(预览版),发布日期为 2014 年 5 月 16 日。因此,在安装时,您需要添加 -pre 参数。

install-package wastorage -pre

这将把 REST SDK 和其他依赖项(如 wastorage.redist)添加到您的项目中。

访问 Azure 表

为了测试它是否有效,我使用一个快速搭建的 C# 项目创建了一个表,然后从 C++ 控制台应用程序访问它。我只使用了 Azure 存储模拟器。这是您需要在项目中包含的头文件。

#include "../packages/wastorage.0.3.0-preview/build/native/include/was/storage_account.h"
#include "../packages/wastorage.0.3.0-preview/build/native/include/was/table.h"

您还可以添加这个 using namespace 声明以节省一些击键次数。

using namespace azure::storage;
这是代码片段,我已添加注释以便于理解。
// This returns the storage account object
auto storage_account = cloud_storage_account::parse(U("UseDevelopmentStorage=true"));

// This returns the table client service object
auto table_client = storage_account.create_cloud_table_client();

// Now, we get a reference to a named table
auto table = table_client.get_table_reference(U("Clients"));

// This will create the table if it does not exist
bool created = table.create_if_not_exists();

// We get everything without a filter
table_query query;
auto results = table.execute_query(query);

// Each item represents a table entity
for (auto item : results)
{
  auto properties = item.properties();

  // Each property from the table entity is returned as a key-value pair
  for (auto property : properties)
  {
    ucout << property.first << U(" = ") << property.second.str() << U("\t");
  }

  ucout << endl;
}

示例输出

Id = 100        Name = John Brown       Phone = 777-1234
Id = 101        Name = Mary Jane        Phone = 777-5678

这基本上和使用 C# 一样简单。一个不同之处(而且是一个重要的不同之处)是,对于 C#,您可以将返回的 table_entity 映射到 .NET 类型。所以我可以写成这样:

TableQuery<client>query = new TableQuery<client>(); 

foreach (var item in table.ExecuteQuery(query))
{ 
    Console.WriteLine("{0} - {1}", item.Name, item.Phone);
}

在这里,Client 是一个实体对象。

class Client : TableEntity
{
    public Client(int id)
    {
        this.Id = id;
        this.PartitionKey = id.ToString();
        this.RowKey = id.ToString();
    }

    public Client()
    {
    }

    public string Name { get; set; }
    public string Phone { get; set; }
    public int Id { get; set; }
}

我在这里猜测,但很可能是通过反射实现的。对于 C++,您需要编写一些辅助代码将返回的数据映射到您的 C++ 对象。这其实不是什么大问题,更多的是编码便利性问题。用 C++ 的方式可能性能更好(这是一个未经证实的个人想法)。

插入数据

以下代码片段展示了如何将数据插入 Azure 表。

void InsertTableData(string_t key, int id, string_t name, string_t phone)
{
  auto storage_account = cloud_storage_account::parse(
      U("UseDevelopmentStorage=true"));
  auto table_client = storage_account.create_cloud_table_client();
  auto table = table_client.get_table_reference(
      U("Clients"));
  bool created = table.create_if_not_exists();

  table_entity entity(partitionKey, key);
  auto& properties = entity.properties();
  properties.reserve(3);
  properties[U("Name")] = entity_property(name);
  properties[U("Phone")] = entity_property(phone);
  properties[U("Id")] = entity_property(id);

  auto operation = table_operation::insert_entity(entity);
  auto result = table.execute(operation);
}

Azure 表数据实际上就是关于属性的。因此,所涉及的操作是创建一个新的表实体,设置一些属性(命名的键和值),然后对表执行 insert_entity 操作。

更新数据

这是更新表数据的方法。

void UpdateTableData(string_t key, int id, string_t name, string_t phone)
{
  auto storage_account = cloud_storage_account::parse( U("UseDevelopmentStorage=true"));
  auto table_client = storage_account.create_cloud_table_client();
  auto table = table_client.get_table_reference( U("Clients")); 
  bool created = table.create_if_not_exists();

  auto operation = table_operation::retrieve_entity( partitionKey, key);
  auto result = table.execute(operation);
  auto entity = result.entity();
  auto& properties = entity.properties(); 
  properties[U("Name")] = entity_property(name); 
  properties[U("Phone")] = entity_property(phone); 
  properties[U("Id")] = entity_property(id);

  auto operationUpdate = table_operation::replace_entity(entity);
  result = table.execute(operationUpdate);
}

这段代码非常相似。retrieve_entity 操作用于访问我们需要更新的实体。更新属性值,然后执行 replace_entity 操作。

删除数据

void DeleteTableData(string_t key)
{  
  auto storage_account = cloud_storage_account::parse( U("UseDevelopmentStorage=true"));  
  auto table_client = storage_account.create_cloud_table_client();  
  auto table = table_client.get_table_reference( U("Clients")); 
  bool created = table.create_if_not_exists();

  auto operation = table_operation::retrieve_entity( partitionKey, key);  
  auto result = table.execute(operation);
  
  auto entity = result.entity();
  auto operationUpdate = table_operation::delete_entity(entity);  
  result = table.execute(operationUpdate);
}

这与更新非常相似,只是执行的是 delete_entity 操作。

查询表数据

虽然不如 SQL 功能强大,但 Azure 表允许您进行最小化的查询。使用原生 SDK,您可以通过 table_query 对象的 set_filter_string 函数来实现。这是上一个示例中修改后的 ReadTableData 方法。

void ReadTableData(string_t filter)
{
  auto storage_account = cloud_storage_account::parse(
      U("UseDevelopmentStorage=true"));
  auto table_client = storage_account.create_cloud_table_client();
  auto table = table_client.get_table_reference(U("Clients"));
  bool created = table.create_if_not_exists();

  table_query query;
  query.set_filter_string(filter);

  auto results = table.execute_query(query);

  for (auto item : results)
  {
    auto properties = item.properties();

    for (auto property : properties)
    {
      ucout << property.first << U(" = ")
            << property.second.str() << U("\t");
    }

    ucout << endl;
  }
}

这是一个示例查询,用于获取 RowKey 值范围内所有行。

void ReadTableData(string_t rowKeyStart, string_t rowKeyEnd) 
{ 
    auto filter = table_query::combine_filter_conditions( 
        table_query::generate_filter_condition(
            U("RowKey"), 
            query_comparison_operator::greater_than_or_equal, 
            rowKeyStart), 
        query_logical_operator::and,
        table_query::generate_filter_condition( 
            U("RowKey"), 
            query_comparison_operator::less_than_or_equal,
            rowKeyEnd)); 

    ReadTableData(filter); 
}

combine_filter_conditions 函数用于创建查询字符串。query_comparison_operator 类允许您设置比较运算符,query_logical_operator 类允许您设置逻辑运算符。如果您想知道,它会被转换为以下字符串。

(RowKey ge '100') and (RowKey le '104')

这是一个类似的方法,它针对 Name 列进行查询。

void ReadTableDataStartingWith(string_t letter1, string_t letter2)
{
    auto filter = table_query::combine_filter_conditions(
        table_query::generate_filter_condition(
          U("Name"), query_comparison_operator::greater_than_or_equal, letter1),
        query_logical_operator::and,
        table_query::generate_filter_condition(
          U("Name"), query_comparison_operator::less_than, letter2));

    ReadTableData(filter);
}

生成的查询过滤器如下所示。

(Name ge 'D') and (Name lt 'E')

您可以这样调用它:

ReadTableDataStartingWith(U("D"), U("E"));

这将返回所有名字以 D 开头的行。查询分区键和行键会更快。另外,对于对数据集的重复查询,您可能希望获取更大一部分数据,然后使用标准的 STL 在内存中进行查询。

访问 Azure Blob 存储

Blob 存储用于存储大量半结构化或非结构化数据,如图像、视频、文档等。Blob 服务允许您创建命名的容器,然后这些容器可以包含一个或多个命名的 Blob,这些 Blob 可以通过 URI(可选)公开访问。您需要添加此头文件包含。

#include "../packages/wastorage.0.3.0-preview/build/native/include/was/blob.h"

创建 Blob

void CreateTextBlobs()
{
  auto storage_account = cloud_storage_account::parse(
      U("UseDevelopmentStorage=true"));
  auto blob_client = storage_account.create_cloud_blob_client();
  auto container = blob_client.get_container_reference(
      U("textdata"));
  bool created = container.create_if_not_exists();

  blob_container_permissions permissions;
  permissions.set_public_access(
      blob_container_public_access_type::container);
  container.upload_permissions(permissions);

  auto text_blob1 = container.get_block_blob_reference(
      U("texts/text1"));
  text_blob1.upload_text(U("This is some text - modified"));

  auto text_blob2 = container.get_block_blob_reference(
      U("texts/text2"));
  text_blob2.upload_from_file(U("./stdafx.h"));
}

使用的类/方法与表存储非常相似。请注意使用 blob_container_permissions 来设置容器的公共访问级别。默认情况下是关闭的,您可以选择将其设置为 blob(客户端可以读取 Blob 数据)或 container(客户端可以列出容器中的 Blob 并读取 Blob 数据)。您可以在 Blob 名称中模拟目录。在上面的示例中,text1 和 text2 都位于 texts 目录下。这只会影响 URI,而不是物理目录。

列出 Blob

void ListTextBlobs()
{
  auto storage_account = cloud_storage_account::parse(
        U("UseDevelopmentStorage=true"));
  auto blob_client = storage_account.create_cloud_blob_client();
  auto container = blob_client.get_container_reference(
        U("textdata"));
  bool created = container.create_if_not_exists();

  continuation_token token;

  auto result = container.list_blobs_segmented(token);

  for (auto dir : result.directories())
  {
    ucout << U("Directory: ") << dir.uri().path() << endl;
    ucout << endl;

    continuation_token dir_token;
    auto resultInner = dir.list_blobs_segmented(dir_token);

    for (auto item : resultInner.blobs())
    {
      ucout << item.name() << endl;
      ucout << item.uri().path() << endl;
      ucout << item.properties().content_type() << endl;
      ucout << endl;
    }   
  }
}

要列出容器中的 Blob,我们需要使用 continuation_token 对象。容器对象支持 list_blobs_segmented 方法,该方法接受此 token。对于返回的每个目录,我们可以使用单独的 continuation_token 对象调用 list_blobs_segmented。一旦我们遍历目录中的 Blob,我们就可以访问名称、URI、内容类型等属性。以下是调用上述函数后的示例输出。

Directory: /devstoreaccount1/textdata/texts/

texts/text1
/devstoreaccount1/textdata/texts/text1
text/plain; charset=utf-8

texts/text2
/devstoreaccount1/textdata/texts/text2
application/octet-stream

请注意,我们上传的文本数据类型为 text/plain,而上传的文件类型为 application/octet-stream。如果您将第一个 Blob 的 URI 粘贴到浏览器中,文本将直接显示出来。而在第二种情况下,文件将被提供下载——显然,这是基于内容类型的浏览器特定行为。

下载 Blob 内容

SDK 使提取 Blob 数据变得非常简单。对于文本内容,您可以直接获取文本;对于非文本内容,您可以将其下载到文件或流。下面的代码片段展示了如何实现。

void DownloadBlobData()
{
    auto storage_account = cloud_storage_account::parse(U("UseDevelopmentStorage=true"));
    auto blob_client = storage_account.create_cloud_blob_client();
    auto container = blob_client.get_container_reference(U("textdata"));
    bool created = container.create_if_not_exists();

    // Read the text content directly
    auto text_blob1 = container.get_block_blob_reference(U("texts/text1"));
    auto text = text_blob1.download_text();
    ucout << text << endl;

    // Download the blob data to a file
    auto text_blob2 = container.get_block_blob_reference(U("texts/text2"));
    text_blob2.download_to_file(U("d:\\tmp\\blobdata.txt"));

    // Download the blob data to an ostream
    stringstreambuf buffer;
    concurrency::streams::ostream output(buffer);
    text_blob2.download_to_stream(output);
    cout << buffer.collection() << endl;
}

使用 Azure 队列

Azure 队列用于存储大量项(称为消息)。每条消息最多可以包含 64 Kb。队列的典型用途是应用程序内部通信,例如,您的网站可能会将消息添加到队列中,供后台服务稍后处理。C++ Azure 存储 SDK 在队列方面也保持了其一致的 API(所以如果您知道如何使用 Blob 和表,那就没问题了)。您需要包含以下头文件。

#include "../packages/wastorage.0.3.0-preview/build/native/include/was/queue.h"

这是一段代码片段,展示了如何将消息插入队列。

void InsertQueueMessages()
{
    auto storage_account = cloud_storage_account::parse(U("UseDevelopmentStorage=true"));
    auto queue_client = storage_account.create_cloud_queue_client();
    auto queue = queue_client.get_queue_reference(U("sample"));
    bool created = queue.create_if_not_exists();

    queue.add_message(cloud_queue_message(U("queue message 01")));
    queue.add_message(cloud_queue_message(U("queue message 02")));
    queue.add_message(cloud_queue_message(U("queue message 03")));
}
像任何不错的队列一样,您可以选择查看(peek)或出队(dequeue)消息。以下是一个示例片段,展示了如何查看队列。
void PeekQueueMessage()
{
    auto storage_account = cloud_storage_account::parse(U("UseDevelopmentStorage=true"));
    auto queue_client = storage_account.create_cloud_queue_client();
    auto queue = queue_client.get_queue_reference(U("sample"));
    bool created = queue.create_if_not_exists();

    queue.download_attributes();
    int count = queue.approximate_message_count();
    ucout << U("Approx count = ") << count << endl;

    // Peek the next 2 messages (assumes at least 2 present)
    auto messages = queue.peek_messages(2);
    for (auto message : messages)
    {
        ucout << message.content_as_string() << endl;
    }
}

count 方法的名字中带有“近似”,因为返回的计数在下次调用 download_attributes 之前不会被刷新。以下是一段代码片段,展示了如何从队列中出队项。

void DeQueueMessages()
{
  auto storage_account = cloud_storage_account::parse(
      U("UseDevelopmentStorage=true"));
  auto queue_client = storage_account
      .create_cloud_queue_client();
  auto queue = queue_client.get_queue_reference(
      U("sample"));
  bool created = queue.create_if_not_exists();

  queue.download_attributes();
  int count = queue.approximate_message_count();
  ucout << U("Approx count = ") << count << endl;

  for (size_t i = 0; i < count; i++)
  {
    auto message = queue.get_message();
    queue.delete_message(message);
    ucout << message.content_as_string() << endl;
  }

  queue.download_attributes();
  count = queue.approximate_message_count();
  ucout << U("Approx count = ") << count << endl;
}

请注意,我在调用 get_message 后调用了 delete_message。这是因为 get_message 只是在默认超时时间(30 秒)内将该项隐藏起来,之后该消息就会回来。这允许您在出现问题时回滚更改。因此,您会获取一条消息,处理它,并在处理完成后,您可以调用 delete_message。这假设您在 30 秒内完成了处理。如果您想增加(或减少)超时时间,您也可以这样做。

默认的 30 秒不可见超时时间有时可能不够。在这些情况下,您可以更改超时时间,以便在删除消息之前有更多时间来处理它。

void DeQueueMessagesWithTimeOut(std::chrono::seconds timeout)
{
  auto storage_account = cloud_storage_account::parse(
      U("UseDevelopmentStorage=true"));
  auto queue_client = storage_account.create_cloud_queue_client();
  auto queue = queue_client.get_queue_reference(
      U("sample"));
  bool created = queue.create_if_not_exists();

  queue.download_attributes();
  int count = queue.approximate_message_count();
  ucout << U("Approx count = ") << count << endl;

  queue_request_options options;
  operation_context op_context;
  auto messages = queue.get_messages(
      count, timeout, options, op_context);
  for (auto message : messages)
  {
    // process the message here
    queue.delete_message(message);
    ucout << message.content_as_string() << endl;
  }

  queue.download_attributes();
  count = queue.approximate_message_count();
  ucout << U("Approx count = ") << count << endl;
}

您也可以更新现有消息。下面的代码片段展示了如何做到。

void UpdateQueueMessage()
{
  auto storage_account = cloud_storage_account::parse(
      U("UseDevelopmentStorage=true"));
  auto queue_client = storage_account.create_cloud_queue_client();
  auto queue = queue_client.get_queue_reference(
      U("sample"));
  bool created = queue.create_if_not_exists();

  auto message = queue.get_message();
  message.set_content(U("Updated content"));
  queue.update_message(
      message, std::chrono::seconds(0), true);
}

需要注意的一点是,消息在队列中的位置会丢失。更新后的内容会回到队列的末尾。不过,对于大多数实际场景来说,这应该无关紧要,因为消费者通常不期望依赖队列中项目的顺序。这可能与您在编程课程中如何使用队列有所不同,因为那时它是一个保证的 FIFO 结构。但请记住,那些队列也不允许原地编辑。

就是这样。一如既往,我非常欢迎您的反馈,包括(尤其是)批判性的意见。请查看我的博客,了解与此类似主题的相关文章,特别是关于 C++ 与 Microsoft 平台和库的用法。

历史

  • 文章发布日期 - 2014 年 6 月 17 日
© . All rights reserved.