概述
這是一個會議或演講的概述內容,主要介紹一個關于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,以滿足高級需求。
總結
這部分強調:
- 自動化測試的重要性(尤其在跨平臺項目中);
- 構建系統與平臺兼容性要簡單、靈活、可維護;
- 設計庫時給用戶留下自由度和擴展性,這能提高庫的實用價值和用戶滿意度。