mirror of
https://github.com/EvernodeXRPL/hpcore.git
synced 2026-06-06 18:26:55 +00:00
Implemented socket message templates. (#40)
Implemented socket message templates to support broadcast (shared_ptr) and to achieve buffer zero-copy.
This commit is contained in:
@@ -1,93 +0,0 @@
|
||||
#include <iostream>
|
||||
#include "socket_client.hpp"
|
||||
#include "../hplog.hpp"
|
||||
|
||||
using tcp = net::ip::tcp;
|
||||
using error = boost::system::error_code;
|
||||
|
||||
namespace sock
|
||||
{
|
||||
|
||||
socket_client::socket_client(net::io_context &ioc, socket_session_handler &session_handler)
|
||||
: resolver_(net::make_strand(ioc)), ws_(net::make_strand(ioc)), sess_handler_(session_handler)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point to socket client which will intiate a connection to server
|
||||
*/
|
||||
// boost async_resolve function requires a port as a string because of that port is passed as a string
|
||||
void socket_client::run(std::string_view host, std::string_view port)
|
||||
{
|
||||
host_ = host;
|
||||
port_ = port;
|
||||
|
||||
// Look up the domain name
|
||||
resolver_.async_resolve(
|
||||
host,
|
||||
port,
|
||||
[self = shared_from_this()](error ec, tcp::resolver::results_type results) {
|
||||
self->on_resolve(ec, results);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes on completion of resolving the server
|
||||
*/
|
||||
void socket_client::on_resolve(error ec, tcp::resolver::results_type results)
|
||||
{
|
||||
if (ec)
|
||||
socket_client_fail(ec, "socket_client_resolve");
|
||||
|
||||
// Make the connection on the IP address we get from a lookup
|
||||
beast::get_lowest_layer(ws_).async_connect(
|
||||
results,
|
||||
[self = shared_from_this()](error ec, tcp::resolver::results_type::endpoint_type type) {
|
||||
self->on_connect(ec, type);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes on completion of connecting to the server
|
||||
*/
|
||||
void socket_client::on_connect(error ec, tcp::resolver::results_type::endpoint_type)
|
||||
{
|
||||
if (ec)
|
||||
socket_client_fail(ec, "socket_client_connect");
|
||||
|
||||
// Turn off the timeout on the tcp_stream, because
|
||||
// the websocket stream has its own timeout system.
|
||||
beast::get_lowest_layer(ws_).expires_never();
|
||||
|
||||
// Set suggested timeout settings for the websocket
|
||||
ws_.set_option(
|
||||
websocket::stream_base::timeout::suggested(
|
||||
beast::role_type::client));
|
||||
|
||||
// Perform the websocket handshake
|
||||
ws_.async_handshake(host_, "/",
|
||||
[self = shared_from_this()](error ec) {
|
||||
self->on_handshake(ec);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes on completion of handshake
|
||||
*/
|
||||
void socket_client::on_handshake(error ec)
|
||||
{
|
||||
//Creates a new socket session object
|
||||
std::make_shared<socket_session>(
|
||||
ws_, sess_handler_)
|
||||
->client_run(std::move(host_), std::move(port_), ec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes on error
|
||||
*/
|
||||
void socket_client::socket_client_fail(beast::error_code ec, char const *what)
|
||||
{
|
||||
LOG_ERR << what << ": " << ec.message();
|
||||
}
|
||||
|
||||
} // namespace sock
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <boost/beast.hpp>
|
||||
#include "socket_session.hpp"
|
||||
#include "socket_session_handler.hpp"
|
||||
#include "../hplog.hpp"
|
||||
|
||||
namespace beast = boost::beast;
|
||||
namespace net = boost::asio;
|
||||
@@ -20,13 +21,14 @@ namespace sock
|
||||
* Represents an active WebSocket client connection
|
||||
* Based on the implementation from https://github.com/vinniefalco/CppCon2018
|
||||
*/
|
||||
class socket_client : public std::enable_shared_from_this<socket_client>
|
||||
template <class T>
|
||||
class socket_client : public std::enable_shared_from_this<socket_client<T>>
|
||||
{
|
||||
tcp::resolver resolver_; // resolver used to resolve host and the port
|
||||
websocket::stream<beast::tcp_stream> ws_; // web socket stream used to send and receive messages
|
||||
std::string host_; // address of the server in which the client connects
|
||||
std::string port_; // port of the server in which client connects
|
||||
socket_session_handler &sess_handler_; // handler passed to gain access to websocket events
|
||||
tcp::resolver resolver; // resolver used to resolve host and the port
|
||||
websocket::stream<beast::tcp_stream> ws; // web socket stream used to send and receive messages
|
||||
std::string host; // address of the server in which the client connects
|
||||
std::string port; // port of the server in which client connects
|
||||
socket_session_handler<T> &sess_handler_; // handler passed to gain access to websocket events
|
||||
|
||||
void on_resolve(error ec, tcp::resolver::results_type results);
|
||||
|
||||
@@ -42,10 +44,99 @@ class socket_client : public std::enable_shared_from_this<socket_client>
|
||||
|
||||
public:
|
||||
// Resolver and socket require an io_context
|
||||
socket_client(net::io_context &ioc, socket_session_handler &session_handler);
|
||||
socket_client(net::io_context &ioc, socket_session_handler<T> &session_handler);
|
||||
|
||||
//Entry point to the client which requires an active host and port
|
||||
void run(std::string_view host, std::string_view port);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
socket_client<T>::socket_client(net::io_context &ioc, socket_session_handler<T> &session_handler)
|
||||
: resolver(net::make_strand(ioc)), ws(net::make_strand(ioc)), sess_handler_(session_handler)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point to socket client which will intiate a connection to server
|
||||
*/
|
||||
// boost async_resolve function requires a port as a string because of that port is passed as a string
|
||||
template <class T>
|
||||
void socket_client<T>::run(std::string_view host, std::string_view port)
|
||||
{
|
||||
this->host = host;
|
||||
this->port = port;
|
||||
|
||||
// Look up the domain name
|
||||
resolver.async_resolve(
|
||||
host,
|
||||
port,
|
||||
[self = this->shared_from_this()](error ec, tcp::resolver::results_type results) {
|
||||
self->on_resolve(ec, results);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes on completion of resolving the server
|
||||
*/
|
||||
template <class T>
|
||||
void socket_client<T>::on_resolve(error ec, tcp::resolver::results_type results)
|
||||
{
|
||||
if (ec)
|
||||
socket_client_fail(ec, "socket_client_resolve");
|
||||
|
||||
// Make the connection on the IP address we get from a lookup
|
||||
beast::get_lowest_layer(ws).async_connect(
|
||||
results,
|
||||
[self = this->shared_from_this()](error ec, tcp::resolver::results_type::endpoint_type type) {
|
||||
self->on_connect(ec, type);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes on completion of connecting to the server
|
||||
*/
|
||||
template <class T>
|
||||
void socket_client<T>::on_connect(error ec, tcp::resolver::results_type::endpoint_type)
|
||||
{
|
||||
if (ec)
|
||||
socket_client_fail(ec, "socket_client_connect");
|
||||
|
||||
// Turn off the timeout on the tcp_stream, because
|
||||
// the websocket stream has its own timeout system.
|
||||
beast::get_lowest_layer(ws).expires_never();
|
||||
|
||||
// Set suggested timeout settings for the websocket
|
||||
ws.set_option(
|
||||
websocket::stream_base::timeout::suggested(
|
||||
beast::role_type::client));
|
||||
|
||||
// Perform the websocket handshake
|
||||
ws.async_handshake(host, "/",
|
||||
[self = this->shared_from_this()](error ec) {
|
||||
self->on_handshake(ec);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes on completion of handshake
|
||||
*/
|
||||
template <class T>
|
||||
void socket_client<T>::on_handshake(error ec)
|
||||
{
|
||||
//Creates a new socket session object
|
||||
std::make_shared<socket_session<T>>(
|
||||
ws, sess_handler_)
|
||||
->client_run(std::move(host), std::move(port), ec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes on error
|
||||
*/
|
||||
template <class T>
|
||||
void socket_client<T>::socket_client_fail(beast::error_code ec, char const *what)
|
||||
{
|
||||
LOG_ERR << what << ": " << ec.message();
|
||||
}
|
||||
|
||||
} // namespace sock
|
||||
#endif
|
||||
@@ -1,112 +0,0 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include "socket_server.hpp"
|
||||
#include "../hplog.hpp"
|
||||
|
||||
namespace net = boost::asio; // namespace asio
|
||||
|
||||
using tcp = net::ip::tcp;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
namespace sock
|
||||
{
|
||||
|
||||
socket_server::socket_server(net::io_context &ioc, tcp::endpoint endpoint, socket_session_handler &session_handler)
|
||||
: acceptor_(ioc), socket_(ioc),sess_handler_(session_handler)
|
||||
{
|
||||
error_code ec;
|
||||
|
||||
// Open the acceptor
|
||||
acceptor_.open(endpoint.protocol(), ec);
|
||||
if (ec)
|
||||
{
|
||||
fail(ec, "open");
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow address reuse
|
||||
acceptor_.set_option(net::socket_base::reuse_address(true));
|
||||
if (ec)
|
||||
{
|
||||
fail(ec, "set_option");
|
||||
return;
|
||||
}
|
||||
|
||||
// Bind to the server address
|
||||
acceptor_.bind(endpoint, ec);
|
||||
if (ec)
|
||||
{
|
||||
fail(ec, "bind");
|
||||
return;
|
||||
}
|
||||
|
||||
// Start listening for connections
|
||||
acceptor_.listen(
|
||||
net::socket_base::max_listen_connections, ec);
|
||||
if (ec)
|
||||
{
|
||||
fail(ec, "listen");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point to socket server which accepts new connections
|
||||
*/
|
||||
void socket_server::run()
|
||||
{
|
||||
|
||||
// Start accepting a connection
|
||||
acceptor_.async_accept(
|
||||
socket_,
|
||||
[self = shared_from_this()](error_code ec) {
|
||||
self->on_accept(ec);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes on error
|
||||
*/
|
||||
void socket_server::fail(error_code ec, char const *what)
|
||||
{
|
||||
// Don't report on canceled operations
|
||||
if (ec == net::error::operation_aborted)
|
||||
return;
|
||||
LOG_ERR << what << ": " << ec.message();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes on acceptance of new connection
|
||||
*/
|
||||
void socket_server::on_accept(error_code ec)
|
||||
{
|
||||
if (ec)
|
||||
{
|
||||
return fail(ec, "accept");
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string port = std::to_string(socket_.remote_endpoint().port());
|
||||
std::string address = socket_.remote_endpoint().address().to_string();
|
||||
|
||||
//Creating websocket stream required to pass to initiate a new session
|
||||
websocket::stream<beast::tcp_stream> ws(std::move(socket_));
|
||||
|
||||
// Launch a new session for this connection
|
||||
std::make_shared<socket_session>(
|
||||
ws, sess_handler_)
|
||||
->server_run(std::move(address), std::move(port));
|
||||
}
|
||||
|
||||
// Accept another connection
|
||||
acceptor_.async_accept(
|
||||
socket_,
|
||||
[self = shared_from_this()](error_code ec) {
|
||||
self->on_accept(ec);
|
||||
});
|
||||
}
|
||||
} // namespace sock
|
||||
@@ -2,12 +2,16 @@
|
||||
#define _SOCK_SERVER_LISTENER_H_
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include "socket_session_handler.hpp"
|
||||
#include "../hplog.hpp"
|
||||
|
||||
namespace net = boost::asio; // namespace asio
|
||||
|
||||
using tcp = net::ip::tcp;
|
||||
using error = boost::system::error_code; // from <boost/system/error_code.hpp>
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
namespace sock
|
||||
{
|
||||
@@ -16,22 +20,124 @@ namespace sock
|
||||
* Represents an active WebSocket server connection
|
||||
* Based on the implementation from https://github.com/vinniefalco/CppCon2018
|
||||
*/
|
||||
class socket_server : public std::enable_shared_from_this<socket_server>
|
||||
template <class T>
|
||||
class socket_server : public std::enable_shared_from_this<socket_server<T>>
|
||||
{
|
||||
tcp::acceptor acceptor_; // acceptor which accepts new connections
|
||||
tcp::socket socket_; // socket in which the client connects
|
||||
socket_session_handler &sess_handler_; // handler passed to gain access to websocket events
|
||||
tcp::acceptor acceptor; // acceptor which accepts new connections
|
||||
tcp::socket socket; // socket in which the client connects
|
||||
socket_session_handler<T> &sess_handler; // handler passed to gain access to websocket events
|
||||
|
||||
void fail(error ec, char const *what);
|
||||
void fail(error_code ec, char const *what);
|
||||
|
||||
void on_accept(error ec);
|
||||
void on_accept(error_code ec);
|
||||
|
||||
public:
|
||||
socket_server(net::io_context &ioc, tcp::endpoint endpoint, socket_session_handler &session_handler);
|
||||
socket_server(net::io_context &ioc, tcp::endpoint endpoint, socket_session_handler<T> &session_handler);
|
||||
|
||||
// Start accepting incoming connections
|
||||
void run();
|
||||
};
|
||||
|
||||
|
||||
template <class T>
|
||||
socket_server<T>::socket_server(net::io_context &ioc, tcp::endpoint endpoint, socket_session_handler<T> &session_handler)
|
||||
: acceptor(ioc), socket(ioc), sess_handler(session_handler)
|
||||
{
|
||||
error_code ec;
|
||||
|
||||
// Open the acceptor
|
||||
acceptor.open(endpoint.protocol(), ec);
|
||||
if (ec)
|
||||
{
|
||||
fail(ec, "open");
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow address reuse
|
||||
acceptor.set_option(net::socket_base::reuse_address(true));
|
||||
if (ec)
|
||||
{
|
||||
fail(ec, "set_option");
|
||||
return;
|
||||
}
|
||||
|
||||
// Bind to the server address
|
||||
acceptor.bind(endpoint, ec);
|
||||
if (ec)
|
||||
{
|
||||
fail(ec, "bind");
|
||||
return;
|
||||
}
|
||||
|
||||
// Start listening for connections
|
||||
acceptor.listen(
|
||||
net::socket_base::max_listen_connections, ec);
|
||||
if (ec)
|
||||
{
|
||||
fail(ec, "listen");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point to socket server which accepts new connections
|
||||
*/
|
||||
template <class T>
|
||||
void socket_server<T>::run()
|
||||
{
|
||||
|
||||
// Start accepting a connection
|
||||
acceptor.async_accept(
|
||||
socket,
|
||||
[self = this->shared_from_this()](error_code ec) {
|
||||
self->on_accept(ec);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes on error
|
||||
*/
|
||||
template <class T>
|
||||
void socket_server<T>::fail(error_code ec, char const *what)
|
||||
{
|
||||
// Don't report on canceled operations
|
||||
if (ec == net::error::operation_aborted)
|
||||
return;
|
||||
LOG_ERR << what << ": " << ec.message();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes on acceptance of new connection
|
||||
*/
|
||||
template <class T>
|
||||
void socket_server<T>::on_accept(error_code ec)
|
||||
{
|
||||
if (ec)
|
||||
{
|
||||
return fail(ec, "accept");
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string port = std::to_string(socket.remote_endpoint().port());
|
||||
std::string address = socket.remote_endpoint().address().to_string();
|
||||
|
||||
//Creating websocket stream required to pass to initiate a new session
|
||||
websocket::stream<beast::tcp_stream> ws(std::move(socket));
|
||||
|
||||
// Launch a new session for this connection
|
||||
std::make_shared<socket_session<T>>(
|
||||
ws, sess_handler)
|
||||
->server_run(std::move(address), std::move(port));
|
||||
}
|
||||
|
||||
// Accept another connection
|
||||
acceptor.async_accept(
|
||||
socket,
|
||||
[self = this->shared_from_this()](error_code ec) {
|
||||
self->on_accept(ec);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace sock
|
||||
|
||||
#endif
|
||||
@@ -1,209 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include "socket_session.hpp"
|
||||
#include "../util.hpp"
|
||||
|
||||
namespace net = boost::asio;
|
||||
|
||||
using tcp = net::ip::tcp;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
namespace sock
|
||||
{
|
||||
|
||||
socket_session::socket_session(websocket::stream<beast::tcp_stream> &websocket, socket_session_handler &sess_handler)
|
||||
: ws_(std::move(websocket)), sess_handler_(sess_handler)
|
||||
{
|
||||
ws_.binary(true);
|
||||
}
|
||||
|
||||
socket_session::~socket_session()
|
||||
{
|
||||
sess_handler_.on_close(this);
|
||||
}
|
||||
|
||||
//port and address will be used to identify from which client the message recieved in the handler
|
||||
void socket_session::server_run(const std::string &&address, const std::string &&port)
|
||||
{
|
||||
port_ = port;
|
||||
address_ = address;
|
||||
|
||||
//Set this flag to identify whether this socket session created when node acts as a server
|
||||
flags_.set(util::SESSION_FLAG::INBOUND);
|
||||
|
||||
// Accept the websocket handshake
|
||||
ws_.async_accept(
|
||||
[sp = shared_from_this()](
|
||||
error ec) {
|
||||
sp->on_accept(ec);
|
||||
});
|
||||
}
|
||||
|
||||
//port and address will be used to identify from which server the message recieved in the handler
|
||||
void socket_session::client_run(const std::string &&address, const std::string &&port, error ec)
|
||||
{
|
||||
port_ = port;
|
||||
address_ = address;
|
||||
|
||||
if (ec)
|
||||
return fail(ec, "handshake");
|
||||
|
||||
sess_handler_.on_connect(this);
|
||||
|
||||
ws_.async_read(
|
||||
buffer_,
|
||||
[sp = shared_from_this()](
|
||||
error_code ec, std::size_t bytes) {
|
||||
sp->on_read(ec, bytes);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes on error
|
||||
*/
|
||||
void socket_session::fail(error_code ec, char const *what)
|
||||
{
|
||||
// LOG_ERR << what << ": " << ec.message();
|
||||
|
||||
// Don't report these
|
||||
if (ec == net::error::operation_aborted ||
|
||||
ec == websocket::error::closed)
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes on acceptance of new connection
|
||||
*/
|
||||
void socket_session::on_accept(error_code ec)
|
||||
{
|
||||
// Handle the error, if any
|
||||
if (ec)
|
||||
return fail(ec, "accept");
|
||||
|
||||
sess_handler_.on_connect(this);
|
||||
|
||||
// Read a message
|
||||
ws_.async_read(
|
||||
buffer_,
|
||||
[sp = shared_from_this()](
|
||||
error_code ec, std::size_t bytes) {
|
||||
sp->on_read(ec, bytes);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Executes on completion of recieiving a new message
|
||||
*/
|
||||
void socket_session::on_read(error_code ec, std::size_t)
|
||||
{
|
||||
//if something goes wrong when trying to read, socket connection will be closed and calling this to inform it to the handler
|
||||
// read may get called when operation_aborted as well.
|
||||
// We don't need to process read operation in that case.
|
||||
if (ec == net::error::operation_aborted)
|
||||
return;
|
||||
|
||||
// Handle the error, if any
|
||||
if (ec)
|
||||
{
|
||||
// if something goes wrong when trying to read, socket connection will be closed and calling this to inform it to the handler
|
||||
on_close(ec, 1);
|
||||
return fail(ec, "read");
|
||||
}
|
||||
|
||||
std::string message = beast::buffers_to_string(buffer_.data());
|
||||
sess_handler_.on_message(this, std::move(message));
|
||||
|
||||
// Clear the buffer
|
||||
buffer_.consume(buffer_.size());
|
||||
|
||||
// Read another message
|
||||
ws_.async_read(
|
||||
buffer_,
|
||||
[sp = shared_from_this()](
|
||||
error_code ec, std::size_t bytes) {
|
||||
sp->on_read(ec, bytes);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Send message through an active websocket connection
|
||||
*/
|
||||
void socket_session::send(std::string &&ss)
|
||||
{
|
||||
// Always add to queue
|
||||
queue_.push_back(ss);
|
||||
|
||||
// Are we already writing?
|
||||
if (queue_.size() > 1)
|
||||
return;
|
||||
|
||||
// We are not currently writing, so send this immediately
|
||||
ws_.async_write(
|
||||
net::buffer(queue_.front()),
|
||||
[sp = shared_from_this()](
|
||||
error_code ec, std::size_t bytes) {
|
||||
sp->on_write(ec, bytes);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Executes on completion of write operation to a socket
|
||||
*/
|
||||
void socket_session::on_write(error_code ec, std::size_t)
|
||||
{
|
||||
// Handle the error, if any
|
||||
if (ec)
|
||||
return fail(ec, "write");
|
||||
|
||||
// Remove the string from the queue
|
||||
queue_.erase(queue_.begin());
|
||||
|
||||
// Send the next message if any
|
||||
if (!queue_.empty())
|
||||
ws_.async_write(
|
||||
net::buffer(queue_.front()),
|
||||
[sp = shared_from_this()](
|
||||
error_code ec, std::size_t bytes) {
|
||||
sp->on_write(ec, bytes);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Close an active websocket connection gracefully
|
||||
*/
|
||||
void socket_session::close()
|
||||
{
|
||||
// Close the WebSocket connection
|
||||
ws_.async_close(websocket::close_code::normal,
|
||||
[sp = shared_from_this()](
|
||||
error_code ec) {
|
||||
sp->on_close(ec, 0);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Executes on completion of closing a socket connection
|
||||
*/
|
||||
//type will be used identify whether the error is due to failure in closing the web socket or transfer of another exception to this method
|
||||
void socket_session::on_close(error_code ec, std::int8_t type)
|
||||
{
|
||||
// sess_handler_.on_close(this);
|
||||
|
||||
// if (type == 1)
|
||||
// return;
|
||||
|
||||
// if (ec)
|
||||
// return fail(ec, "close");
|
||||
}
|
||||
|
||||
// When called, initializes the unique id string for this session.
|
||||
void socket_session::init_uniqueid()
|
||||
{
|
||||
// Create a unique id for the session combining ip and port.
|
||||
// We prepare this appended string here because we need to use it for finding elemends from the maps
|
||||
// for validation purposes whenever a message is received.
|
||||
uniqueid_.append(address_).append(":").append(port_);
|
||||
}
|
||||
|
||||
} // namespace sock
|
||||
@@ -1,11 +1,14 @@
|
||||
#ifndef _SOCK_SERVER_SESSION_H_
|
||||
#define _SOCK_SERVER_SESSION_H_
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <bitset>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/beast.hpp>
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include "../util.hpp"
|
||||
#include "socket_session_handler.hpp"
|
||||
|
||||
namespace beast = boost::beast;
|
||||
@@ -14,51 +17,67 @@ namespace websocket = boost::beast::websocket;
|
||||
namespace http = boost::beast::http;
|
||||
|
||||
using tcp = net::ip::tcp;
|
||||
using error = boost::system::error_code;
|
||||
using error_code = boost::system::error_code;
|
||||
|
||||
namespace sock
|
||||
{
|
||||
|
||||
/**
|
||||
* Represents an outbound message that is sent with a websocket.
|
||||
* We use this class to wrap different object types holding actual message contents.
|
||||
* We use this mechanism to achieve end-to-end zero-copy between original message
|
||||
* content generator and websocket flush.
|
||||
*/
|
||||
class outbound_message
|
||||
{
|
||||
public:
|
||||
// Returns a pointer to the internal buffer owned by the message object.
|
||||
// Contents of this buffer is the message that is sent/received with the socket.
|
||||
virtual std::string_view buffer() = 0;
|
||||
};
|
||||
|
||||
//Forward Declaration
|
||||
template <class T>
|
||||
class socket_session_handler;
|
||||
|
||||
/**
|
||||
* Represents an active WebSocket connection
|
||||
*/
|
||||
class socket_session : public std::enable_shared_from_this<socket_session>
|
||||
template <class T>
|
||||
class socket_session : public std::enable_shared_from_this<socket_session<T>>
|
||||
{
|
||||
beast::flat_buffer buffer_; // used to store incoming messages
|
||||
websocket::stream<beast::tcp_stream> ws_; // websocket stream used send an recieve messages
|
||||
std::vector<std::string> queue_; // uses to store messages temporarily until it is sent to the relevant party
|
||||
socket_session_handler &sess_handler_; // handler passed to gain access to websocket events
|
||||
beast::flat_buffer buffer; // used to store incoming messages
|
||||
websocket::stream<beast::tcp_stream> ws; // websocket stream used send an recieve messages
|
||||
std::vector<T> queue; // used to store messages temporarily until it is sent to the relevant party
|
||||
socket_session_handler<T> &sess_handler; // handler passed to gain access to websocket events
|
||||
|
||||
void fail(error ec, char const *what);
|
||||
void fail(error_code ec, char const *what);
|
||||
|
||||
void on_accept(error ec);
|
||||
void on_accept(error_code ec);
|
||||
|
||||
void on_read(error ec, std::size_t bytes_transferred);
|
||||
void on_read(error_code ec, std::size_t bytes_transferred);
|
||||
|
||||
void on_write(error ec, std::size_t bytes_transferred);
|
||||
void on_write(error_code ec, std::size_t bytes_transferred);
|
||||
|
||||
void on_close(error ec, std::int8_t type);
|
||||
void on_close(error_code ec, std::int8_t type);
|
||||
|
||||
public:
|
||||
socket_session(websocket::stream<beast::tcp_stream> &websocket, socket_session_handler &sess_handler);
|
||||
|
||||
socket_session(websocket::stream<beast::tcp_stream> &websocket, socket_session_handler<T> &sess_handler);
|
||||
|
||||
~socket_session();
|
||||
|
||||
// Port and the address of the remote party is being saved to used in the session handler
|
||||
// to identify from which remote party the message recieved. Since the port is passed as a string
|
||||
// to identify from which remote party the message recieved. Since the port is passed as a string
|
||||
// from the parent we store as it is, since we are not going to pass it anywhere or used in a method
|
||||
|
||||
// The port of the remote party.
|
||||
std::string port_;
|
||||
std::string port;
|
||||
|
||||
// The IP address of the remote party.
|
||||
std::string address_;
|
||||
std::string address;
|
||||
|
||||
// The unique identifier of the remote party (format <ip>:<port>).
|
||||
std::string uniqueid_;
|
||||
std::string uniqueid;
|
||||
|
||||
// The set of util::SESSION_FLAG enum flags that will be set by user-code of this calss.
|
||||
// We mainly use this to store contexual information about this session based on the use case.
|
||||
@@ -66,14 +85,232 @@ public:
|
||||
std::bitset<8> flags_;
|
||||
|
||||
void server_run(const std::string &&address, const std::string &&port);
|
||||
void client_run(const std::string &&address, const std::string &&port, error ec);
|
||||
void client_run(const std::string &&address, const std::string &&port, error_code ec);
|
||||
|
||||
void send(std::string &&ss);
|
||||
void send(T msg);
|
||||
|
||||
// When called, initializes the unique id string for this session.
|
||||
void init_uniqueid();
|
||||
|
||||
void close();
|
||||
};
|
||||
|
||||
template <class T>
|
||||
socket_session<T>::socket_session(websocket::stream<beast::tcp_stream> &websocket, socket_session_handler<T> &sess_handler)
|
||||
: ws(std::move(websocket)), sess_handler(sess_handler)
|
||||
{
|
||||
// We use binary data instead of ASCII/UTF8 character data.
|
||||
ws.binary(true);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
socket_session<T>::~socket_session()
|
||||
{
|
||||
sess_handler.on_close(this);
|
||||
}
|
||||
|
||||
//port and address will be used to identify from which client the message recieved in the handler
|
||||
template <class T>
|
||||
void socket_session<T>::server_run(const std::string &&address, const std::string &&port)
|
||||
{
|
||||
this->port = port;
|
||||
this->address = address;
|
||||
|
||||
//Set this flag to identify whether this socket session created when node acts as a server
|
||||
flags_.set(util::SESSION_FLAG::INBOUND);
|
||||
|
||||
// Accept the websocket handshake
|
||||
ws.async_accept(
|
||||
[sp = this->shared_from_this()](
|
||||
error_code ec) {
|
||||
sp->on_accept(ec);
|
||||
});
|
||||
}
|
||||
|
||||
//port and address will be used to identify from which server the message recieved in the handler
|
||||
template <class T>
|
||||
void socket_session<T>::client_run(const std::string &&address, const std::string &&port, error_code ec)
|
||||
{
|
||||
this->port = port;
|
||||
this->address = address;
|
||||
|
||||
if (ec)
|
||||
return fail(ec, "handshake");
|
||||
|
||||
sess_handler.on_connect(this);
|
||||
|
||||
ws.async_read(
|
||||
buffer,
|
||||
[sp = this->shared_from_this()](
|
||||
error_code ec, std::size_t bytes) {
|
||||
sp->on_read(ec, bytes);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes on error
|
||||
*/
|
||||
template <class T>
|
||||
void socket_session<T>::fail(error_code ec, char const *what)
|
||||
{
|
||||
// LOG_ERR << what << ": " << ec.message();
|
||||
|
||||
// Don't report these
|
||||
if (ec == net::error::operation_aborted ||
|
||||
ec == websocket::error::closed)
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes on acceptance of new connection
|
||||
*/
|
||||
template <class T>
|
||||
void socket_session<T>::on_accept(error_code ec)
|
||||
{
|
||||
// Handle the error, if any
|
||||
if (ec)
|
||||
return fail(ec, "accept");
|
||||
|
||||
sess_handler.on_connect(this);
|
||||
|
||||
// Read a message
|
||||
ws.async_read(
|
||||
buffer,
|
||||
[sp = this->shared_from_this()](
|
||||
error_code ec, std::size_t bytes) {
|
||||
sp->on_read(ec, bytes);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Executes on completion of recieiving a new message
|
||||
*/
|
||||
template <class T>
|
||||
void socket_session<T>::on_read(error_code ec, std::size_t)
|
||||
{
|
||||
//if something goes wrong when trying to read, socket connection will be closed and calling this to inform it to the handler
|
||||
// read may get called when operation_aborted as well.
|
||||
// We don't need to process read operation in that case.
|
||||
if (ec == net::error::operation_aborted)
|
||||
return;
|
||||
|
||||
// Handle the error, if any
|
||||
if (ec)
|
||||
{
|
||||
// if something goes wrong when trying to read, socket connection will be closed and calling this to inform it to the handler
|
||||
on_close(ec, 1);
|
||||
return fail(ec, "read");
|
||||
}
|
||||
|
||||
// Wrap the buffer data in a string_view and call session handler.
|
||||
// We DO NOT transfer ownership of buffer data to the session handler. It should
|
||||
// read and process the message and we will clear the buffer after its done with it.
|
||||
const char *buffer_data = net::buffer_cast<const char *>(buffer.data());
|
||||
std::string_view message(buffer_data, buffer.size());
|
||||
sess_handler.on_message(this, message);
|
||||
|
||||
// Clear the buffer
|
||||
buffer.consume(buffer.size());
|
||||
|
||||
// Read another message
|
||||
ws.async_read(
|
||||
buffer,
|
||||
[sp = this->shared_from_this()](
|
||||
error_code ec, std::size_t bytes) {
|
||||
sp->on_read(ec, bytes);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Send message through an active websocket connection
|
||||
*/
|
||||
template <class T>
|
||||
void socket_session<T>::send(T msg)
|
||||
{
|
||||
// Always add to queue
|
||||
queue.push_back(std::move(msg));
|
||||
|
||||
// Are we already writing?
|
||||
if (queue.size() > 1)
|
||||
return;
|
||||
|
||||
std::string_view sv = queue.front().buffer();
|
||||
|
||||
// We are not currently writing, so send this immediately
|
||||
ws.async_write(
|
||||
// Project the outbound_message buffer from the queue front into the asio buffer.
|
||||
net::buffer(sv.data(), sv.length()),
|
||||
[sp = this->shared_from_this()](
|
||||
error_code ec, std::size_t bytes) {
|
||||
sp->on_write(ec, bytes);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Executes on completion of write operation to a socket
|
||||
*/
|
||||
template <class T>
|
||||
void socket_session<T>::on_write(error_code ec, std::size_t)
|
||||
{
|
||||
// Handle the error, if any
|
||||
if (ec)
|
||||
return fail(ec, "write");
|
||||
|
||||
// Remove the string from the queue
|
||||
queue.erase(queue.begin());
|
||||
|
||||
// Send the next message if any
|
||||
if (!queue.empty())
|
||||
{
|
||||
std::string_view sv = queue.front().buffer();
|
||||
ws.async_write(
|
||||
net::buffer(sv.data(), sv.length()),
|
||||
[sp = this->shared_from_this()](
|
||||
error_code ec, std::size_t bytes) {
|
||||
sp->on_write(ec, bytes);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Close an active websocket connection gracefully
|
||||
*/
|
||||
template <class T>
|
||||
void socket_session<T>::close()
|
||||
{
|
||||
// Close the WebSocket connection
|
||||
ws.async_close(websocket::close_code::normal,
|
||||
[sp = this->shared_from_this()](
|
||||
error_code ec) {
|
||||
sp->on_close(ec, 0);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Executes on completion of closing a socket connection
|
||||
*/
|
||||
//type will be used identify whether the error is due to failure in closing the web socket or transfer of another exception to this method
|
||||
template <class T>
|
||||
void socket_session<T>::on_close(error_code ec, std::int8_t type)
|
||||
{
|
||||
// sess_handler.on_close(this);
|
||||
|
||||
// if (type == 1)
|
||||
// return;
|
||||
|
||||
// if (ec)
|
||||
// return fail(ec, "close");
|
||||
}
|
||||
|
||||
// When called, initializes the unique id string for this session.
|
||||
template <class T>
|
||||
void socket_session<T>::init_uniqueid()
|
||||
{
|
||||
// Create a unique id for the session combining ip and port.
|
||||
// We prepare this appended string here because we need to use it for finding elemends from the maps
|
||||
// for validation purposes whenever a message is received.
|
||||
uniqueid.append(address).append(":").append(port);
|
||||
}
|
||||
|
||||
} // namespace sock
|
||||
#endif
|
||||
|
||||
@@ -7,28 +7,30 @@ namespace sock
|
||||
{
|
||||
|
||||
// Forward declaration
|
||||
template <class T>
|
||||
class socket_session;
|
||||
|
||||
/**
|
||||
* Represents a WebSocket sessions handler. Can inherit from this class and access websocket events
|
||||
*/
|
||||
template <class T>
|
||||
class socket_session_handler
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Executes on initiation of a new connection
|
||||
*/
|
||||
virtual void on_connect(socket_session *session) = 0;
|
||||
virtual void on_connect(socket_session<T> *session) = 0;
|
||||
|
||||
/**
|
||||
* Executes on recieval of new message
|
||||
*/
|
||||
virtual void on_message(socket_session *session, std::string &&message) = 0;
|
||||
virtual void on_message(socket_session<T> *session, std::string_view message) = 0;
|
||||
|
||||
/**
|
||||
* Executes on websocket connection close
|
||||
*/
|
||||
virtual void on_close(socket_session *session) = 0;
|
||||
virtual void on_close(socket_session<T> *session) = 0;
|
||||
};
|
||||
} // namespace sock
|
||||
|
||||
|
||||
Reference in New Issue
Block a user