CppCon 2014 学习第2天:Using Web Services in C++

概述

这是一个会议或演讲的概述内容,主要介绍一个关于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_anypplx::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...  */ }
});

这段代码的意思是:

  1. 创建一个任务 intTask,任务体是一个抛出 std::runtime_error 异常的函数。
    pplx::create_task 接受一个 lambda,返回一个 pplx::task<int> 对象。这个 lambda 执行时直接抛异常。

  2. 在这个任务完成后调用 .then(...) 注册一个续续操作(continuation)。
    这个续续操作接受一个 pplx::task<int> 对象 op,它代表之前的任务。

  3. 续续操作里面用 try-catch 来处理任务执行结果。

    • 调用 op.get() 会尝试获取任务结果。
    • 如果任务成功完成,get() 返回结果值。
    • 如果任务执行时抛了异常(这里正好是 std::runtime_error),get() 会重新抛出该异常,于是进入 catch 块,进行错误处理。
  4. 续续操作无论任务成功还是失败都会执行,因为续续是绑定在任务完成后的回调。

总结:
这段代码展示了如何用 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 请求并发送:

  1. http_request req(methods::POST);
    创建一个 HTTP 请求对象,指定请求方法为 POST。

  2. req.set_request_uri(U("mypath"));
    设置请求的 URI 路径为 "mypath"

  3. req.headers().add(header_names::user_agent, U("myclient"));
    在请求头中添加 User-Agent 字段,值为 "myclient"

  4. req.set_body(U("Hello World"));
    设置请求体内容为 "Hello World"

  5. 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 请求,并处理响应的过程:

  1. 创建一个 http_client 对象,目标地址是 "http://myserver.com"U 宏用来处理跨平台字符串编码。
  2. 使用 client.request(methods::GET).get() 发送一个同步的 GET 请求,等待服务器响应。
  3. 检查响应的状态码是否为 OK(即 HTTP 200,表示请求成功)。
  4. 如果成功,调用 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 请求并处理响应:

  1. client.request(methods::GET)
    异步发起一个 GET 请求,返回一个 pplx::task<http_response>,表示未来某个时间会得到服务器的响应。

  2. .then([](http_response response) { ... })
    请求完成后,这个回调被调用,接收 http_response 对象。
    里面可以检查状态码,确保请求成功,然后调用 response.extract_string(),异步提取响应体为字符串,返回一个新的任务。

  3. .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 请求,并分步骤处理响应:

  1. client.request(methods::GET);
    发起异步 GET 请求,返回一个 pplx::task<http_response>,表示未来会有响应。

  2. .then([](http_response response) {...})
    这是第一个回调,当请求完成且响应头接收到时调用。
    里面调用 response.extract_string(),开始异步提取完整响应体(字符串形式),返回另一个任务 pplx::task<utility::string_t>

  3. return extractString;
    把这个任务返回,允许链式调用继续等待响应体的读取完成。

  4. .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>),具体理解如下:

  1. http_client client(U("http://myserver.com"));
    创建一个 HTTP 客户端,目标服务器地址是 "http://myserver.com"

  2. http_request request(methods::GET);
    构造一个 GET 请求。

  3. container_buffer<std::vector<uint8_t>> buffer;
    创建一个基于 std::vector<uint8_t> 的缓冲区,作为响应数据的流式存储区。

  4. buffer.collection().reserve(1024 * 1024 * 4);
    一次性预先分配约 4MB 内存,避免多次重新分配,提高效率。

  5. request.set_response_stream(buffer.create_ostream());
    将请求的响应流绑定到这个缓冲区的输出流,响应数据会写入这里。

  6. http_response response = client.request(request).get();
    发送请求,阻塞等待响应。

  7. response.content_ready().wait();
    等待响应内容完全准备好。

  8. 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);

这段代码展示了两种用不同类型缓冲区支持流的方式,具体理解如下:

  1. 文件缓冲区
streambuf<uint8_t> buffer = file_buffer<uint8_t>::open(U("myfile")).get();
  • 通过 file_buffer<uint8_t>::open(U("myfile")) 打开一个名为 "myfile" 的文件,并返回一个异步任务。
  • .get() 等待任务完成并获得与文件关联的缓冲区对象。
  • 这个缓冲区将会直接操作文件,适合流式读写大文件,避免一次性全部加载到内存。
  1. 原始内存缓冲区
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);
理解:
  1. 创建一个 websocket_outgoing_message 对象。
  2. 使用 set_utf8_message() 设置要发送的文本内容。
  3. 调用 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);
