W01. WebSocketエコーサーバー/クライアントを実装する
WebSocketは、クライアントとサーバーの間で双方向にメッセージをやり取りするためのプロトコルです。cpp-httplibはサーバーとクライアントの両方のAPIを提供しています。まずは一番シンプルなエコーサーバーから見てみましょう。
サーバー: エコーサーバー
#include <httplib.h> int main() { httplib::Server svr; svr.WebSocket("/echo", [](const httplib::Request &req, httplib::ws::WebSocket &ws) { std::string msg; while (ws.is_open()) { auto result = ws.read(msg); if (result == httplib::ws::ReadResult::Fail) { break; } ws.send(msg); // 受け取った内容をそのまま返す } }); svr.listen("0.0.0.0", 8080); }
#include <httplib.h> int main() { httplib::Server svr; svr.WebSocket("/echo", [](const httplib::Request &req, httplib::ws::WebSocket &ws) { std::string msg; while (ws.is_open()) { auto result = ws.read(msg); if (result == httplib::ws::ReadResult::Fail) { break; } ws.send(msg); // 受け取った内容をそのまま返す } }); svr.listen("0.0.0.0", 8080); }
svr.WebSocket()でWebSocket用のハンドラを登録します。ハンドラが呼ばれた時点で、すでにWebSocketのハンドシェイクは完了しています。ループの中でws.read()してws.send()するだけで、エコー動作が完成します。
read()の返り値はReadResult列挙値で、次の3種類です。
ReadResult::Text: テキストメッセージを受信ReadResult::Binary: バイナリメッセージを受信ReadResult::Fail: エラー、または接続が閉じた
クライアント: エコーを叩く
#include <httplib.h> int main() { httplib::ws::WebSocketClient cli("ws://localhost:8080/echo"); if (!cli.connect()) { std::cerr << "failed to connect" << std::endl; return 1; } cli.send("Hello, WebSocket!"); std::string msg; if (cli.read(msg) != httplib::ws::ReadResult::Fail) { std::cout << "received: " << msg << std::endl; } cli.close(); }
#include <httplib.h> int main() { httplib::ws::WebSocketClient cli("ws://localhost:8080/echo"); if (!cli.connect()) { std::cerr << "failed to connect" << std::endl; return 1; } cli.send("Hello, WebSocket!"); std::string msg; if (cli.read(msg) != httplib::ws::ReadResult::Fail) { std::cout << "received: " << msg << std::endl; } cli.close(); }
URLにはws://(平文)またはwss://(TLS)を指定します。connect()でハンドシェイクを行い、あとはsend()とread()でサーバーと同じAPIでやり取りできます。
テキストとバイナリの送り分け
send()には2つのオーバーロードがあり、テキストとバイナリで使い分けられます。
ws.send("Hello"); // テキストフレーム ws.send(binary_data, binary_data_size); // バイナリフレーム
ws.send("Hello"); // テキストフレーム ws.send(binary_data, binary_data_size); // バイナリフレーム
std::stringを受け取るオーバーロードはテキスト、const char*とサイズを受け取るオーバーロードはバイナリとして送られます。詳しくはW04. バイナリフレームを送受信するを参照してください。
スレッドとの関係
WebSocket接続はハンドラが終わるまで生き続けるので、1接続につきワーカースレッドを1つ占有します。同時接続数が多い場合は、スレッドプールを動的スケーリングに設定しましょう。
svr.new_task_queue = [] { return new httplib::ThreadPool(8, 128); };
svr.new_task_queue = [] { return new httplib::ThreadPool(8, 128); };
詳細はS21. マルチスレッド数を設定するを参照してください。
Note: HTTPSサーバーの上でWebSocketを動かしたいときは、
httplib::Serverの代わりにhttplib::SSLServerを使えば、同じWebSocket()ハンドラがそのまま動きます。クライアント側はwss://スキームを指定するだけです。