How to make a non blocking server - c++11

I want to have a non blocking server to read the incoming data in my application.
this is the workflow i want.
void main()
{
// create a socket server
CreateSocket();
while( true )
{
// keep doing the other tasks.
// if we receive a message than process it
}
}
this is the code for the Socket connection
#pragma once
//importing libraries
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
using namespace boost::asio;
using ip::tcp;
using std::cout;
using std::endl;
class con_handler : public boost::enable_shared_from_this<con_handler>
{
private:
tcp::socket sock;
std::string message = "Hello From Server";
enum { max_length = 2048};
char data[max_length];
public:
typedef boost::shared_ptr<con_handler> pointer;
con_handler(boost::asio::io_service& io_service) : sock( io_service) {}
// creating the pointer
static pointer create(boost::asio::io_service& io_service)
{
return pointer(new con_handler(io_service));
}
// socket creation
tcp::socket& socket()
{
return sock;
}
void start()
{
sock.async_read_some(
boost::asio::buffer(data, max_length),
boost::bind(&con_handler::handle_read,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_read(const boost::system::error_code& err, size_t bytes_transferred)
{
if (!err) {
cout << data << endl;
}
else {
std::cerr << "error: " << err.message() << std::endl;
sock.close();
}
}
void handle_write(const boost::system::error_code& err, size_t bytes_transferred)
{
if (!err) {
cout << "Server sent Hello message!" << endl;
}
else {
std::cerr << "error: " << err.message() << endl;
sock.close();
}
}
};
////////////////////////////////////////////////////////
#pragma once
#include "TCP_Server.h"
class Server
{
private:
tcp::acceptor acceptor_;
void start_accept()
{
// socket
con_handler::pointer connection = con_handler::create(acceptor_.get_io_service());
// asynchronous accept operation and wait for a new connection.
acceptor_.async_accept(connection->socket(),
boost::bind(&Server::handle_accept, this, connection,
boost::asio::placeholders::error));
}
public:
//constructor for accepting connection from client
Server(boost::asio::io_service& io_service) : acceptor_(io_service, tcp::endpoint(tcp::v4(), 1234))
{
start_accept();
}
void handle_accept(con_handler::pointer connection, const boost::system::error_code& err)
{
if (!err) {
connection->start();
}
start_accept();
}
};
Currently the socket connection is working but it is blocking the other process.
Do i need to have a separate thread for this workflow or i can do it in the same thread.
this is my main function
#include "pch.h"
#include <iostream>
#include "Server.h"
int main()
{
boost::asio::io_service io_service;
Server server(io_service);
io_service.run();
while (true)
{
std::cout << " function is running Running";
}
}
I am able to receive input messages but i never reach the while statement.
i would want while to keep printing and the server to receive message at the same time.

Related

Boost.asio OpenSSL HTTPS Proxy Request handshake failed C++

I am learning TCP Socket Windows programing with Boost/OpenSSL/VS2019. I guess the below code is a typical server example that echoes received data to the client, it worked as expected when tested with client program. But when tested with a browser such as Chrome or IE11(set proxy IP and port 443 in the browsers), the handshake part always failed with message (handshake failed) "https proxy request (SSL routines, ssl3_get_record) [asio.ssl:336130203]". I generated Self-signed Certificate using OpenSSL for testing, and used it in the client program in the way like ssl_context.load_verify_file("cert.pem"), the test turned out to be good. Then I installed "cert.pem" in IE(and Chrome), set the proxy IP and port 443, I was expecting that the handshake will pass, but unfortunately it always failed with the above message.
My Questions are,
In order to make browsers work(the handshake part for now), the domain name or IP, or any other identification info in the Self-signed Certificate has to be accurate to the "proxy" server where I am testing on? I generated a dummy certificate for testing, it has nothing to do with the machine I am testing on. The client program(sends message to the server and receives the message sent back from the server) worked OK with it.
I am trying to implement a simple HTTPS relay, I understand there are many things need to do, my HTTP proxy works so far. To incorporate SSL, the first thing I want to ensure the proxy server can accept browsers' connections thru HTTPS/Proxy request, then transport data back and forth. Is there anything specific in the browser for HTTPS proxy request that failed the handshake? Or am I in the wrong way to build HTTPS/SSL relay? Thank you very much.
class session
{
public:
session(boost::asio::io_service& io_service,
boost::asio::ssl::context& context)
: socket_(io_service, context)
{
}
ssl_socket::lowest_layer_type& socket()
{
return socket_.lowest_layer();
}
void start()
{
socket_.async_handshake(boost::asio::ssl::stream_base::server,
boost::bind(&session::handle_handshake, this,
boost::asio::placeholders::error));
}
void handle_handshake(const boost::system::error_code& error)
{
if (!error)
{
std::cout << "handshake good" << std::endl;
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
std::cout << "handshake failed " + error.what() << std::endl;
delete this;
}
}
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
boost::asio::async_write(socket_,
boost::asio::buffer(data_, bytes_transferred),
boost::bind(&session::handle_write, this,
boost::asio::placeholders::error));
}
else
{
delete this;
}
}
void handle_write(const boost::system::error_code& error)
{
if (!error)
{
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
delete this;
}
}
private:
ssl_socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class server
{
public:
server(boost::asio::io_service& io_service, unsigned short port)
: io_service_(io_service),
acceptor_(io_service,
boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
context_(boost::asio::ssl::context::sslv23)
{
context_.set_options(
boost::asio::ssl::context::default_workarounds
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::single_dh_use);
context_.set_password_callback(boost::bind(&server::get_password, this));
context_.use_certificate_chain_file("cert.pem");
context_.use_private_key_file("key.pem", boost::asio::ssl::context::pem);
context_.use_tmp_dh_file("dh2048.pem");
start_accept();
}
std::string get_password() const
{
return "test";
}
void start_accept()
{
session* new_session = new session(io_service_, context_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
void handle_accept(session* new_session,
const boost::system::error_code& error)
{
if (!error)
{
std::cout << "accept good" << std::endl;
new_session->start();
}
else
{
delete new_session;
}
start_accept();
}
private:
boost::asio::io_service& io_service_;
boost::asio::ip::tcp::acceptor acceptor_;
boost::asio::ssl::context context_;
};
Tried:
Generated self-signed Certificate files(cert.pem, key.pem, dh2048.pem) using OpenSSL
Build Server and client Program with the above certificates, Server listening on port 443, Client and server is on the same machine. Handshake between client and server went thru, message sent between client and server went thru. Moved the client to a separate PC, it works as well.
Installed the above certificate in IE and Chrome on the same machine.
Connect browsers with the server via HTTPS proxy on port 443, Handshake failed.
Expected:
Handshake between Browsers and "the Proxy" server goes thru without an error.
Actually Resulted:
browser Handshake failed with message "https proxy request (SSL routines, ssl3_get_record) [asio.ssl:336130203]"
Your server is not an HTTP server. You don't disclose what kind of proxy you're using but, if you just point e.g. Chrome at your server, it doesn't like being treated that way either.
Here's with the code made self-contained:
I'd suggest starting out with at least a valid HTTP response, e.g.:
void handle_read(error_code error, size_t bytes_transferred) {
if (!error) {
res_ = {http::status::ok, 10,
std::string(data_.data(), bytes_transferred)};
http::async_write(socket_, res_,
boost::bind(&session::handle_write, this,
asio::placeholders::error));
} else {
delete this;
}
}
This would echo minimal valid HTTP responses ignoring the request:
HTTP/1.0 200 OK
foo
bar
HTTP/1.0 200 OK
bar
qux
HTTP/1.0 200 OK
qux
We're ignoring the client's request, and we're not warning about content-length or keepalive. Le'ts improve that by going HTTP/1.1:
void handle_read(error_code error, size_t bytes_transferred) {
if (!error) {
res_ = {http::status::ok, 11,
std::string(data_.data(), bytes_transferred)};
res_.keep_alive(false);
res_.prepare_payload();
http::async_write(socket_, res_,
boost::bind(&session::handle_write, this,
asio::placeholders::error));
} else {
delete this;
}
}
Now we get responses like on openssl s_client -connect localhost:8989 -quiet -verify_quiet <<< "Hello world":
HTTP/1.1 200 OK
Connection: close
Content-Length: 12
Hello world
Does the browser like it better?
Curl doesn't complain:
curl -k https://localhost:8989/my/page
GET /my/page HTTP/1.1
Host: localhost:8989
User-Agent: curl/7.81.0
Accept: */*
My browser browser sends a load of cookies to the localhost domain:
Not shown in the browser are the actual response headers:
HTTP/1.1 200 OK
Connection: close
Content-Length: 1003
In fact, if the request is larger than 1024 bytes, the full request can't even be received before the server blurts out a partial "echo" and disconnects. Let's improve the situation by at least reading the entire request headers:
asio::async_read_until(
socket_, asio::dynamic_buffer(data_), "\r\n\r\n",
boost::bind(&session::handle_read, this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
See it In Full On Coliru
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind/bind.hpp>
#include <iostream>
#include <boost/beast.hpp>
namespace asio = boost::asio;
namespace ssl = asio::ssl;
namespace beast = boost::beast;
namespace http = beast::http;
using asio::ip::tcp;
using boost::system::error_code;
using ssl_socket = ssl::stream<tcp::socket>;
using namespace std::chrono_literals;
class session {
public:
session(asio::io_service& io_service, ssl::context& context)
: socket_(io_service, context) {}
ssl_socket::lowest_layer_type& socket() {
return socket_.lowest_layer();
}
void start() {
socket_.async_handshake( //
ssl_socket::server,
boost::bind(&session::handle_handshake, this,
asio::placeholders::error));
}
void handle_handshake(error_code error) {
if (!error) {
std::cout << "handshake good" << std::endl;
asio::async_read_until(
socket_, asio::dynamic_buffer(data_), "\r\n\r\n",
boost::bind(&session::handle_read, this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
} else {
std::cout << "handshake failed " + error.what() << std::endl;
delete this;
}
}
void handle_read(error_code error, size_t bytes_transferred) {
if (!error) {
res_ = {http::status::ok, 11,
std::string(data_.data(), bytes_transferred)};
res_.keep_alive(false);
res_.prepare_payload();
http::async_write(socket_, res_,
boost::bind(&session::handle_write, this,
asio::placeholders::error));
} else {
delete this;
}
}
void handle_write(error_code error) {
if (!error) {
socket_.async_read_some( //
asio::buffer(data_),
boost::bind(&session::handle_read, this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
} else {
delete this;
}
}
private:
ssl_socket socket_;
std::string data_;
http::response<http::string_body> res_;
};
class server {
public:
using Ctx = ssl::context;
server(asio::io_service& io_service, uint16_t port)
: io_service_(io_service)
, acceptor_(io_service, {tcp::v4(), port})
, context_(Ctx::sslv23) //
{
acceptor_.set_option(tcp::acceptor::reuse_address(true));
context_.set_options(Ctx::default_workarounds | Ctx::no_sslv2 |
Ctx::single_dh_use);
context_.set_password_callback(&server::get_password);
context_.use_certificate_chain_file("cert.pem");
context_.use_private_key_file("key.pem", Ctx::pem);
context_.use_tmp_dh_file("dh2048.pem");
start_accept();
}
private:
static std::string get_password(size_t, Ctx::password_purpose) {
return "test";
}
void start_accept() {
session* new_session = new session(io_service_, context_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this,
new_session,
asio::placeholders::error));
}
void handle_accept(session* new_session, error_code error) {
if (!error) {
std::cout << "accept good" << std::endl;
new_session->start();
} else {
delete new_session;
}
start_accept();
}
private:
asio::io_service& io_service_;
tcp::acceptor acceptor_;
ssl::context context_;
};
int main() {
asio::io_service ioc;
server s(ioc, 8989);
ioc.run_for(30s);
}
Further Work
In fact, you probably need to read the HTTP request anyways (since the context is browsers and HTTP proxies). So, perhaps use Beast again:
void do_receive() {
http::async_read(
socket_, buf_, req_,
boost::bind(&session::handle_read, this,
asio::placeholders::error,
asio::placeholders::bytes_transferred));
}
Now we can get rid of the keep_alive(false) since we don't clobber our input.
Now, the delete this anti-pattern can be replaced by the enable_shared_from_this pattern.
If we now move the socket creation to the server and avoid passing io_service& references, we can also remove the violation of encapsulation that was socket() and stop depending on io_service which has been deprecated for several Asio versions.
The result is eerily close to the Beast HTTP server examples:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind/bind.hpp>
#include <iostream>
#include <boost/beast.hpp>
#include <boost/lexical_cast.hpp> // for the request echo
namespace asio = boost::asio;
namespace ssl = asio::ssl;
namespace beast = boost::beast;
namespace http = beast::http;
using asio::ip::tcp;
using boost::system::error_code;
using ssl_socket = ssl::stream<tcp::socket>;
using namespace std::chrono_literals;
struct session : public std::enable_shared_from_this<session> {
session(tcp::socket s, ssl::context& context)
: socket_(std::move(s), context) {}
void start() {
socket_.async_handshake( //
ssl_socket::server,
boost::bind(&session::handle_handshake, shared_from_this(),
asio::placeholders::error));
}
private:
void handle_handshake(error_code error) {
if (!error) {
std::cout << "handshake good" << std::endl;
do_receive();
} else {
std::cout << "handshake failed " + error.what() << std::endl;
}
}
void do_receive() {
http::async_read(
socket_, buf_, req_,
boost::bind(&session::handle_read, shared_from_this(),
asio::placeholders::error,
asio::placeholders::bytes_transferred));
}
void handle_read(error_code error, size_t /*bytes_transferred*/) {
if (!error) {
res_ = {http::status::ok, 11,
boost::lexical_cast<std::string>(req_)};
res_.keep_alive(false);
res_.prepare_payload();
http::async_write(socket_, res_,
boost::bind(&session::handle_write,
shared_from_this(),
asio::placeholders::error));
}
}
void handle_write(error_code error) {
if (!error) {
do_receive();
}
}
private:
ssl_socket socket_;
beast::flat_buffer buf_;
http::request<http::string_body> req_;
http::response<http::string_body> res_;
};
struct server {
server(asio::any_io_executor ex, uint16_t port)
: acceptor_(ex, {tcp::v4(), port})
, context_(Ctx::sslv23) //
{
acceptor_.set_option(tcp::acceptor::reuse_address(true));
context_.set_options(Ctx::default_workarounds | Ctx::no_sslv2 |
Ctx::single_dh_use);
context_.set_password_callback(&server::get_password);
context_.use_certificate_chain_file("cert.pem");
context_.use_private_key_file("key.pem", Ctx::pem);
context_.use_tmp_dh_file("dh2048.pem");
start_accept();
}
private:
using Ctx = ssl::context;
tcp::acceptor acceptor_;
Ctx context_;
static std::string get_password(size_t, Ctx::password_purpose) {
return "test";
}
void start_accept() {
acceptor_.async_accept(
// make_strand(acceptor_.get_executor()), // for multi-threaded servers
[this](error_code ec, tcp::socket s) {
if (!ec) {
std::cout << "accept good" << std::endl;
auto sess = std::make_shared<session>(std::move(s),
context_);
sess->start();
}
start_accept();
});
}
};
int main() {
asio::io_context ioc;
server s(ioc.get_executor(), 8989);
ioc.run_for(30s);
}
UPDATE
Saving information from the comments for the future:
Here's my review of your code, same but 100 lines of code less.
Adding back 50 lines I implemented the minimal CONNECT parsing and connect code: http://coliru.stacked-crooked.com/a/28fbdaf23ab00586 - See it working with
curl -px http://localhost:8989
on my system, for both HTTP and HTTPS targets:
Listing: Full HTTTP CONNECT de,p supporting any protocol (HTTPS included)
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <boost/beast/http.hpp>
#include <boost/bind/bind.hpp>
#include <iomanip>
#include <iostream>
namespace Tcp {
namespace asio = boost::asio;
namespace ph = asio::placeholders;
using tcp = asio::ip::tcp;
using socket_type = tcp::socket;
using error_code = boost::system::error_code;
template <typename Session> struct Listener {
Listener(asio::any_io_executor ex, uint16_t bind_port)
: acceptor_(ex, tcp::endpoint({}, bind_port)) {}
void do_accept() {
acceptor_.async_accept( //
make_strand(acceptor_.get_executor()), [this](error_code ec, tcp::socket s) {
std::cerr << "accepted " << s.remote_endpoint() << " (" << ec.message() << ")" << std::endl;
if (!ec) {
std::make_shared<Session>(std::move(s))->start();
do_accept();
} else {
std::cerr << "do_accept: " << ec.message() << std::endl;
}
});
}
private:
tcp::acceptor acceptor_;
};
namespace util {
// Relay from from_ to to_ sockets
// Lifetime shared with shared owner
struct half_duplex {
using Owner = std::weak_ptr<void>;
half_duplex(socket_type& f, socket_type& t) : from_(f), to_(t) {}
void start(Owner w) {
owner_ = w;
do_read();
}
// send previously received pending data first
template <typename ConstBufferSequence> void start(Owner w, ConstBufferSequence pending) {
owner_ = w;
async_write(to_, pending, boost::bind(&half_duplex::on_written, owner_shared(), ph::error));
}
private:
socket_type& from_;
socket_type& to_;
Owner owner_;
std::array<uint8_t, 8192> buf_;
void do_read() {
from_.async_read_some(
asio::buffer(buf_),
boost::bind(&half_duplex::on_read, owner_shared(), ph::error, ph::bytes_transferred));
}
void on_read(error_code ec, size_t xfer) {
if (!ec)
async_write(to_, asio::buffer(buf_, xfer),
boost::bind(&half_duplex::on_written, owner_shared(), ph::error));
}
void on_written(error_code ec) {
if (!ec)
do_read();
}
std::shared_ptr<half_duplex> owner_shared() {
if (auto o = owner_.lock())
return std::shared_ptr<half_duplex>(o, this); // aliasing constructor
else
throw std::bad_weak_ptr();
}
};
} // namespace util
namespace proxy {
namespace http = boost::beast::http;
struct Session : std::enable_shared_from_this<Session> {
Session(socket_type s) : client_(std::move(s)), server_(s.get_executor()) {}
void start() {
http::async_read(
client_, lead_in_, req_,
boost::bind(&Session::on_connect_request, shared_from_this(), ph::error));
}
private:
asio::streambuf lead_in_;
http::request<http::empty_body> req_;
http::response<http::empty_body> res_;
socket_type client_, server_;
util::half_duplex down_stream_{server_, client_};
util::half_duplex up_stream_{client_, server_};
void on_connect_request(error_code ec) {
if (ec.failed() || req_.method() != http::verb::connect)
return; // TODO error handling
std::cerr << "Connect request: " << req_ << std::endl;
// TODO handle headers?
std::string upstream(req_.target());
auto pos = upstream.find_last_of(":");
auto host = upstream.substr(0, pos);
auto svc = upstream.substr(pos + 1);
if (svc.empty())
svc = "http";
// TODO async resolve?
auto eps = tcp::resolver(server_.get_executor()).resolve(host, svc);
asio::async_connect(server_, eps,
boost::bind(&Session::on_connect, shared_from_this(), ph::error));
}
void on_connect(error_code ec) {
if (ec)
return; // TODO error handling
std::cerr << "Connected to " << server_.remote_endpoint() << std::endl;
res_ = {http::status::ok, req_.version()};
res_.keep_alive(true);
res_.prepare_payload();
http::async_write(client_, res_,
boost::bind(&Session::on_connect_response, shared_from_this(), ph::error));
}
void on_connect_response(error_code ec) {
if (ec)
return; // TODO error handling
up_stream_.start(shared_from_this());
if (lead_in_.size())
down_stream_.start(shared_from_this(), lead_in_.data());
else
down_stream_.start(shared_from_this());
}
};
}
using Proxy = Listener<proxy::Session>;
} // namespace Tcp
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cerr << "usage: " << std::quoted(argv[0]) << " <bind port>\n";
return 1;
}
auto bind_port = static_cast<uint16_t>(::atoi(argv[1]));
try {
boost::asio::io_context ioc;
Tcp::Proxy p(ioc.get_executor(), bind_port);
p.do_accept();
ioc.run();
} catch (std::exception const& e) {
std::cerr << "main: " << e.what() << std::endl;
return 1;
}
}

Boost Server breaking the messages from the client

If i send messages locally from the same system than the boost server receives messages properly.
When the client is a remote application on other system and sending messages through TCP\IP than randomly some messages break(Line Enter).
Like if the client has sent "THIS IS A MESSAGE" the server will read it as
"THIS IS A ME
SSAGE"
This is the Server class.
#pragma once
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/write.hpp>
#include <iostream>
#include <global.h>
#include <memory>
#include <fstream>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <queue>
using boost::asio::ip::tcp;
class session
: public std::enable_shared_from_this<session>
{
public:
session(tcp::socket socket)
: socket_(std::move(socket))
{
}
void start()
{
auto self(shared_from_this());
// dispatch not strictly necessary for single-threaded contexts
dispatch(
socket_.get_executor(),
[this, self]
{
do_read();
});
}
private:
void handleCommand()
{
enqueueAnswer();
}
void enqueueAnswer()
{
if (stdqueAnswers.size() == 1)
{
do_write();
}
}
void do_read()
{
auto self(shared_from_this());
socket_.async_read_some(boost::asio::buffer(data_, max_length),
[this, self](boost::system::error_code ec, std::size_t length)
{
if (!ec)
{
if (length > 0) {
// In case the message has a leading 1 than we have to send a answer back to the client.
if (data_[0] == '1') {
std::string stdstrCmd(data_);
stdstrCmd.erase(0, 2);
wavefrontAccess->ReceiveCommandExternalGet(stdstrCmd);
handleCommand();
}
else
{
std::string strData(data_, length);
if(!strData.empty() || strData.find_first_not_of(' ') != std::string::npos)
{
// There's a non-space.
commandsQueue.push(strData); // this is std Queue
}
}
}
do_read();
}
});
}
void do_write()
{
if (stdqueAnswers.empty())
return;
auto self(shared_from_this());
async_write(
socket_,
boost::asio::buffer(stdqueAnswers.front()),
[this, self](boost::system::error_code ec, size_t)
{
if (!ec)
{
stdqueAnswers.pop();
do_write();
}
});
}
tcp::socket socket_;
enum { max_length = 12000 };
char data_[max_length];
};
class server
{
public:
server(boost::asio::io_context& io_context, std::uint16_t port)
: acceptor_{ io_context, tcp::endpoint(tcp::v4(), port) }
{
acceptor_.listen();
do_accept();
}
private:
void do_accept()
{
acceptor_.async_accept(
make_strand(acceptor_.get_executor()),
[this](boost::system::error_code ec, tcp::socket socket)
{
if (!ec)
{
std::make_shared<session>(std::move(socket))->start();
do_accept();
}
});
}
tcp::acceptor acceptor_;
};
Like Ruslan explains, you should not use read_some, but a higher level operation that reads a full "message", as defined by your application level wire protocol.
You clearly already have some protocol (the leading bytes), and we cannot guess what the rest could be. For simplicity, let's assume that a full message ends with a \n character. Here's my simplifying take:
async_read_until(
socket_, boost::asio::dynamic_buffer(data_, max_length), "\n",
[this, self](boost::system::error_code ec, size_t length) {
std::cerr << "async_read_until() " << ec.message() << std::endl;
if (!ec) {
std::string msg = data_.substr(0, length /* - 1*/);
data_.erase(0, length);
if (!msg.empty() && msg.front() == '1') {
// we have to send a answer back to the client
wavefrontAccess->ReceiveCommandExternalGet(msg.substr(2));
handleCommand();
} else {
if (msg.find_first_not_of(' ') != std::string::npos) {
// There's a non-space
commandsQueue.push(msg);
}
}
do_read();
}
});
I've simplified by making your buffer std::string directly. This immediately "solves" the complexity that read_until might obviously read more than a single message.
Uncomment the /* -1 */ to exclude the \n character from the message
With some missing bits mocked up:
Live On Coliru
#include <boost/asio.hpp>
#include <deque>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <queue>
using boost::asio::ip::tcp;
class session : public std::enable_shared_from_this<session> {
public:
session(tcp::socket socket) : socket_(std::move(socket)) {}
void start() {
auto self(shared_from_this());
std::cerr << "start() " << socket_.remote_endpoint() << std::endl;
dispatch(socket_.get_executor(), [this, self] { do_read(); });
}
private:
void handleCommand() { enqueueAnswer(); }
void enqueueAnswer() {
if (stdqueAnswers.size() == 1) {
do_write();
}
}
void do_read() {
auto self(shared_from_this());
async_read_until(
socket_, boost::asio::dynamic_buffer(data_, max_length), "\n",
[this, self](boost::system::error_code ec, size_t length) {
std::cerr << "async_read_until() " << ec.message() << std::endl;
if (!ec) {
std::string msg = data_.substr(0, length /* - 1*/);
data_.erase(0, length);
if (!msg.empty() && msg.front() == '1') {
// we have to send a answer back to the client
wavefrontAccess->ReceiveCommandExternalGet(msg.substr(2));
handleCommand();
} else {
if (msg.find_first_not_of(' ') != std::string::npos) {
// There's a non-space
commandsQueue.push(msg);
}
}
do_read();
}
});
}
void do_write() {
if (stdqueAnswers.empty())
return;
auto self(shared_from_this());
async_write( //
socket_, boost::asio::buffer(stdqueAnswers.front()),
[this, self](boost::system::error_code ec, size_t) {
std::cerr << "async_write() " << ec.message() << std::endl;
if (!ec) {
stdqueAnswers.pop_back();
do_write();
}
});
}
enum { max_length = 12000 };
tcp::socket socket_;
std::string data_;
std::deque<std::string> stdqueAnswers;
std::queue<std::string> commandsQueue;
struct WavefrontAccess {
session* _sess;
void ReceiveCommandExternalGet(std::string cmd) {
_sess->stdqueAnswers.push_back("reply for '" + std::move(cmd) + "'");
}
};
std::unique_ptr<WavefrontAccess> wavefrontAccess =
std::make_unique<WavefrontAccess>(WavefrontAccess{this});
};
class server {
public:
server(boost::asio::io_context& io_context, uint16_t port)
: acceptor_{io_context, {{}, port}} {
acceptor_.listen();
do_accept();
}
private:
void do_accept() {
acceptor_.async_accept(
make_strand(acceptor_.get_executor()),
[this](boost::system::error_code ec, tcp::socket socket) {
std::cerr << "accept: " << ec.message() << " "
<< (ec ? tcp::endpoint{} : socket.remote_endpoint())
<< std::endl;
if (!ec) {
std::make_shared<session>(std::move(socket))->start();
do_accept();
}
});
}
tcp::acceptor acceptor_;
};
int main() {
boost::asio::io_context ioc(1);
server s(ioc, 7878);
ioc.run();
}
Even locally, messages may split into smaller TCP packets, for both client and server.
"Framing" protocol must be able to encode and decode sequence of variable sized messages unambiguously for any sequence of split parts (e.g. abc, ab c, a bc, a b c).
Your protocol can do "framing" by itself, or work on top of other "framing" protocol.
Example: TCP
Fixed length header (20 bytes) contains size(s) of variable size fields, including message content.
TCP reads 20 bytes.
TCP parses message size.
TCP reads that amount of bytes.
(repeat)
Example: HTTP
Header doesn't have fixed length.
However it's structure is unambiguous.
It's a text which ends with \r\n\r\n.
This text mustn't include \r\n\r\n, but it can represent it with escaping.
Somewhere in that text, there is size of message.
HTTP reads text (character by character) until it sees \r\n\r\n.
HTTP parses message size.
HTTP reads that amount of bytes.
(repeat)
Possible solution
If your message text doesn't have limitations (e.g. may include terminator/escape string), then you need to write message size before each message.
Otherwise if your messages have structure, you can write "terminator" (e.g. end-of-line or zero-terminator) after end each message, and read message until "terminator".

Do i need boost strand

I modified echo example but I have issues understanding boost strand.
Do I need or not?
I yes please be kind and give code for.
I searched examples but I can not understand (Im just stupid..)
Thank you
#include <cstdlib>
#include <iostream>
#include <memory>
#include <utility>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include "mingw.thread.h"
#include <boost/bind.hpp>
class session
: public std::enable_shared_from_this<session>
{
public:
session(boost::asio::ip::tcp::socket socket)
: socket_(std::move(socket))
{
}
void start()
{
input_buffer_str = "[2J[H[A\r\n*ServerV8*\r\nSCAN_BADGE:\r\n";
out_msg =input_buffer_str;
do_write(out_msg.length());
}
private:
void do_read()
{
// std::cout << "do_read" <<"" <<std::endl;
auto self(shared_from_this());
//async_read_until for Telnet client
async_read_until(socket_,input_buffer_,'\r',
[this, self](boost::system::error_code ec, std::size_t length)
{
if (!ec)
{
do_write(length);
}
else
{
// std::cout<<ec <<"Disconection?"<<std::endl;
input_buffer_str.clear();
out_msg.clear();
input_buffer_.consume(length);
// Some Cleanup
}
});
}
// async_write back to Telnet client based on received
// will be result on SQL Query
void do_write(std::size_t length)
{
auto self(shared_from_this());
std::istream(&input_buffer_) >> input_buffer_str;
input_buffer_.consume(length);
std::cout << input_buffer_str <<std::endl;
if (input_buffer_str == "c") close();
if (input_buffer_str == "a") out_msg = "test_a\r\n";
if (input_buffer_str == "t")
{
Sleep(30000);
out_msg = "test_t\r\n";
}
if(input_buffer_str.length()>0 && out_msg.length()>0)
{
input_buffer_str.clear();
boost::asio::async_write(socket_, boost::asio::buffer(out_msg, out_msg.length()),
[this, self](boost::system::error_code ec, std::size_t length)
{
if (!ec) do_read();
else
{
out_msg.clear();
input_buffer_.consume(length);
// Some Cleanup
}
out_msg.clear();
});
}else do_read();
}
// close() Telnet client will be when user pushes F2 (Handheld scanner)
void close()
{
std::cout <<"Closing..."<<std::endl;
boost::system::error_code ec;
input_buffer_str.clear();
out_msg.clear();
socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
Sleep(250);
socket_.close(ec);
// delete data_;
if (ec)
{
std::cout<<ec << std::endl;
// An error occurred.
}
std::cout <<"Closed..."<<std::endl;
}
boost::asio::streambuf input_buffer_;
std::string input_buffer_str;
std::string out_msg ;
boost::asio::ip::tcp::socket socket_;
//enum { max_length = 1024 };
//char data_[max_length];
//std::string ClearScreen= "[2J[H[A"; "\r\n*ServerV8*\r\nSCAN_BADGE:\r\n";
};
Copy paste from example
class server
{
public:
server(boost::asio::io_context& io_context, short port)
: acceptor_(io_context, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
{
do_accept();
}
private:
void do_accept()
{
acceptor_.async_accept(
[this](boost::system::error_code ec, boost::asio::ip::tcp::socket socket)
{
if (!ec)
{
std::make_shared<session>(std::move(socket))->start();
}
do_accept();
});
}
boost::asio::ip::tcp::acceptor acceptor_;
};
added Threads Some queries to sql might take depends on Network
int main(int argc, char* argv[])
{
auto thread_count = std::thread::hardware_concurrency(); // for multi core
boost::thread_group m_Threads;
boost::asio::io_context m_io_context;
server srv(m_io_context, 23);
for(unsigned int i = 0; i < thread_count; ++i)
m_Threads.create_thread( [&](){m_io_context.run();});
m_Threads.join_all();
return 0;
}

Boost asio send and receive messages

I'm trying to send and receive messages from a client and server using TCP. I'm trying it with threading, and I don't know how to do this at all. I can connect to the server just fine, but I need to be able to send and receive messages from both places on demand. I've been searching for hours and have come up empty, as all of the results on Google are overcomplicated and cluttering.
struct Client
{
boost::asio::ip::tcp::socket socket;
Client(const char* host = HOST, const char* port = PORT) : socket(io_service)
{
boost::asio::ip::tcp::resolver resolver(io_service);
boost::asio::ip::tcp::resolver::iterator endpoint = resolver.resolve(boost::asio::ip::tcp::resolver::query(HOST, PORT));
boost::asio::connect(this->socket, endpoint);
};
};
That's all I have so far for the client.
For the server:
const int PORT = 52275;
int main()
{
try
{
boost::asio::io_service io_service;
boost::asio::ip::tcp::acceptor acceptor(io_service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), PORT));
{
boost::asio::ip::tcp::socket socket(io_service);
acceptor.accept(socket);
main();
}
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << std::endl;
}
}
Since it's apparently safe to assume you just want to send /any/ message by the simplest means possible:
Live On Coliru
#include <boost/asio.hpp>
struct Client
{
boost::asio::io_service& io_service;
boost::asio::ip::tcp::socket socket;
Client(boost::asio::io_service& svc, std::string const& host, std::string const& port)
: io_service(svc), socket(io_service)
{
boost::asio::ip::tcp::resolver resolver(io_service);
boost::asio::ip::tcp::resolver::iterator endpoint = resolver.resolve(boost::asio::ip::tcp::resolver::query(host, port));
boost::asio::connect(this->socket, endpoint);
};
void send(std::string const& message) {
socket.send(boost::asio::buffer(message));
}
};
#include <iostream>
static const int PORT = 52275;
void client_thread() {
boost::asio::io_service svc;
Client client(svc, "127.0.0.1", std::to_string(PORT));
client.send("hello world\n");
client.send("bye world\n");
}
void server_thread() {
try
{
boost::asio::io_service io_service;
boost::asio::ip::tcp::acceptor acceptor(io_service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), PORT));
{
boost::asio::ip::tcp::socket socket(io_service);
acceptor.accept(socket);
boost::asio::streambuf sb;
boost::system::error_code ec;
while (boost::asio::read(socket, sb, ec)) {
std::cout << "received: '" << &sb << "'\n";
if (ec) {
std::cout << "status: " << ec.message() << "\n";
break;
}
}
}
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << std::endl;
}
}
#include <boost/thread.hpp>
int main() {
boost::thread_group tg;
tg.create_thread(server_thread);
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
tg.create_thread(client_thread);
tg.join_all();
}