理解:
  1. 创建一个 websocket_outgoing_message 对象。
  2. 使用 file_buffer 打开名为 "myfile" 的文件,并创建一个输入流。
  3. 使用 set_binary_message() 设置要发送的二进制数据,最多发送 10 个字节。
  4. 通过 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) {// 在这里处理消息内容});

逐步理解:

  1. client.receive()

    • 启动异步接收操作。
    • 一旦有来自服务器的消息,这个操作就会完成,并传入 websocket_incoming_message 对象。
  2. .then([](websocket_incoming_message msg) { return msg.extract_string(); })

    • 提取消息体(内容)为字符串。
    • 注意注释:消息体可能还没有完全到达,所以 extract_string() 返回的是一个异步任务(pplx::task<std::string>)。
    • 它会等待直到消息全部到达,然后提取为 std::string
  3. .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,以满足高级需求。

总结

这部分强调:

  • 自动化测试的重要性(尤其在跨平台项目中);
  • 构建系统与平台兼容性要简单、灵活、可维护
  • 设计库时给用户留下自由度和扩展性,这能提高库的实用价值和用户满意度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/85113.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【OpenHarmony】【交叉编译】使用gn在Linux编译3568a上运行的可执行程序

linux下编译arm64可执行程序 一.gn ninja安装二.交叉编译工具链安装1.arm交叉编译工具2.安装arm64编译器 三. gn文件添加arm及arm64工具链四.编译验证 本文以gn nijia安装中demo为例&#xff0c;将其编译为在arm64(rk_3568_a开发板)环境下可运行的程序 一.gn ninja安装 安装g…

【开发心得】AstrBot对接飞书失败的问题探究

飞书与AstrBot的集成使用中,偶尔出现连接不稳定的现象。尽管不影响核心功能,但为深入探究技术细节并推动后续优化,需系统性记录该问题。先从底层通信机制入手,分析连接建立的逻辑与数据交互流程。基于实际现象,明确问题发生的具体场景和表现特征,进而梳理潜在影响因素,为…

Spring Boot 3.5.0中文文档上线

Spring Boot 3.5.0 中文文档翻译完成&#xff0c;需要的可收藏 传送门&#xff1a;Spring Boot 3.5.0 中文文档

7.atlas安装

1.服务器规划 软件版本参考&#xff1a; https://cloud.google.com/dataproc/docs/concepts/versioning/dataproc-release-2.2?hlzh-cn 由于hive3.1.3不完全支持jdk8,所以将hive的版本调整成4.0.1。这个版本没有验证过&#xff0c;需要读者自己抉择。 所有的软件都安装再/op…

c# 获取电脑 分辨率 及 DPI 设置

using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices;/// <summary> /// 这个可以 /// </summary> class Program {static void Main(){//设置DPI感知try{SetProcessDpiAwareness(…

LangChain表达式(LCEL)实操案例1

案例1&#xff1a;写一篇短文&#xff0c;然后对这篇短文进行打分 from langchain_core.output_parsers import StrOutputParser from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.runnables import RunnableWithMessageHist…

OleDbParameter.Value 与 DataTable.Rows.Item.Value 的性能对比

OleDbParameter.Value 与 DataTable.Rows.Item.Value 的性能对比 您提到的两种赋值操作属于不同场景&#xff0c;它们的性能和稳定性取决于具体使用方式。下面从几个维度进行分析&#xff1a; 1. 操作本质对比 &#xff08;1&#xff09;OleDbParameter.Value 用途&#xf…

【Opencv+Yolo】Day2_图像处理

目录 一、图像梯度计算 图像梯度-sobal算子&#xff1a; Scharr&#xff1a;权重变化更大&#xff08;线条更加丰富&#xff0c;比Sobel更加细致捕捉更多梯度信息&#xff09; Laplacian算子&#xff1a;对噪音点敏感&#xff08;可以和其他一起结合使用&#xff09; 二、边…

STM32通过rt_hw_hard_fault_exception中的LR寄存器追溯程序问题​

1. 问题现象 程序运行导致rt_hw_hard_fault_exception 如图 显示错误相关代码 struct exception_stack_frame {uint32_t r0;uint32_t r1;uint32_t r2;uint32_t r3;uint32_t r12; uint32_t lr; // 链接寄存器 (LR)uint32_t pc; // 程序计数器 (PC)uint32_t psr; // 程序状态…

Mac安装配置InfluxDB,InfluxDB快速入门,Java集成InfluxDB

1. 与MySQL的比较 InfluxDBMySQL解释BucketDatabase数据库MeasurementTable表TagIndexed Column索引列FieldColumn普通列PointRow每行数据 2. 安装FluxDB brew update默认安装 2.x的版本 brew install influxdb查看influxdb版本 influxd version # InfluxDB 2.7.11 (git: …

【spring】spring中的retry重试机制; resilience4j熔断限流教程;springboot整合retry+resilience4j教程

在调用三方接口时&#xff0c;我们一般要考虑接口调用失败的处理&#xff0c;可以通过spring提供的retry来实现&#xff1b;如果重试几次都失败了&#xff0c;可能就要考虑降级补偿了&#xff1b; 有时我们也可能要考虑熔断&#xff0c;在微服务中可能会使用sentinel来做熔断&a…

(21)量子计算对密码学的影响

文章目录 2️⃣1️⃣ 量子计算对密码学的影响 &#x1f30c;&#x1f50d; TL;DR&#x1f680; 量子计算&#xff1a;密码学的终结者&#xff1f;⚡ 量子计算的破坏力 &#x1f510; Java密码学体系面临的量子威胁&#x1f525; 受影响最严重的Java安全组件 &#x1f6e1;️ 后…

经营分析会,财务该怎么做?

目录 一、业绩洞察&#xff1a;从「现象描述」到「因果分析」 1.分层拆解 2.关联验证 3.根因追溯 二、预算管理&#xff1a;从「刚性控制」到「动态平衡」 1.分类管控 2.滚动校准 3.价值评估 三、客户与市场&#xff1a;从「交易记录」到「价值评估」 1.价值分层 2.…

进阶智能体实战九、图文需求分析助手(ChatGpt多模态版)(帮你生成 模块划分+页面+表设计、状态机、工作流、ER模型)

🧠 基于 ChatGPT 多模态大模型的需求文档分析助手 本文将介绍如何利用 OpenAI 的 GPT-4o 多模态能力,构建一个智能的需求文档分析助手,自动提取功能模块、菜单设计、字段设计、状态机、流程图和 ER 模型等关键内容。 一、🔧 环境准备 在开始之前,请确保您已经完成了基础…

图书管理系统的设计与实现

湖南软件职业技术大学 本科毕业设计(论文) 设计(论文)题目 图书管理系统的设计与实现 学生姓名 学生学号 所在学院 专业班级 毕业设计(论文)真实性承诺及声明 学生对毕业设计(论文)真实性承诺 本人郑重声明:所提交的毕业设计(论文)作品是本人在指导教师的指导下,独…

直线模组在手术机器人中有哪些技术挑战?

手术机器人在现代医疗领域发挥着越来越重要的作用&#xff0c;直线模组作为其关键部件&#xff0c;对手术机器人的性能有着至关重要的影响。然而&#xff0c;在手术机器人中使用直线模组面临着诸多技术挑战&#xff0c;具体如下&#xff1a; 1、‌高精度要求‌&#xff1a;手术…

技术-工程-管用养修保-智能硬件-智能软件五维黄金序位模型

融智学工程技术体系&#xff1a;五维协同架构 基于邹晓辉教授的框架&#xff0c;工程技术体系重构为&#xff1a;技术-工程-管用养修保-智能硬件-智能软件五维黄金序位模型&#xff1a; math \mathbb{E}_{\text{技}} \underbrace{\prod_{\text{Dis}} \text{TechnoCore}}_{\…

InnoDB引擎逻辑存储结构及架构

简化理解版 想象 InnoDB 是一个高效运转的仓库&#xff1a; 核心内存区 (大脑 & 高速缓存 - 干活超快的地方) 缓冲池 Buffer Pool (最最核心&#xff01;)&#xff1a; 作用&#xff1a; 相当于仓库的“高频货架”。把最常用的数据&#xff08;表数据、索引&#xff09;从…

贫血模型与充血模型:架构设计的分水岭

在企业级应用的架构设计中&#xff0c;贫血模型和充血模型一直是架构师们争论的热点话题。两者背后分别代表着“事务脚本模式”和“领域模型模式”两种截然不同的设计思想。而理解这两者的差异&#xff0c;有助于开发者根据实际业务场景做出更合理的架构决策。 贫血模型&#…

Linux的调试器--gbd/cgbd

1.引入 #include <stdio.h> int Sum(int s, int e) {int result 0;for(int i s; i < e; i){result i;}return result; } int main() {int start 1;int end 100;printf("I will begin\n");int n Sum(start, end);printf("running done, result i…