WebSocketpp
WebSocketpp 簡介
WebSocket
是 HTML5 開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。WebSocket 使得 CS 之間的數據交換變得更加簡單,允許 S 主動向 C 推送數據。WebSocket 和 HTTP 一樣都是應用層的一種協議,可以使用各種編程語言實現之。
WebSocketpp
是一個僅僅由頭文件構成的 C++庫,它實現了 WebSocket 協議(rfc 6455
),可以在項目中方便簡潔地實現客戶端和服務器。WebSocketpp 底層使用了 C++ 的 iostream 庫和基於 Boost 的 Asio 庫。
下載安裝
如前所述,WebSocketpp 基於 Boost 的 Asio 庫,並且如果要在項目中實現wss
,則還需要 libssl-dev 庫的支持。
- 安裝 Boost 庫。
- 安裝 libssl-dev 庫。
- 安裝 WebSocketpp 庫。
- 去官網下載源碼,WebSocketpp
- 創建
build
目錄,用來存放cmake
生成的中間文件。 cd build && cmake ..
,執行MakeList.txt
。make && sudo make install
,安裝 WebSocket 庫,默認放在/usr/local/include
中。
當前,WebSocketpp 0.8.1 最高兼容的 Boost 版本是 1.69.0,WebSocketpp 的兼容性問題
編譯執行樣例
# 編譯服務器
cd websocketpp/examples/echo_server
g++ echo_server.cpp -o echo_server -lboost_system -lpthread
./echo_server
# 編譯客戶端
cd websocketpp/examples/echo_client
g++ echo_client.cpp -o echo_client -lboost_system -lpthread
./echo_client
WebSocket 基本概念
如果要在項目中利用 WebSocketpp 實現 WebSocket 客戶端或服務器,基本要include
兩種頭文件,一種是決定編程角色的頭文件(Role),另一種是配置編程角色的配置頭文件(Config)。
Role
Role 有兩種,分別是 Server 和 Client,很好理解。
- 如果開發的是客戶端(Client),則要
include <websocketpp/client.hpp>
,後面就可以使用websocketpp::client<>
模板類來創建不同配置的客戶端。 - 如果開發的是服務器(Server),則要
include <websocketpp/server.hpp>
,後面就可以使用websocketpp::server<>
模板類來創建不同配置的服務器。
Config
Config 主要用來配置 client 或者 server 的屬性,WebSocketpp 主要提供了 3 種配置:
config::core
,有限功能的配置,基於 C++11 的功能,不需要 Boost 庫支持。config::asio
,使用 Boost 的 Asio 庫,提供完整的 ws 功能。config::asio_tls
,使用 Boost 的 Asio 庫,提供完整的 wss 功能。
這樣,根據不同的 Role 和 Config 組合,可以實現各種各類的 Client 和 Server,如下:
Server_config | Server | Client | Client_config |
---|---|---|---|
core | core.hpp | core_client.hpp | core_client |
asio | asio_no_tls.hpp | asio_no_tls_client.hpp | asio_client |
asio_tls | asio.hpp | asio_client.hpp | asio_tls_client |
如需要開發一個基於 Boost Asio,使用 ws 的服務器的話,要包含的頭文件如下:
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
EndPoint
EndPoint 看成是最後需要實現的具體的物件,即哪種配置的服務器或者客戶端。
如需要開發一個基於 Boost Asio,使用 ws 的服務器的話,完整的開頭應該如下:
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
typedef websocketpp::server<websocketpp::config::asio> server;
WebSocketpp 框架的基本步驟
相關基礎介紹
websocketpp::connection_hdl
,用來識別當前連接用的,可以通過endpoint.get_con_formhdl
獲取連接信息。websocketpp::server<>::message_ptr
,保存了收到的數據,msg.get_payload()
返回的是具體的數據,get_opcode()
返回的是數據的編碼格式。endpoint.send(hdl, data_str, data_code)
,用於發送數據,需要指明發送的內容和編碼格式。- 主動關閉連接:
conn->close(cvValue, strReason)
- 綁定的事件列表,詳細可查看事件列表
open([endpint *,] connection_hdl);
fail([endpint *,] connection_hdl);
close([endpint *,] connection_hdl);
message([endpint *,] connection_hdl, message_ptr);
tls_context_ptr tls_init(connection_hdl);
endpoint.set_open_handler(std::bind(&on_open, &endpoint, ::_1 ));
endpoint.set_fail_handler(std::bind(&on_fail, &endpoint, ::_1 ));
endpoint.set_message_handler(std::bind(&on_message, &endpoint, ::_1, ::_2 ));
endpoint.set_close_handler(std::bind(&on_close, &endpoint, ::_1 ));
Client
- 創建一個 client。
- 初始化 client。
init_asio()
,初始化內部的 Boost 的 Asio,作爲後續連接使用。start_perpetual()
,設置 client 爲永久執行,這樣就可以多次連接,否則只能進行一次連接,非必須。
- 綁定事件回調函數,主要有
onopen(), onclose(), onfail(), onmessage()
等。 - 建立連接。
get_connection()
,獲取連接。connect()
,開始連接。
- 開始執行主循環。
#include <websocketpp/config/asio_no_tls_client.hpp>
#include <websocketpp/client.hpp>
#include <iostream>
using websocketpp::lib::bind;
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
typedef websocketpp::client<websocketpp::config::asio_client> client;
typedef websocketpp::config::asio_client::message_type::ptr message_ptr;
// 消息回調函數
void on_message(client *c, websocketpp::connection_hdl hdl, message_ptr msg)
{
std::cout << "on_message called with hdl: " << hdl.lock().get()
<< " and message: " << msg->get_payload()
<< std::endl;
websocketpp::lib::error_code ec;
c->send(hdl, msg->get_payload(), msg->get_opcode(), ec);
if (ec)
{
std::cout << "Echo failed because: " << ec.message() << std::endl;
}
}
int main()
{
client c; // 創建一個client
std::string uri = "ws://localhost:9002";
try
{
// 設置日誌
c.set_access_channels(websocketpp::log::alevel::all);
c.clear_access_channels(websocketpp::log::alevel::frame_payload);
// 初始化asio
c.init_asio();
// 綁定消息回調函數
c.set_message_handler(bind(&on_message, &c, ::_1, ::_2));
// 創建連接
websocketpp::lib::error_code ec;
client::connection_ptr con = c.get_connection(uri, ec);
if (ec)
{
std::cout << "could not create connection because: " << ec.message() << std::endl;
return 0;
}
// 建立連接
c.connect(con);
// 開始主循環
c.run();
}
catch (websocketpp::exception const &e)
{
std::cout << e.what() << std::endl;
}
return 0;
}
服務器
- 創建一個 server。
- 初始化 server。
init_asio()
,初始化內部的 Boost 的 Asio,作爲後續連接使用。
- 綁定事件回調函數,主要有
onopen(), onclose(), onfail(), onmessage()
等。 - 接受客戶端連接,
start_accept()
- 開始執行主循環。
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
#include <iostream>
using websocketpp::lib::bind;
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
typedef websocketpp::server<websocketpp::config::asio> server;
typedef server::message_ptr message_ptr;
// 消息回調函數
void on_message(server *s, websocketpp::connection_hdl hdl, message_ptr msg)
{
std::cout << "on_message called with hdl: " << hdl.lock().get()
<< " and message: " << msg->get_payload()
<< std::endl;
if (msg->get_payload() == "stop-listening")
{
s->stop_listening();
return;
}
try
{
s->send(hdl, msg->get_payload(), msg->get_opcode());
}
catch (websocketpp::exception const &e)
{
std::cout << "Echo failed because: "
<< "(" << e.what() << ")" << std::endl;
}
}
int main()
{
// 創建一個實例
server echo_server;
try
{
// 設置日誌
echo_server.set_access_channels(websocketpp::log::alevel::all);
echo_server.clear_access_channels(websocketpp::log::alevel::frame_payload);
// 初始化Asio
echo_server.init_asio();
// 綁定消息回調函數
echo_server.set_message_handler(bind(&on_message, &echo_server, ::_1, ::_2));
// 監聽9002端口
echo_server.listen(9002);
// 開啓接受服務
echo_server.start_accept();
// 開始主循環
echo_server.run();
}
catch (websocketpp::exception const &e)
{
std::cout << e.what() << std::endl;
}
catch (...)
{
std::cout << "other exception" << std::endl;
}
}
wss 協議
#include <websocketpp/config/asio_client.hpp>
#include <websocketpp/client.hpp>
#include <iostream>
typedef websocketpp::client<websocketpp::config::asio_tls_client> client;
typedef std::shared_ptr<boost::asio::ssl::context> context_ptr;
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;
typedef websocketpp::config::asio_client::message_type::ptr message_ptr;
void on_message(client* c, websocketpp::connection_hdl hdl, message_ptr msg) {
std::cout << "on_message called with hdl: " << hdl.lock().get()
<< " and message: " << msg->get_payload()
<< std::endl;
websocketpp::lib::error_code ec;
c->send(hdl, msg->get_payload(), msg->get_opcode(), ec);
if (ec) {
std::cout << "Echo failed because: " << ec.message() << std::endl;
}
}
static context_ptr on_tls_init() {
// establishes a SSL connection
context_ptr ctx = std::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::sslv23);
try {
ctx->set_options(boost::asio::ssl::context::default_workarounds |
boost::asio::ssl::context::no_sslv2 |
boost::asio::ssl::context::no_sslv3 |
boost::asio::ssl::context::single_dh_use);
} catch (std::exception &e) {
std::cout << "Error in context pointer: " << e.what() << std::endl;
}
return ctx;
}
int main(int argc, char* argv[]) {
client c;
std::string uri = "wss://localhost";
c.set_access_channels(websocketpp::log::alevel::all);
c.clear_access_channels(websocketpp::log::alevel::frame_payload);
c.init_asio();
c.set_tls_init_handler(bind(&on_tls_init)); // wss協議
c.set_message_handler(bind(&on_message,&c,::_1,::_2));
websocketpp::lib::error_code ec;
client::connection_ptr con = c.get_connection(uri, ec);
if (ec) {
std::cout << "could not create connection because: " << ec.message() << std::endl;
return 0;
}
c.connect(con);
c.run();
}