Making an asynchronous Client with boost::asio

i'm trying to make an asynchronous Client with boost::asio,
i use the daytime asynchronous Server(in the tutorial).
However sometimes the Client don't receive the Message, sometimes it do :O
I'm sorry if this is too much code, but i don't know what's wrong :/
Client:
#include <iostream>
#include <stdio.h>
#include <ostream>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/array.hpp>
#include <boost/asio.hpp>
using namespace std;
using boost::asio::ip::tcp;
class TCPClient
{
public:
TCPClient(boost::asio::io_service& IO_Service, tcp::resolver::iterator EndPointIter);
void Write();
void Close();
private:
boost::asio::io_service& m_IOService;
tcp::socket m_Socket;
boost::array<char, 128> m_Buffer;
size_t m_BufLen;
private:
void OnConnect(const boost::system::error_code& ErrorCode,
tcp::resolver::iterator EndPointIter);
void OnReceive(const boost::system::error_code& ErrorCode);
void DoClose();
};
TCPClient::TCPClient(boost::asio::io_service& IO_Service, tcp::resolver::iterator EndPointIter)
: m_IOService(IO_Service), m_Socket(IO_Service)
{
tcp::endpoint EndPoint = *EndPointIter;
m_Socket.async_connect(EndPoint,
boost::bind(&TCPClient::OnConnect, this, boost::asio::placeholders::error, ++EndPointIter));
}
void TCPClient::Close()
{
m_IOService.post(
boost::bind(&TCPClient::DoClose, this));
}
void TCPClient::OnConnect(const boost::system::error_code& ErrorCode,
tcp::resolver::iterator EndPointIter)
{
if (ErrorCode == 0)
// Successful connected
{
m_Socket.async_receive(boost::asio::buffer(m_Buffer.data(), m_BufLen),
boost::bind(&TCPClient::OnReceive, this, boost::asio::placeholders::error));
} else if (EndPointIter != tcp::resolver::iterator())
{
m_Socket.close();
tcp::endpoint EndPoint = *EndPointIter;
m_Socket.async_connect(EndPoint,
boost::bind(&TCPClient::OnConnect, this, boost::asio::placeholders::error, ++EndPointIter));
}
}
void TCPClient::OnReceive(const boost::system::error_code& ErrorCode)
{
if (ErrorCode == 0)
{
std::cout << m_Buffer.data() << std::endl;
m_Socket.async_receive(boost::asio::buffer(m_Buffer.data(), m_BufLen),
boost::bind(&TCPClient::OnReceive, this, boost::asio::placeholders::error));
} else {
DoClose();
}
}
void TCPClient::DoClose()
{
m_Socket.close();
}
int main()
{
try {
boost::asio::io_service IO_Service;
tcp::resolver Resolver(IO_Service);
tcp::resolver::query Query("127.0.0.1", "daytime");
tcp::resolver::iterator EndPointIterator = Resolver.resolve(Query);
TCPClient Client(IO_Service, EndPointIterator);
boost::thread ClientThread(
boost::bind(&boost::asio::io_service::run, &IO_Service));
std::cout << "Client started." << std::endl;
std::string Input;
while (Input != "exit")
{
std::cin >> Input;
}
Client.Close();
ClientThread.join();
} catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
Server:
http://www.boost.org/doc/libs/1_39_0/doc/html/boost_asio/tutorial/tutdaytime3/src.html
Bother to init m_BufLen?

Resources