| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 网络协议 -> C++ websocket client/server library -> 正文阅读 |
|
[网络协议]C++ websocket client/server library |
Chapter 1: Initial Setup & BasicsSetting up the basic types, opening and closing connections, sending and receiving messages. Step 1A basic program loop that prompts the user for a command and then processes it. In this tutorial we will modify this program to perform tasks and retrieve data from a remote server over a WebSocket connection. Build
Code so farnote?A code snapshot for each step is present next to this tutorial file in the git repository. #include <iostream> #include <string> int main() { bool done = false; std::string input; while (!done) { std::cout << "Enter Command: "; std::getline(std::cin, input); if (input == "quit") { done = true; } else if (input == "help") { std::cout << "\nCommand List:\n" << "help: Display this help text\n" << "quit: Exit the program\n" << std::endl; } else { std::cout << "Unrecognized Command" << std::endl; } } return 0; } Step 2Add WebSocket++ includes and set up an endpoint type. WebSocket++ includes two major object types. The endpoint and the connection. The endpoint creates and launches new connections and maintains default settings for those connections. Endpoints also manage any shared network resources. The connection stores information specific to each WebSocket session.
Connections do not maintain a link back to their associated endpoint. Endpoints do not maintain a list of outstanding connections. If your application needs to iterate over all connections it will need to maintain a list of them itself. WebSocket++ endpoints are built by combining an endpoint role with an endpoint config. There are two different types of endpoint roles, one each for the client and server roles in a WebSocket session. This is a client tutorial so we will use the client role? WebSocket++ endpoints have a group of settings that may be configured at compile time via the? The endpoint role takes a template parameter called? Combine a config with an endpoint role to produce a fully configured endpoint. This type will be used frequently so I would recommend a typedef here.
BuildAdding WebSocket++ has added a few dependencies to our program that must be addressed in the build system. Firstly, the WebSocket++ and Boost library headers must be in the include search path of your build system. How exactly this is done depends on where you have the WebSocket++ headers installed and what build system you are using. In addition to the new headers, boost::asio depends on the?
Code so far#include <websocketpp/config/asio_no_tls_client.hpp> #include <websocketpp/client.hpp> #include <iostream> #include <string> typedef websocketpp::client<websocketpp::config::asio_client> client; int main() { bool done = false; std::string input; while (!done) { std::cout << "Enter Command: "; std::getline(std::cin, input); if (input == "quit") { done = true; } else if (input == "help") { std::cout << "\nCommand List:\n" << "help: Display this help text\n" << "quit: Exit the program\n" << std::endl; } else { std::cout << "Unrecognized Command" << std::endl; } } return 0; } Step 3Create endpoint wrapper object that handles initialization and setting up the background thread. In order to process user input while network processing occurs in the background we are going to use a separate thread for the WebSocket++ processing loop. This leaves the main thread free to process foreground user input. In order to enable simple RAII style resource management for our thread and endpoint we will use a wrapper object that configures them both in its constructor. Terminology: websocketpp::lib namespace WebSocket++ is designed to be used with a C++11 standard library. As this is not universally available in popular build systems the Boost libraries may be used as polyfills for the C++11 standard library in C++98 build environments. The? This tutorial uses the? >[TODO: link to more information about websocketpp::lib namespace and C++11 setup] Within the? First, we set the endpoint logging behavior to silent by clearing all of the access and error logging channels. [TODO: link to more information about logging] m_endpoint.clear_access_channels(websocketpp::log::alevel::all); m_endpoint.clear_error_channels(websocketpp::log::elevel::all); Next, we initialize the transport system underlying the endpoint and set it to perpetual mode. In perpetual mode the endpoint's processing loop will not exit automatically when it has no connections. This is important because we want this endpoint to remain active while our application is running and process requests for new WebSocket connections on demand as we need them. Both of these methods are specific to the asio transport. They will not be necessary or present in endpoints that use a non-asio config. m_endpoint.init_asio(); m_endpoint.start_perpetual(); Finally, we launch a thread to run the? m_thread.reset(new websocketpp::lib::thread(&client::run, &m_endpoint)); BuildNow that our client endpoint template is actually instantiated a few more linker dependencies will show up. In particular, WebSocket clients require a cryptographically secure random number generator. WebSocket++ is able to use either?
Code so far#include <websocketpp/config/asio_no_tls_client.hpp> #include <websocketpp/client.hpp> #include <websocketpp/common/thread.hpp> #include <websocketpp/common/memory.hpp> #include <iostream> #include <string> typedef websocketpp::client<websocketpp::config::asio_client> client; class websocket_endpoint { public: websocket_endpoint () { m_endpoint.clear_access_channels(websocketpp::log::alevel::all); m_endpoint.clear_error_channels(websocketpp::log::elevel::all); m_endpoint.init_asio(); m_endpoint.start_perpetual(); m_thread.reset(new websocketpp::lib::thread(&client::run, &m_endpoint)); } private: client m_endpoint; websocketpp::lib::shared_ptr<websocketpp::lib::thread> m_thread; }; int main() { bool done = false; std::string input; websocket_endpoint endpoint; while (!done) { std::cout << "Enter Command: "; std::getline(std::cin, input); if (input == "quit") { done = true; } else if (input == "help") { std::cout << "\nCommand List:\n" << "help: Display this help text\n" << "quit: Exit the program\n" << std::endl; } else { std::cout << "Unrecognized Command" << std::endl; } } return 0; } Step 4Opening WebSocket connections This step adds two new commands to utility_client. The ability to open a new connection and the ability to view information about a previously opened connection. Every connection that gets opened will be assigned an integer connection id that the user of the program can use to interact with that connection. New Connection Metadata ObjectIn order to track information about each connection a? Update <tt>websocket_endpoint</tt>The? The connect methodA new WebSocket connection is initiated via a three step process. First, a connection request is created by? Terminology <tt>connection_ptr</tt> WebSocket++ keeps track of connection related resources using a reference counted shared pointer. The type of this pointer is? When is it safe to use?
Terminology <tt>connection_hdl</tt> Because of the limited thread safety of the? Connection handles are not used directly. They are used by endpoint methods to identify the target of the desired action. For example, the endpoint method that sends a new message will take as a parameter the hdl of the connection to send the message to. When is it safe to use? **
If connection creation succeeds, the next sequential connection ID is generated and a? int new_id = m_next_id++; metadata_ptr metadata(new connection_metadata(new_id, con->get_handle(), uri)); m_connection_list[new_id] = metadata; Next, the connection request is configured. For this step the only configuration we will do is setting up a few default handlers. Later on we will return and demonstrate some more detailed configuration that can happen here (setting user agents, origin, proxies, custom headers, subprotocols, etc). Terminology: Registering handlers WebSocket++ provides a number of execution points where you can register to have a handler run. Which of these points are available to your endpoint will depend on its config. TLS handlers will not exist on non-TLS endpoints for example. A complete list of handlers can be found at?http://www.zaphoyd.com/websocketpp/manual/reference/handler-list. Handlers can be registered at the endpoint level and at the connection level. Endpoint handlers are copied into new connections as they are created. Changing an endpoint handler will affect only future connections. Handlers registered at the connection level will be bound to that specific connection only. The signature of handler binding methods is the same for endpoints and connections. The format is:? All handlers take one argument, a callable type that can be converted to a? The function signature of each handler can be looked up in the list above in the manual. In general, all handlers include the?
In this example we are going to set connection specific handlers that are bound directly to the metadata object associated with our connection. This allows us to avoid performing a lookup in each handler to find the metadata object we plan to update which is a bit more efficient. Lets look at the parameters being sent to bind in detail: con->set_open_handler(websocketpp::lib::bind( &connection_metadata::on_open, metadata, &m_endpoint, websocketpp::lib::placeholders::_1 ));
Finally, we call? Handler Member FunctionsThe open handler we registered,? The fail handler we registered,? New CommandsTwo new commands have been set up. "connect [uri]" will pass the URI to the? } else if (input.substr(0,7) == "connect") { int id = endpoint.connect(input.substr(8)); if (id != -1) { std::cout << "> Created connection with id " << id << std::endl; } } else if (input.substr(0,4) == "show") { int id = atoi(input.substr(5).c_str()); connection_metadata::ptr metadata = endpoint.get_metadata(id); if (metadata) { std::cout << *metadata << std::endl; } else { std::cout << "> Unknown connection id " << id << std::endl; } } BuildThere are no changes to the build instructions from step 3 RunEnter Command: connect not a websocket uri > Connect initialization error: invalid uri Enter Command: show 0 > Unknown connection id 0 Enter Command: connect ws://echo.websocket.org > Created connection with id 0 Enter Command: show 0 > URI: ws://echo.websocket.org > Status: Open > Remote Server: Kaazing Gateway > Error/close reason: N/A Enter Command: connect ws://wikipedia.org > Created connection with id 1 Enter Command: show 1 > URI: ws://wikipedia.org > Status: Failed > Remote Server: Apache > Error/close reason: Invalid HTTP status. Code so far#include <websocketpp/config/asio_no_tls_client.hpp> #include <websocketpp/client.hpp> #include <websocketpp/common/thread.hpp> #include <websocketpp/common/memory.hpp> #include <cstdlib> #include <iostream> #include <map> #include <string> #include <sstream> typedef websocketpp::client<websocketpp::config::asio_client> client; class connection_metadata { public: typedef websocketpp::lib::shared_ptr<connection_metadata> ptr; connection_metadata(int id, websocketpp::connection_hdl hdl, std::string uri) : m_id(id) , m_hdl(hdl) , m_status("Connecting") , m_uri(uri) , m_server("N/A") {} void on_open(client * c, websocketpp::connection_hdl hdl) { m_status = "Open"; client::connection_ptr con = c->get_con_from_hdl(hdl); m_server = con->get_response_header("Server"); } void on_fail(client * c, websocketpp::connection_hdl hdl) { m_status = "Failed"; client::connection_ptr con = c->get_con_from_hdl(hdl); m_server = con->get_response_header("Server"); m_error_reason = con->get_ec().message(); } friend std::ostream & operator<< (std::ostream & out, connection_metadata const & data); private: int m_id; websocketpp::connection_hdl m_hdl; std::string m_status; std::string m_uri; std::string m_server; std::string m_error_reason; }; std::ostream & operator<< (std::ostream & out, connection_metadata const & data) { out << "> URI: " << data.m_uri << "\n" << "> Status: " << data.m_status << "\n" << "> Remote Server: " << (data.m_server.empty() ? "None Specified" : data.m_server) << "\n" << "> Error/close reason: " << (data.m_error_reason.empty() ? "N/A" : data.m_error_reason); return out; } class websocket_endpoint { public: websocket_endpoint () : m_next_id(0) { m_endpoint.clear_access_channels(websocketpp::log::alevel::all); m_endpoint.clear_error_channels(websocketpp::log::elevel::all); m_endpoint.init_asio(); m_endpoint.start_perpetual(); m_thread.reset(new websocketpp::lib::thread(&client::run, &m_endpoint)); } int connect(std::string const & uri) { websocketpp::lib::error_code ec; client::connection_ptr con = m_endpoint.get_connection(uri, ec); if (ec) { std::cout << "> Connect initialization error: " << ec.message() << std::endl; return -1; } int new_id = m_next_id++; connection_metadata::ptr metadata_ptr(new connection_metadata(new_id, con->get_handle(), uri)); m_connection_list[new_id] = metadata_ptr; con->set_open_handler(websocketpp::lib::bind( &connection_metadata::on_open, metadata_ptr, &m_endpoint, websocketpp::lib::placeholders::_1 )); con->set_fail_handler(websocketpp::lib::bind( &connection_metadata::on_fail, metadata_ptr, &m_endpoint, websocketpp::lib::placeholders::_1 )); m_endpoint.connect(con); return new_id; } connection_metadata::ptr get_metadata(int id) const { con_list::const_iterator metadata_it = m_connection_list.find(id); if (metadata_it == m_connection_list.end()) { return connection_metadata::ptr(); } else { return metadata_it->second; } } private: typedef std::map<int,connection_metadata::ptr> con_list; client m_endpoint; websocketpp::lib::shared_ptr<websocketpp::lib::thread> m_thread; con_list m_connection_list; int m_next_id; }; int main() { bool done = false; std::string input; websocket_endpoint endpoint; while (!done) { std::cout << "Enter Command: "; std::getline(std::cin, input); if (input == "quit") { done = true; } else if (input == "help") { std::cout << "\nCommand List:\n" << "connect <ws uri>\n" << "show <connection id>\n" << "help: Display this help text\n" << "quit: Exit the program\n" << std::endl; } else if (input.substr(0,7) == "connect") { int id = endpoint.connect(input.substr(8)); if (id != -1) { std::cout << "> Created connection with id " << id << std::endl; } } else if (input.substr(0,4) == "show") { int id = atoi(input.substr(5).c_str()); connection_metadata::ptr metadata = endpoint.get_metadata(id); if (metadata) { std::cout << *metadata << std::endl; } else { std::cout << "> Unknown connection id " << id << std::endl; } } else { std::cout << "> Unrecognized Command" << std::endl; } } return 0; } Step 5Closing connections This step adds a command that allows you to close a WebSocket connection and adjusts the quit command so that it cleanly closes all outstanding connections before quitting. Getting connection close information out of WebSocket++Terminology: WebSocket close codes & reasons The WebSocket close handshake involves an exchange of optional machine readable close codes and human readable reason strings. Each endpoint sends independent close details. The codes are short integers. The reasons are UTF8 text strings of at most 125 characters. More details about valid close code ranges and the meaning of each code can be found at?https://tools.ietf.org/html/rfc6455#section-7.4 The? During the close handler call WebSocket++ connections offer the following methods for accessing close handshake information:
Note:?there are some special close codes that will report a code that was not actually sent on the wire. For example 1005/"no close code" indicates that the endpoint omitted a close code entirely and 1006/"abnormal close" indicates that there was a problem that resulted in the connection closing without having performed a close handshake. Add close handlerThe? void on_close(client * c, websocketpp::connection_hdl hdl) { m_status = "Closed"; client::connection_ptr con = c->get_con_from_hdl(hdl); std::stringstream s; s << "close code: " << con->get_remote_close_code() << " (" << websocketpp::close::status::get_string(con->get_remote_close_code()) << "), close reason: " << con->get_remote_close_reason(); m_error_reason = s.str(); } Similarly to? Add close method to <tt>websocket_endpoint</tt>This method starts by looking up the given connection ID in the connection list. Next a close request is sent to the connection's handle with the specified WebSocket close code. This is done by calling? void close(int id, websocketpp::close::status::value code) { websocketpp::lib::error_code ec; con_list::iterator metadata_it = m_connection_list.find(id); if (metadata_it == m_connection_list.end()) { std::cout << "> No connection found with id " << id << std::endl; return; } m_endpoint.close(metadata_it->second->get_hdl(), code, "", ec); if (ec) { std::cout << "> Error initiating close: " << ec.message() << std::endl; } } Add close option to the command loop and help messageA close option is added to the command loop. It takes a connection ID and optionally a close code and a close reason. If no code is specified the default of 1000/Normal is used. If no reason is specified, none is sent. The? An entry is also added to the help system to describe how the new command may be used. else if (input.substr(0,5) == "close") { std::stringstream ss(input); std::string cmd; int id; int close_code = websocketpp::close::status::normal; std::string reason; ss >> cmd >> id >> close_code; std::getline(ss,reason); endpoint.close(id, close_code, reason); } Close all outstanding connections in <tt>websocket_endpoint</tt> destructorUntil now quitting the program left outstanding connections and the WebSocket++ network thread in a lurch. Now that we have a method of closing connections we can clean this up properly. The destructor for? ~websocket_endpoint() { m_endpoint.stop_perpetual(); for (con_list::const_iterator it = m_connection_list.begin(); it != m_connection_list.end(); ++it) { if (it->second->get_status() != "Open") { // Only close open connections continue; } std::cout << "> Closing connection " << it->second->get_id() << std::endl; websocketpp::lib::error_code ec; m_endpoint.close(it->second->get_hdl(), websocketpp::close::status::going_away, "", ec); if (ec) { std::cout << "> Error closing connection " << it->second->get_id() << ": " << ec.message() << std::endl; } } m_thread->join(); } BuildThere are no changes to the build instructions from step 4 RunEnter Command: connect ws://localhost:9002 > Created connection with id 0 Enter Command: close 0 1001 example message Enter Command: show 0 > URI: ws://localhost:9002 > Status: Closed > Remote Server: WebSocket++/0.4.0 > Error/close reason: close code: 1001 (Going away), close reason: example message Enter Command: connect ws://localhost:9002 > Created connection with id 1 Enter Command: close 1 1006 > Error initiating close: Invalid close code used Enter Command: quit > Closing connection 1 Step 6Sending and receiving messages This step adds a command to send a message on a given connection and updates the show command to print a transcript of all sent and received messages for that connection. Terminology: WebSocket message types (opcodes) WebSocket messages have types indicated by their opcode. The protocol currently specifies two different opcodes for data messages, text and binary. Text messages represent UTF8 text and will be validated as such. Binary messages represent raw binary bytes and are passed through directly with no validation. WebSocket++ provides the values? Sending MessagesMessages are sent using? Each method takes a? The first overload,? The second overload,? The third overload,?
Add send method to <tt>websocket_endpoint</tt>Like the close method, send will start by looking up the given connection ID in the connection list. Next a send request is sent to the connection's handle with the specified WebSocket message and the text opcode. Finally, we record the sent message with our connection metadata object so later our show connection command can print a list of messages sent. void send(int id, std::string message) { websocketpp::lib::error_code ec; con_list::iterator metadata_it = m_connection_list.find(id); if (metadata_it == m_connection_list.end()) { std::cout << "> No connection found with id " << id << std::endl; return; } m_endpoint.send(metadata_it->second->get_hdl(), message, websocketpp::frame::opcode::text, ec); if (ec) { std::cout << "> Error sending message: " << ec.message() << std::endl; return; } metadata_it->second->record_sent_message(message); } Add send option to the command loop and help messageA send option is added to the command loop. It takes a connection ID and a text message to send. An entry is also added to the help system to describe how the new command may be used. else if (input.substr(0,4) == "send") { std::stringstream ss(input); std::string cmd; int id; std::string message = ""; ss >> cmd >> id; std::getline(ss,message); endpoint.send(id, message); } Add glue to <tt>connection_metadata</tt> for storing sent messagesIn order to store messages sent on this connection some code is added to? void record_sent_message(std::string message) { m_messages.push_back(">> " + message); } Finally the connection metadata output operator is updated to also print a list of processed messages: out << "> Messages Processed: (" << data.m_messages.size() << ") \n"; std::vector<std::string>::const_iterator it; for (it = data.m_messages.begin(); it != data.m_messages.end(); ++it) { out << *it << "\n"; } Receiving MessagesMessages are received by registering a message handler. This handler will be called once per message received and its signature is? Add a message handler to method to <tt>connection_metadata</tt>The message receiving behave that we are implementing will be to collect all messages sent and received and to print them in order when the show connection command is run. The sent messages are already being added to that list. Now we add a message handler that pushes received messages to the list as well. Text messages are pushed as-is. Binary messages are first converted to printable hexadecimal format. void on_message(websocketpp::connection_hdl hdl, client::message_ptr msg) { if (msg->get_opcode() == websocketpp::frame::opcode::text) { m_messages.push_back(msg->get_payload()); } else { m_messages.push_back(websocketpp::utility::to_hex(msg->get_payload())); } } In order to have this handler called when new messages are received we also register it with our connection. Note that unlike most other handlers, the message handler has two parameters and thus needs two placeholders. con->set_message_handler(websocketpp::lib::bind( &connection_metadata::on_message, metadata_ptr, websocketpp::lib::placeholders::_1, websocketpp::lib::placeholders::_2 )); BuildThere are no changes to the build instructions from step 5 RunIn this example run we are connecting to the WebSocket++ example echo_server. This server will repeat any message we send back to it. You can also try testing this with the echo server at? Enter Command: connect ws://localhost:9002 > Created connection with id 0 Enter Command: send 0 example message Enter Command: show 0 > URI: ws://localhost:9002 > Status: Open > Remote Server: WebSocket++/0.4.0 > Error/close reason: N/A > Messages Processed: (2) >> example message << example message Step 7Using TLS / Secure WebSockets
Chapter 2: Intermediate FeaturesStep 8Intermediate level features
Misc stuff not sure if it should be included here or elsewhere?core websocket++ control flow. A handshake, followed by a split into 2 independent control strands
Important observations Handlers run in line with library processing which has several implications applications should be aware of: |
|
网络协议 最新文章 |
使用Easyswoole 搭建简单的Websoket服务 |
常见的数据通信方式有哪些? |
Openssl 1024bit RSA算法---公私钥获取和处 |
HTTPS协议的密钥交换流程 |
《小白WEB安全入门》03. 漏洞篇 |
HttpRunner4.x 安装与使用 |
2021-07-04 |
手写RPC学习笔记 |
K8S高可用版本部署 |
mySQL计算IP地址范围 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/26 6:35:20- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |