概述
这是一个会议或演讲的概述内容,主要介绍一个关于C++ Rest SDK的分享,翻译和理解如下:
翻译
概述
- 先介绍什么是典型的Web服务结构和它的特征
- 讲讲调用这些Web服务的几种方式
- 重点介绍自己团队开发的一个C++库(C++ Rest SDK),这个库帮助程序员方便地使用REST API
- 会通过示例代码讲解这个库怎么用,还会演示实际操作
- 假设听众已经对HTTP协议、JSON格式、REST架构风格等网络基础知识有所了解,不做基础普及
到底什么是网web service
那network service又是什么?
Remote Procedure Call
这段内容是在解释“网络服务”或“Web 服务”的核心概念。我们逐句来理解:
RFC 707 – “基于网络的资源共享的高级框架”
RFC(Request for Comments)是互联网标准文档,RFC 707 定义了一种高层次的架构,用于在网络中共享资源。
最简单地说,就是在另一台机器上执行一个函数并获取结果
这说明网络服务的核心功能是远程调用:客户端通过网络“调用”服务器上的函数,服务器执行后返回结果。
服务器向客户端提供一组可调用的操作
这就像一个远程 API,客户端只需要“调用”,不关心细节。
使用类似本地函数调用的方式,屏蔽底层细节,例如:
- 参数的编码与解码格式
- 底层传输协议(如 HTTP、TCP、UDP 等)
总结:
网络服务的目标是让客户端像调用本地函数一样调用远程服务,而无需关心底层的网络传输、参数封装等技术细节。这是构建 REST、RPC、GraphQL 等系统的基础思想。
可以类比为:
你写了一个函数
add(a, b)
,本来是本地调用,但通过网络服务框架,你可以在别的机器上调用这个函数,像本地一样用add(2, 3)
得到结果5
,而所有底层细节都被封装隐藏了。
What is a web service?
Web 服务是一种软件系统,旨在支持机器与机器之间通过网络进行互操作。
也就是说,Web 服务的核心目的,是让不同的计算机系统能够自动互相调用和交流数据,不需要人为干预。这种“机器对机器”的通信,是现代系统集成的基础。
简单来说,Web 服务就是让两台计算机通过互联网进行通信的方式。
可以理解为一个通道:A 电脑通过某种标准(比如 HTTP + JSON)向 B 电脑发请求,B 执行处理并返回结果。整个流程就像你给一个人发消息,他看完回复你,但实际上是两个程序在交互。
Web 服务目前有两种常见的形式:SOAP 和 REST。
- SOAP(Simple Object Access Protocol):早期使用 XML 的一种复杂通信协议,强调严格的格式和标准。
- REST(Representational State Transfer):更轻量级,通常基于 HTTP + JSON,灵活易用,是现在主流方式。
虽然“Web 服务”这个词最初常常与 SOAP 搭配使用,但现在不局限于此。
也就是说,很多人一说“Web 服务”,第一反应还是 SOAP,但实际上现代大多数系统都采用 REST 或其他形式的服务了。
这次讨论主要关注 REST 风格的服务,也就是通过 HTTP 提供的 Web API。
比如你访问 https://api.example.com/users/123
获取用户数据,这种就是典型的 REST 风格服务。它利用 HTTP 的 GET/POST/PUT/DELETE 等方法实现资源的操作,简洁清晰,广泛使用于移动应用、网页、后端服务之间。
总结:
这段内容的重点在于说明:
- Web 服务是计算机之间的标准通信机制
- 主要有 SOAP 和 REST 两种风格
- 当前主流是基于 HTTP 的 REST API
- 我们要讨论的重点,就是如何构建和使用这种 REST 式的 Web 接口
REpresentational State Transfer(REST)
表述性状态转移
分词解释:
-
Representational(表述性):
指的是资源的“表现形式”或“表现层”。在 REST 中,客户端不会直接操作服务器上的资源本身,而是通过资源的表现形式(比如 JSON、XML)进行交互。 -
State(状态):
指的是资源在某一时刻的状态。比如一个用户对象{ "name": "Alice", "age": 25 }
就代表了这个用户在当前的状态。 -
Transfer(转移):
表示客户端和服务器之间通过 HTTP 协议传输资源的状态表示,客户端通过这些表示来了解资源状态,并可能通过请求修改它。
总结式理解:
REST 的含义是:
通过标准的 HTTP 协议,客户端和服务器传输资源的“表现形式”,并通过这些表现形式实现系统状态的变更或查询。
它的关键思想包括:
- 使用统一接口(如 HTTP GET、POST、PUT、DELETE)
- 无状态通信(每个请求都不依赖前一个)
- 资源通过 URL 唯一定位
- 数据表现形式通常是 JSON、XML 等
-
设计分布式系统的架构风格
REST 是一种设计网络分布式系统的架构模式,强调系统组件之间的松耦合和可伸缩性。 -
一个服务如果满足以下约束,则称为 RESTful
RESTful 服务必须遵循一组设计原则:-
客户端-服务器分离
客户端和服务器职责分明,客户端负责用户界面,服务器负责数据存储和处理。 -
无状态
每个请求都独立,服务器不会保存客户端的会话状态,简化服务器设计并提升可伸缩性。 -
统一接口
通过统一的接口(如 HTTP 标准方法)与资源交互,降低系统复杂度。 -
可缓存
响应可以被缓存,提升性能和可伸缩性。 -
分层系统
系统可以由多个层组成,每层只与相邻层通信,支持负载均衡和安全策略。 -
按需代码(可选)
服务器可以将可执行代码(如 JavaScript)传送到客户端扩展功能。
-
-
对于 Web 服务来说,这意味着:
-
通过 URI 与资源交互
资源通过唯一的 URI(统一资源标识符)定位和访问。 -
使用 HTTP 方法作为接口
典型方法如 GET(读取)、POST(创建)、PUT(更新)、DELETE(删除)等。 -
无特定数据格式,但通常使用 JSON
数据格式灵活,但 JSON 是最常见的传输格式,便于阅读和解析。
-
有哪些实际的例子呢?
流行的网络服务/网络接口(Web APIs)
服务协议/标准
- Google Maps:HTTP、URI、XML、JSON
- Facebook Graph API:HTTP、URI、OAuth、JSON
- Twitter:HTTP、URI、OAuth、JSON
- Amazon S3:HTTP、URI、XML
- Azure Storage:HTTP、URI、XML、JSON(表格)
- Dropbox:HTTP、URI、OAuth、JSON
- WordPress:HTTP、URI、OAuth、JSON
常见协议和标准包括 HTTP、URI、JSON、OAuth。
这只是其中一小部分网络接口,实际上还有非常多,值得进一步了解和探索。
我现在怎么使用这些网络接口(Web APIs)?
可用的选项
有些服务提供专门的客户端 SDK,不过大多数可能不支持 C++。
你可以直接向公开的 HTTP 接口发请求,大多数语言(除 C++ 之外)都有很成熟的库支持。
那么 C++ 怎么办呢?可以使用现成的 HTTP 库,或者在平台支持的情况下用特定平台的 HTTP API,也可以自己基于 TCP socket 实现 HTTP。
如果你还关注异步处理、跨平台兼容性和符合现代 C++11 风格的代码,这就更有挑战,需要更合适的库或框架来支持这些需求。
尤其是 C++ 的特殊性,调用 Web API 有多种方式,但 C++ 缺乏直接成熟的库支持,特别是异步和跨平台方面更难实现,需要开发者选择合适的工具或自行实现相关功能。
The C++ Rest SDK
C++ Rest SDK 是一个专门为 C++ 设计的库,帮助开发者更方便地访问各种 Web 服务接口。它不追求从零开始,而是整合已有技术,提供统一且易用的接口,从而降低开发复杂度,提高效率。
- 该 SDK 旨在弥补现有工具在 C++ 访问网络服务方面的不足,提供一套高层次的 API,涵盖 HTTP、URI、JSON、OAuth 以及 WebSockets 等核心功能模块,方便开发者直接调用。
- 它不是从头重写所有东西,而是聪明地复用现有的开源库和操作系统/平台提供的 API,减少重复劳动。
- 最终目标是让开发者更简单地调用 Web API,同时也方便他们基于此库来编写自己的客户端 SDK。
整体来说,它是一个集成式的、高效的网络服务访问框架,专为现代 C++ 开发而设计。
- 简单的 API:比起功能繁杂,提供一个清晰、直接易用的接口更重要,降低使用门槛。
- 异步支持:所有输入输出操作以及可能耗时的工作都采用异步处理,避免阻塞,提升效率和响应能力。
- C++11 风格:采用现代 C++11 的语法和特性,让代码更简洁、表达力更强。
- 跨平台兼容:演示的代码可以运行在多种平台上,包括 Windows 桌面和服务器(从 XP 开始)、Windows Store、Windows Phone、macOS(OS X)、iOS、Ubuntu 和 Android,体现了良好的跨平台能力。
Asynchrony pplx::task
pplx::task
Parallel Patterns Library(PPL)提供了跨平台的任务处理功能,特点是:
-
它的任务(tasks)类似于 C++ 标准库中的
std::future
,但增加了“续作”(continuations)功能,也就是说,任务完成后可以自动触发后续任务,方便实现异步操作的链式调用。 -
将来这些 PPL 的任务模型可能会被更新的 futures 标准(如提案 N3970)所替代。
-
目前有很多关于基于任务的编程的演讲和资料,可以学习如何用这种方式来写异步代码。
总结就是:PPL 的任务机制为跨平台异步编程提供了便利,比 std::future
更灵活,支持连续执行的异步操作。
pplx::task – continuations
pplx::task
是 C++ Parallel Patterns Library (PPL) 中用于异步编程的一个核心类,类似于 std::future
,但更强大。
“continuations”(续作) 指的是你可以在一个异步任务完成后,链式地绑定后续操作,也就是说,当这个任务结束时,会自动触发接下来的操作,而不需要手动等待。
举个简单例子:
pplx::task<int> t = some_async_operation();
t.then([](int result) {// 这个 lambda 是 continuation,等任务完成后执行std::cout << "结果是: " << result << std::endl;
});
这样,then
里的代码会在异步任务完成后自动运行,方便写出清晰且非阻塞的异步代码。
总结:
pplx::task
表示一个异步操作。- Continuations 是链式回调,任务完成后自动调用。
- 让异步流程更自然、可读,避免“回调地狱”。
pplx::task<int> intTask = start_op();
intTask.then([](int value) {// Execute some work once operation has completed...
});
这段代码展示了 pplx::task<int>
的基本用法和续作(continuation)的概念:
start_op()
是一个返回pplx::task<int>
的异步函数,表示一个异步操作,最终会产生一个int
类型的结果。intTask
接收这个异步任务。intTask.then(...)
注册了一个续作(continuation),也就是当异步任务完成后自动执行的函数。- 传入的 lambda 函数参数
value
就是异步操作完成后的结果(int 类型)。 - 续作里的代码会在
start_op()
异步完成后被调用,可以继续执行后续逻辑。
简单理解:
“先启动一个异步操作,等它结束了自动调用后续函数,拿到结果后做处理。”
这就是基于 pplx::task
和续作机制的异步编程方式,方便链式调用和非阻塞操作。
Can also compose tasks using pplx::when_any and pplx::when_all constructs
你还可以使用 pplx::when_any
和 pplx::when_all
这两个构造函数来组合多个 pplx::task
任务。
pplx::when_any
:当一组任务中任意一个完成时就触发,返回最快完成的那个任务结果。pplx::when_all
:等待一组任务全部完成后触发,返回所有任务的结果。
这两个函数非常适合处理多个异步操作的同步和协调。
理解成:
你可以把多个异步任务绑在一起,要么等第一个完成(when_any),要么等全部完成(when_all),方便管理复杂异步流程。
pplx::task<int> intTask = pplx::create_task([]() -> int { throw std::runtime_error("error"); });
intTask.then([](pplx::task<int> op) {try {int value = op.get();Task based continuation, always executes} catch (const std::runtime_error &e) {
/* Perform error handling... */ }
});
这段代码的意思是:
-
创建一个任务
intTask
,任务体是一个抛出std::runtime_error
异常的函数。
pplx::create_task
接受一个 lambda,返回一个pplx::task<int>
对象。这个 lambda 执行时直接抛异常。 -
在这个任务完成后调用
.then(...)
注册一个续续操作(continuation)。
这个续续操作接受一个pplx::task<int>
对象op
,它代表之前的任务。 -
续续操作里面用
try-catch
来处理任务执行结果。- 调用
op.get()
会尝试获取任务结果。 - 如果任务成功完成,
get()
返回结果值。 - 如果任务执行时抛了异常(这里正好是
std::runtime_error
),get()
会重新抛出该异常,于是进入catch
块,进行错误处理。
- 调用
-
续续操作无论任务成功还是失败都会执行,因为续续是绑定在任务完成后的回调。
总结:
这段代码展示了如何用 pplx::task
异步执行一个可能抛异常的操作,并且在续续里优雅地捕获并处理异常,保证异步任务链中错误不会未处理地传递。
如果用简单语言说,就是:
异步操作执行过程中可能发生错误,我们用续续函数捕获并处理错误,保证程序健壮。
http_client
- 支持 HTTP/1.1 协议,基于客户端发起的请求/响应模式。
- 提供了简单的高层 API,方便管理 HTTP 请求。
- 不需要开发者自己管理底层的连接细节。
也就是说,这个库帮你封装了 HTTP 协议细节,使用时只需专注于发请求和收响应,无需操心连接的建立、维护和关闭等复杂工作。
http_client– hello world upload
http_client client(U("http://myserver.com"));
http_response response = client.request(methods::POST, U("mypath"), U("Hello World")).get();
if (response.status_code() == status_codes::OK) {// Inspect response...
}
``
* 创建一个 `http_client` 对象,目标服务器地址是 `http://myserver.com`。
* 通过这个客户端,发送一个 POST 请求,请求路径是 `mypath`,请求体内容是 `"Hello World"`。
* 使用 `.get()` 同步等待请求结果返回,得到 `http_response` 对象。
* 判断响应的状态码是否是 200(OK)。
* 如果状态码是 OK,就可以继续处理或查看响应内容。这里的 ‘U’ 是一个宏或函数,用来创建平台相关的字符串字面量,保证代码在不同系统上都能正确处理字符串编码。* 在 Windows 平台上,使用的是 UTF-16 编码,字符串类型通常是 `std::wstring`。
* 在其他平台(如 Linux、macOS)上,使用的是 UTF-8 编码,字符串类型通常是 `std::string`。通过 ‘U’ 包裹字符串,代码可以透明地适配不同平台的字符串类型,避免编码和类型不匹配的问题。```cpp
// Manually build up request.
http_request req(methods::POST);
req.set_request_uri(U("mypath"));
req.headers().add(header_names::user_agent, U(“myclient"));
req.set_body(U("Hello World"));
http_response response = client.request(req).get();
这段代码展示了如何手动构建一个 HTTP POST 请求并发送:
-
http_request req(methods::POST);
创建一个 HTTP 请求对象,指定请求方法为 POST。 -
req.set_request_uri(U("mypath"));
设置请求的 URI 路径为"mypath"
。 -
req.headers().add(header_names::user_agent, U("myclient"));
在请求头中添加 User-Agent 字段,值为"myclient"
。 -
req.set_body(U("Hello World"));
设置请求体内容为"Hello World"
。 -
http_response response = client.request(req).get();
使用之前定义的client
发送该请求,并同步等待获取响应。
总结:
这段代码通过手动设置 URI、请求头和请求体,展示了比直接调用 client.request(method, uri, body)
更灵活的请求构造方式。
http_client
还支持接收配置参数,用来设置一些选项,比如请求超时、数据块大小(chunk size)等。
这让你可以根据需要灵活调整客户端行为,比如控制等待响应的最长时间,或者调整数据传输的缓冲大小,以优化性能或适应不同网络环境。
http_client client(U("http://myserver.com"));
http_response response = client.request(methods::GET).get();
if (response.status_code() == status_codes::OK) {const utility::string_t body = response.extract_string().get();
}
这段代码演示了使用 http_client
发起一个简单的 GET 请求,并处理响应的过程:
- 创建一个
http_client
对象,目标地址是"http://myserver.com"
,U
宏用来处理跨平台字符串编码。 - 使用
client.request(methods::GET).get()
发送一个同步的 GET 请求,等待服务器响应。 - 检查响应的状态码是否为
OK
(即 HTTP 200,表示请求成功)。 - 如果成功,调用
response.extract_string().get()
从响应中提取字符串内容(即服务器返回的正文),并保存到body
变量中。
总结:这是一个标准的同步 HTTP GET 请求流程,拿到结果后转成字符串以便后续使用。
http_response
对象提供了多种方法,可以将响应体(body)以不同形式提取出来,具体包括:
- 作为字符串(string)
- 作为 JSON 对象(方便处理结构化数据)
- 作为字节向量(vector,适合二进制数据)
- 作为数据流(stream,适合大文件或分块读取)
这使得处理 HTTP 响应更加灵活,可以根据需求选择合适的数据形式。
http_client client(U("http://myserver.com"));
client.request(methods::GET).then([](http_response response) {// Check status code...return response.extract_string();}).then([](const utility::string_t &body) {// Use string...});
这段代码示范了如何用异步方式通过 http_client
发起一个 GET 请求并处理响应:
-
client.request(methods::GET)
异步发起一个 GET 请求,返回一个pplx::task<http_response>
,表示未来某个时间会得到服务器的响应。 -
.then([](http_response response) { ... })
请求完成后,这个回调被调用,接收http_response
对象。
里面可以检查状态码,确保请求成功,然后调用response.extract_string()
,异步提取响应体为字符串,返回一个新的任务。 -
.then([](const utility::string_t &body) { ... })
上一个任务完成后,这个回调被调用,拿到响应体字符串body
,可以对数据进行处理。
总结:
这是典型的基于任务链(continuations)的异步编程风格,通过连续调用 .then()
把异步操作串联起来,代码清晰且不阻塞线程。
http_client client(U("http://myserver.com"));
pplx::task<http_response> pendingRequest = client.request(methods::GET);
pendingRequest.then([](http_response response) // Entire request sent, response headers arrived{pplx::task<utility::string_t> extractString = response.extract_string();return extractString;}).then([](const utility::string_t &body) // Entire response arrived{// Use string...});
这段代码展示了如何用 http_client
发起异步 GET 请求,并分步骤处理响应:
-
client.request(methods::GET);
发起异步 GET 请求,返回一个pplx::task<http_response>
,表示未来会有响应。 -
.then([](http_response response) {...})
这是第一个回调,当请求完成且响应头接收到时调用。
里面调用response.extract_string()
,开始异步提取完整响应体(字符串形式),返回另一个任务pplx::task<utility::string_t>
。 -
return extractString;
把这个任务返回,允许链式调用继续等待响应体的读取完成。 -
.then([](const utility::string_t &body) {...})
第二个回调,等响应体完全接收后执行,拿到响应体字符串,可以进行具体业务处理。
总结:
这个流程展示了异步处理 HTTP 响应的两个阶段:
- 先收到响应头,开始解析数据
- 等完整数据到达后,再处理响应体
通过链式 .then()
,代码清晰且非阻塞。
光说不练确实不容易信服
It’s not that I don’t believe you, but I need to see it
http_client– efficient streaming
http_client
的高效流式处理,指的是它支持以流的方式处理 HTTP 响应体数据,而不是一次性把整个响应读入内存。这种方式适合处理大文件下载、视频流、或者任何大数据量的传输,避免内存占用过高。
具体特点包括:
- 边接收边处理:响应体数据可以边接收边消费,无需等待整个响应完成。
- 支持流接口:可以将响应体作为输入流来读取,比如
std::istream
,方便与标准流操作结合。 - 降低延迟和内存压力:适合处理大文件或长连接,提升性能和资源利用率。
简言之,http_client
通过流式接口,实现了高效、低开销的 HTTP 数据传输,适合需要处理大数据或持续数据流的应用场景。
http_client client(U("http://myserver.com"));
// Create stream backed by a vector.
http_request request(methods::GET);
container_buffer<std::vector<uint8_t>> buffer;
buffer.collection().reserve(1024 * 1024 * 4); // Heap allocation all at once up front
request.set_response_stream(buffer.create_ostream());
// Send request and wait for response to arrive.
http_response response = client.request(request).get();
response.content_ready().wait();
std::vector<uint8_t> body = std::move(buffer.collection()); // Important to move!
这段代码演示了如何用 http_client
进行高效的流式接收,把 HTTP 响应体直接写入一个预分配好的内存缓冲区(std::vector<uint8_t>
),具体理解如下:
-
http_client client(U("http://myserver.com"));
创建一个 HTTP 客户端,目标服务器地址是"http://myserver.com"
。 -
http_request request(methods::GET);
构造一个 GET 请求。 -
container_buffer<std::vector<uint8_t>> buffer;
创建一个基于std::vector<uint8_t>
的缓冲区,作为响应数据的流式存储区。 -
buffer.collection().reserve(1024 * 1024 * 4);
一次性预先分配约 4MB 内存,避免多次重新分配,提高效率。 -
request.set_response_stream(buffer.create_ostream());
将请求的响应流绑定到这个缓冲区的输出流,响应数据会写入这里。 -
http_response response = client.request(request).get();
发送请求,阻塞等待响应。 -
response.content_ready().wait();
等待响应内容完全准备好。 -
std::vector<uint8_t> body = std::move(buffer.collection());
将缓冲区中的数据移动到body
,避免额外拷贝,提升性能。
总结:
这段代码展示了如何用流式方式将 HTTP 响应体数据直接写入内存缓冲区,适合大数据量接收,避免一次性拷贝或内存浪费,是高效网络数据处理的典型写法。
// Buffer backed by a file.
streambuf<uint8_t> buffer = file_buffer<uint8_t>::open(U("myfile")).get();
// Buffer backed by raw memory.
const size_t size = 1024 * 1024 * 4;
char *raw = new char[size];
rawptr_buffer<uint8_t> buffer(reinterpret_cast<uint8_t *>(raw), size);
这段代码展示了两种用不同类型缓冲区支持流的方式,具体理解如下:
- 文件缓冲区
streambuf<uint8_t> buffer = file_buffer<uint8_t>::open(U("myfile")).get();
- 通过
file_buffer<uint8_t>::open(U("myfile"))
打开一个名为"myfile"
的文件,并返回一个异步任务。 .get()
等待任务完成并获得与文件关联的缓冲区对象。- 这个缓冲区将会直接操作文件,适合流式读写大文件,避免一次性全部加载到内存。
- 原始内存缓冲区
const size_t size = 1024 * 1024 * 4;
char *raw = new char[size];
rawptr_buffer<uint8_t> buffer(reinterpret_cast<uint8_t *>(raw), size);
- 手动在堆上分配一块 4MB 的原始内存(
char* raw
)。 - 使用
rawptr_buffer<uint8_t>
把这块原始内存封装成缓冲区,传给流式接口使用。 - 这样可以用自定义内存管理或与已有内存结合,适合不想复制数据,直接操作内存的场景。
总结:
这两种缓冲区类型(文件缓冲区和原始内存缓冲区)为流式数据处理提供了灵活的底层支持,可以针对不同应用场景优化数据读写方式。
这段内容讲的是使用“连续存储缓冲区”(buffer backed with contiguous storage)时带来的性能优化和具体应用,理解如下:
-
连续存储缓冲区
使用内存连续的一块区域作为缓冲区,比如std::vector
或原始内存块,可以让某些平台上的操作更高效。因为连续内存便于快速访问和处理。 -
调整消息体的块大小(chunk size)
合理设置数据块的大小,避免频繁地上下栈传递数据,从而减少开销,提高性能。 -
获取指向内部存储的指针
可以直接拿到缓冲区内部连续存储的指针,方便和底层平台接口交互,减少复制。 -
发送请求体时
直接把缓冲区指针传递给底层平台 API 进行读取,避免不必要的数据复制或转换。 -
接收响应体时
直接把数据写入缓冲区对应的内存区域,也就是传给平台 API 直接写入缓冲区。
总结就是:通过使用连续内存的缓冲区,并且配合底层平台接口的直接读写,可以显著提升网络请求/响应的处理效率,减少内存操作开销。
websocket_client
这段内容讲的是WebSockets技术及其特点,理解如下:
-
支持双向通信
WebSocket允许在建立一次持久的TCP连接后,客户端和服务器之间可以双向实时通信,不再局限于传统的HTTP请求-响应模式。 -
与HTTP请求/响应模型对比
传统HTTP是客户端发请求,服务器回应;而WebSocket连接建立后,服务器可以主动推送消息给客户端,不需要客户端每次都发请求。 -
应用场景
WebSocket适合频繁的小消息交换场景,比如在线游戏、互动协作应用、聊天系统等,需要实时、低延迟的双向通讯。
总结:WebSocket是建立在HTTP基础上的一种协议,解决了传统HTTP通信单向性和请求响应模型的限制,使得客户端和服务器可以持续且双向地交换数据,非常适合实时交互类应用。
websocket_client client;
client.connect(U("ws://myserver.com")).then([] {// Connection successfully opened...
});
// Later on when done...
client.close().then([] {// Connection is closed...
});
这段代码演示了如何用 C++ Rest SDK 使用 websocket_client
来建立和关闭 WebSocket 连接,理解如下:
-
websocket_client client;
创建一个 WebSocket 客户端实例。 -
client.connect(U("ws://myserver.com"))
向指定的 WebSocket 服务器发起连接请求,U()
是平台相关的字符串封装。 -
.then([] { ... });
连接成功后执行的回调函数,表示连接已经打开。 -
client.close()
关闭当前的 WebSocket 连接。 -
.then([] { ... });
连接关闭后执行的回调函数,表示连接已经关闭。
总结:这段代码展示了WebSocket连接的建立和关闭过程,且都是异步操作,配合回调来处理连接成功和关闭事件。
websocket_client
也支持配置选项,比如:
-
HTTP 升级请求头(Upgrade request headers):
在建立 WebSocket 连接的过程中,客户端需要通过 HTTP 请求发起“协议升级”(Upgrade)请求,websocket_client
允许你自定义这些请求头,比如添加身份验证信息、自定义头字段等。 -
子协议(Subprotocols):
WebSocket 支持协商“子协议”,用于定义客户端和服务器之间通信的具体协议(比如使用 JSON-RPC、chat 协议等)。websocket_client
可以配置要使用的子协议,让服务器知道你打算用哪种通信方式。
总结
你可以通过配置 websocket_client
来更精细地控制连接行为,例如添加认证信息或声明使用的通信协议,使其更好地适应复杂的网络场景。
websocket_client– sending
// Strings
websocket_outgoing_message msg;
msg.set_utf8_message("Hello World");
pplx::task<void> sendTask = client.send(msg);
// Binary
websocket_outgoing_message msg;
streambuf<uint8_t> buffer = file_buffer<uint8_t>::open(U("myfile")).get();
msg.set_binary_message(buffer.create_istream(), 10); // Send only 10 bytes
pplx::task<void> sendTask = client.send(msg);
这段代码展示了如何使用 websocket_client
通过 WebSocket 发送消息,包含 两种类型的消息:字符串(文本)和二进制数据。
文本消息发送(UTF-8 编码字符串)
websocket_outgoing_message msg;
msg.set_utf8_message("Hello World");
pplx::task<void> sendTask = client.send(msg);
理解:
- 创建一个
websocket_outgoing_message
对象。 - 使用
set_utf8_message()
设置要发送的文本内容。 - 调用
client.send(msg)
异步发送这条消息。
二进制消息发送(从文件读取数据)
websocket_outgoing_message msg;
streambuf<uint8_t> buffer = file_buffer<uint8_t>::open(U("myfile")).get();
msg.set_binary_message(buffer.create_istream(), 10); // Send only 10 bytes
pplx::task<void> sendTask = client.send(msg);
理解:
- 创建一个
websocket_outgoing_message
对象。 - 使用
file_buffer
打开名为"myfile"
的文件,并创建一个输入流。 - 使用
set_binary_message()
设置要发送的二进制数据,最多发送 10 个字节。 - 通过
client.send(msg)
异步发送消息。
核心理解:
-
websocket_outgoing_message
是 WebSocket 客户端用于封装消息的对象。 -
可以发送:
- UTF-8 文本消息(通常用于聊天、命令等)
- 二进制消息(例如图像、音频、文件片段)
-
所有发送都是 异步的,通过
pplx::task
表示操作结果,可继续添加.then(...)
处理发送成功或失败。
websocket_client– receiving
websocket_client client;
client.receive().then([](websocket_incoming_message msg) {return msg.extract_string(); // Msg body could still be arriving}).then([](std::string &data) {});
这段代码展示了如何使用 websocket_client
异步接收来自服务器的 WebSocket 消息,并提取为字符串进行处理。我们来逐步理解:
代码解析:
websocket_client client;client.receive().then([](websocket_incoming_message msg) {return msg.extract_string(); // 消息内容可能还在到达过程中}).then([](std::string &data) {// 在这里处理消息内容});
逐步理解:
-
client.receive()
- 启动异步接收操作。
- 一旦有来自服务器的消息,这个操作就会完成,并传入
websocket_incoming_message
对象。
-
.then([](websocket_incoming_message msg) { return msg.extract_string(); })
- 提取消息体(内容)为字符串。
- 注意注释:消息体可能还没有完全到达,所以
extract_string()
返回的是一个异步任务(pplx::task<std::string>
)。 - 它会等待直到消息全部到达,然后提取为
std::string
。
-
.then([](std::string &data) { ... })
- 当字符串准备好之后,调用这个函数处理提取出来的消息内容。
总结:
- 这是典型的异步编程模式(使用
pplx::task
)。 receive()
-> 接收到消息对象;extract_string()
-> 提取消息体为字符串(可能需要等待);- 第二个
.then
是真正的业务处理逻辑,比如打印消息、逻辑判断等。 - 支持 无阻塞消息处理,很适合 WebSocket 这种持续通信的场景。
类比:
就像你在等快递:
- 快递车(WebSocket)来了,但你还要打开箱子提取内容(extract_string)。
- 然后你才真正使用里面的物品(处理
data
)。
WebSocket 接收消息的两种更高级的处理方式。我们来逐条理解:
1. 也可以将消息体作为流来访问
- 除了提取为字符串(
extract_string()
),还可以用 流(stream) 的方式读取消息体。 - 这在处理大消息或二进制数据时特别有用,因为你可以一边接收、一边处理,避免一次性加载全部内容到内存中。
类比:就像你可以选择一次性读完一本书(字符串),也可以一页一页地读(流)。
2. 添加了通过回调函数重复接收消息的选项
-
WebSocket 是持久连接,服务器可以持续推送消息。
-
可以设置一个回调函数,每当有新消息到达时就自动调用,无需每次都手动调用
.receive()
。 -
这使得程序更自动、更高效地持续处理接收的数据,非常适合:
- 聊天应用
- 实时监控系统
- 多人在线协作
总结
这些特性使 WebSocket 客户端:
- 更灵活(字符串 vs 流)
- 更智能(手动轮询 vs 自动回调)
- 更适合持续、实时、高频率通信的应用场景。
Cross platform learning
这段内容讲的是构建跨平台 C++ 库(如 C++ REST SDK)时的测试和平台兼容性策略,逐条解释如下:
自动化测试基础设施
-
Gated、Rolling、Nightly
- Gated:每次代码合并前先跑测试,确保不会破坏主分支。
- Rolling:持续集成方式,代码变更后自动触发构建和测试。
- Nightly:每天自动构建并运行所有测试,检测潜在问题。
这些机制保障了代码的稳定性和高质量。
默认将测试用例设计为跨平台
- 不只是代码要能跨平台运行,测试也要能在各个平台(Windows、Linux、macOS 等)上跑。
- 有助于尽早发现平台差异导致的问题。
小心构建系统的复杂性
- 支持多个平台可能导致 构建脚本、依赖管理、工具链配置变得复杂。
- 要注意保持构建系统的清晰和可维护性(例如使用 CMake 时注意条件判断不要过多嵌套)。
把“平台”当作“功能”来看待
-
不同平台本身就可以看作是“功能特性”:
- 支持 Windows 是一个功能;
- 支持 Android 是另一个功能;
- 不同平台的功能支持情况也可能不同(比如是否支持 WebSocket)。
有时需要写小的 shim(适配层)来屏蔽缺失库
- 有的平台没有某些标准库或第三方库的支持。
- 需要写小工具或封装层(shim)来“填补空缺”,统一对外 API。
即使用户当前不需要跨平台,也希望你“提供选项”
- 用户喜欢库具备跨平台潜力,即便他们现在只在某个平台用。
- 建议:将 API 层次分明设计,允许用户下沉到底层平台 API,以满足高级需求。
总结
这部分强调:
- 自动化测试的重要性(尤其在跨平台项目中);
- 构建系统与平台兼容性要简单、灵活、可维护;
- 设计库时给用户留下自由度和扩展性,这能提高库的实用价值和用户满意度。