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






4.95/5 (9投票s)
这是 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 日