Related
I have the following simple coroutine-based server:
class Server
{
private:
boost::asio::io_service Service;
boost::asio::ip::tcp::acceptor Acceptor;
boost::asio::ip::tcp::socket Socket;
private:
void Accept(boost::asio::yield_context Yield);
void Write(boost::asio::yield_context Yield);
public:
Server(): Acceptor(Service), Socket(Service) {}
void Open(unsigned short PortNum);
void Run();
void Stop();
};
void Server::Accept(boost::asio::yield_context Yield)
{
boost::system::error_code ec;
for (;;)
{
Socket.close();
Acceptor.async_accept(Socket,Yield[ec]);
spawn(Yield,std::bind(&Server::Write,this,Yield[ec]));
}
}
void Server::Write(boost::asio::yield_context Yield)
{
char InBuffer[1024]= {};
std::size_t Size;
boost::system::error_code ec;
double Data= 6.66;
for (;;)
{
boost::asio::streambuf OutBuffer;
std::ostream os(&OutBuffer);
Size= Socket.async_read_some(boost::asio::buffer(InBuffer),Yield[ec]);
if (ec)
break;
os.write(reinterpret_cast<const char *>(&Data),sizeof(double));
Socket.async_write_some(OutBuffer.data(),Yield[ec]);
if (ec)
break;
}
}
void Server::Open(unsigned short PortNum)
{
Acceptor.open(boost::asio::ip::tcp::v4());
Acceptor.bind({{},PortNum});
Acceptor.listen();
}
void Server::Run()
{
spawn(Service,std::bind(&Server::Accept,this,std::placeholders::_1));
Service.run();
}
void Server::Stop()
{
Service.stop();
}
I want to run this server on a thread and stop it cleanly when the main program is about to finish:
int main()
{
Server s;
s.Open(1024);
std::thread Thread(&Server::Run,&s);
Sleep(10'000);
s.Stop();
Thread.join();
}
Unfortunately, if there is a connected socket, when I call Stop an exception boost::coroutines::detail::forced_unwind is thrown.
I have also tried creating an explicit strand and dispatching a Socket.close() before stopping with the same result.
Is there something wrong with this approach?
I’m having trouble trying to stop gracefully a similar server ( stackoverflow.com/questions/50833730/…). – metalfox 4 hours ago
Here's a minimal change that shows how to handle
an Exit command that closes a session
a Shutdown command that closes the server (so it stops accepting connections and terminates after the last session exits)
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
using boost::asio::ip::tcp;
using boost::system::error_code;
using boost::asio::streambuf;
int main() {
boost::asio::io_service svc;
tcp::acceptor a(svc);
a.open(tcp::v4());
a.set_option(tcp::acceptor::reuse_address(true));
a.bind({{}, 6767}); // bind to port 6767 on localhost
a.listen(5);
using session = std::shared_ptr<tcp::socket>;
std::function<void()> do_accept;
std::function<void(session)> do_session;
do_session = [&](session s) {
// do a read
auto buf = std::make_shared<boost::asio::streambuf>();
async_read_until(*s, *buf, "\n", [&,s,buf](error_code ec, size_t /*bytes*/) {
if (ec)
std::cerr << "read failed: " << ec.message() << "\n";
else {
std::istream is(buf.get());
std::string line;
while (getline(is, line)) // FIXME being sloppy with partially read lines
{
async_write(*s, boost::asio::buffer("Ack\n", 4), [&,s,buf](error_code ec, size_t) {
if (ec) std::cerr << "write failed: " << ec.message() << "\n";
});
if (line == "Exit") {
std::cout << "Exit received\n";
return;
}
if (line == "Shutdown") {
std::cout << "Server shutdown requested\n";
a.close();
return;
}
}
do_session(s); // full duplex, can read while writing, using a second buffer
}
});
};
do_accept = [&] {
auto s = std::make_shared<session::element_type>(svc);
a.async_accept(*s, [&,s](error_code ec) {
if (ec)
std::cerr << "accept failed: " << ec.message() << "\n";
else {
do_session(s);
do_accept(); // accept the next
}
});
};
do_accept(); // kick-off
svc.run(); // wait for shutdown (Ctrl-C or failure)
}
Note the sample sessions
echo -en "hello world\nExit\n" | netcat 127.0.0.1 6767
echo -en "hello world\nShutdown\n" | netcat 127.0.0.1 6767
Printing
Ack
Ack
Ack
Ack
Exit received
Server shutdown requested
accept failed: Operation canceled
A Terminate Command
If you want a "Terminate" command that actively closes all open sessions and shuts down the server, you'll have to
keep a list of sessions
or use signal
You can see code for both approaches here: Boost ASIO: Send message to all connected clients
The simplest way to integrate with the current sample:
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
#include <list>
using boost::asio::ip::tcp;
using boost::system::error_code;
using boost::asio::streambuf;
int main() {
boost::asio::io_service svc;
tcp::acceptor a(svc);
a.open(tcp::v4());
a.set_option(tcp::acceptor::reuse_address(true));
a.bind({{}, 6767}); // bind to port 6767 on localhost
a.listen(5);
using session = std::shared_ptr<tcp::socket>;
using sessref = std::weak_ptr<tcp::socket>;
std::function<void()> do_accept;
std::function<void(session)> do_session;
std::list<sessref> session_list;
auto garbage_collect_sessions = [&session_list] {
session_list.remove_if(std::mem_fn(&sessref::expired));
};
do_session = [&](session s) {
// do a read
auto buf = std::make_shared<boost::asio::streambuf>();
async_read_until(*s, *buf, "\n", [&,s,buf](error_code ec, size_t /*bytes*/) {
if (ec)
std::cerr << "read failed: " << ec.message() << "\n";
else {
std::istream is(buf.get());
std::string line;
while (getline(is, line)) // FIXME being sloppy with partially read lines
{
async_write(*s, boost::asio::buffer("Ack\n", 4), [&,s,buf](error_code ec, size_t) {
if (ec) std::cerr << "write failed: " << ec.message() << "\n";
});
if (line == "Exit") {
std::cout << "Exit received\n";
return;
}
if (line == "Shutdown") {
std::cout << "Server shutdown requested\n";
a.close();
return;
}
if (line == "Terminate") {
std::cout << "Server termination requested\n";
a.close();
for (auto wp : session_list) {
if (auto session = wp.lock())
session->close();
}
return;
}
}
do_session(s); // full duplex, can read while writing, using a second buffer
}
});
};
do_accept = [&] {
auto s = std::make_shared<session::element_type>(svc);
a.async_accept(*s, [&,s](error_code ec) {
if (ec)
std::cerr << "accept failed: " << ec.message() << "\n";
else {
garbage_collect_sessions();
session_list.push_back(s);
do_session(s);
do_accept(); // accept the next
}
});
};
do_accept(); // kick-off
svc.run(); // wait for shutdown (Ctrl-C or failure)
}
Which obvioiusly uses a session_list to implement the "Terminate" command:
if (line == "Terminate") {
std::cout << "Server termination requested\n";
a.close();
for (auto wp : session_list) {
if (auto session = wp.lock())
session->close();
}
return;
}
I am trying to combine the famous boost ssl client/server connection examples into a single program. For your kind reference, the base classes are like this:
#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
namespace bt
{
//
// client.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
enum { max_length = 1024 };
class client
{
public:
client(boost::asio::io_service& io_service, boost::asio::ssl::context& context,
boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
: socket_(io_service, context)
{
boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
socket_.lowest_layer().async_connect(endpoint,
boost::bind(&client::handle_connect, this,
boost::asio::placeholders::error, ++endpoint_iterator));
}
void handle_connect(const boost::system::error_code& error,
boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
{
std::cout << "handle_connect\n";
if (!error)
{
std::cout << "handle_connect No error\n";
socket_.async_handshake(boost::asio::ssl::stream_base::client,
boost::bind(&client::handle_handshake, this,
boost::asio::placeholders::error));
}
else if (endpoint_iterator != boost::asio::ip::tcp::resolver::iterator())
{
std::cout << "handle_connect retry!\n";
socket_.lowest_layer().close();
boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
socket_.lowest_layer().async_connect(endpoint,
boost::bind(&client::handle_connect, this,
boost::asio::placeholders::error, ++endpoint_iterator));
}
else
{
std::cout << "Connect failed: " << error << "\n";
}
}
void handle_handshake(const boost::system::error_code& error)
{
std::cout << "client handle_handshake\n";
if (!error)
{
std::cout << "Enter message: ";
// std::cin.getline(request_, max_length);
sprintf(request_, "%s", "Hi Testing...");
size_t request_length = strlen(request_);
boost::asio::async_write(socket_,
boost::asio::buffer(request_, request_length),
boost::bind(&client::handle_write, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
std::cout << "Handshake failed: " << error << "\n";
}
}
void handle_write(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
boost::asio::async_read(socket_,
boost::asio::buffer(reply_, bytes_transferred),
boost::bind(&client::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
std::cout << "Write failed: " << error << "\n";
}
}
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
std::cout << "Reply: ";
std::cout.write(reply_, bytes_transferred);
std::cout << "\n";
}
else
{
std::cout << "Read failed: " << error << "\n";
}
}
private:
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
char request_[max_length];
char reply_[max_length];
};
//
// server.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
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()
{
std::cout << "session start->handshake\n";
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)
{
std::cout << "session handle_handshake\n";
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;
}
}
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_(io_service, boost::asio::ssl::context::sslv23)
{
//std::cout << "server()\n";
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("server.crt");
context_.use_private_key_file("server.key", boost::asio::ssl::context::pem);
context_.use_tmp_dh_file("dh1024.pem");
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));
}
std::string get_password() const
{
return "test";
}
void handle_accept(session* new_session,
const boost::system::error_code& error)
{
std::cout << "server() handle_accept\n";
if (!error)
{
std::cout << "server() handle_accept !error\n";
new_session->start();
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));
}
else
{
std::cout << "server() handle_accept error:" << error.message() << std::endl;
delete new_session;
}
}
private:
boost::asio::io_service& io_service_;
boost::asio::ip::tcp::acceptor acceptor_;
boost::asio::ssl::context context_;
};
}//namespace bt
And the the main program is:
BOOST_AUTO_TEST_CASE(accept_ssl_connection_1)
{
boost::asio::io_service io_service_1;
boost::asio::io_service io_service_2;
int port = random_port();
std::stringstream i("");
i << port;
std::cout << "Port is:" << i.str() << std::endl;
//server
bt::server(io_service_1, port);
//client
boost::asio::ip::tcp::resolver resolver(io_service_2);
boost::asio::ip::tcp::resolver::query query("127.0.0.1", i.str());
boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);
boost::asio::ssl::context ctx(io_service_2, boost::asio::ssl::context::sslv23);
ctx.set_verify_mode(boost::asio::ssl::context::verify_peer);
ctx.load_verify_file("server.crt");
bt::client c(io_service_2, ctx, iterator);
boost::thread thread1(boost::bind(&boost::asio::io_service::run, &io_service_1));
boost::thread thread2(boost::bind(&boost::asio::io_service::run, &io_service_2));
thread1.join();
thread2.join();
}
And here is the output I am getting:
Port is:7200
server() handle_accept
handle_connect
Connect failed: system:111
server() handle_accept error:Operation canceled
The program works if clien and server are built and run individually. I guess I have a mistake in io_service usage.
Could you please help me detect the issue?
1. Style
I suggest you put more effort in making the code readable.
Code is for humans to read, not computers
In your case, the extreme brevity like
bt::client c(...);
Leads to bugs like
bt::server(io_service_1, port);
There's not a lot of difference with the - probably intended - variable declaration
bt::server s(io_service_1, port);
Otherwise, the newly constructed server is immediately destructed and thereby cancels all pending operations.
2. Debugging
Try to actually present readable messages:
std::cout << "Connect failed: " << error.message() << "\n";
std::cout << "Handshake failed: " << error.message() << "\n";
std::cout << "Write failed: " << error.message() << "\n";
std::cout << "Read failed: " << error.message() << "\n";
std::cout << "server() handle_accept error:" << error.message() << std::endl;
This would tell you that "125" means "Operation aborted" etc.. This is what made me add a little trace here and there:
~session() { std::cout << "Deleting session!\n"; }
~server() { std::cout << "Deleting server!\n"; }
2. Asio Review, more style
Instead of doing things manually, prefer the composed operations defined in boost:
client(ba::io_service &io_service, ssl::context &context, tcp::resolver::iterator endpoint_iterator)
: socket_(io_service, context)
{
ba::async_connect(socket_.lowest_layer(), endpoint_iterator,
boost::bind(&client::handle_connect, this, bap::error));
}
void handle_connect(const boost::system::error_code &error) {
std::cout << "handle_connect\n";
if (!error) {
std::cout << "handle_connect No error\n";
socket_.async_handshake(ssl::stream_base::client, boost::bind(&client::handle_handshake, this, bap::error));
} else {
std::cout << "Connect failed: " << error.message() << "\n";
}
}
This does the whole iterator dance. But less error-prone.
Use namespace aliases to get readable/manageable lines
using boost::asio::ip::tcp;
namespace ba = boost::asio;
namespace bap = boost::asio::placeholders;
namespace ssl = boost::asio::ssl;
Use smart pointers (delete this? ugh)
Consider using 1 io_service. Using two doesn't add anything, really, and the names didn't clarify a thing. In fact, the first minutes of staring at your code had me dis-entangling the code for client and server, painstakingly verifying that they didn't mistakenly use the wrong service, leading to premature run() completion.
Account for race conditions. In your code, server and client run independently unsynchronized. At least add a delay:
boost::this_thread::sleep_for(boost::chrono::seconds(1));
to avoid the client connecting to the server before it started accepting connections.
Prefer boost::thread_group over lose threads:
boost::thread_group tg;
// ...
tg.create_thread(boost::bind(&ba::io_service::run, &io_service_1));
// ...
tg.create_thread(boost::bind(&ba::io_service::run, &io_service_2));
// ...
tg.join_all();
In fact, with 1 io_service and 1 thread, you sidestep all of the above (the async operations are synchronized due the implicit strand)
use higherlevel standard library features (e.g. std::to_string(int) instead of std::ostringstream; if you cannot use c++11, use boost::lexical_cast or write your own to_string-type helper function).
If the address is hardcoded to loopback, no need to "resolve" anything: just connect to tcp::endpoint{{}, port}
Consider moving ctx into client (like you moved the ssl params for the server into that class too)
prefer boost::array/std::array over raw arrays (request_ and reply_)
Why do you read as many bytes as you sent? Did you mean
ba::async_read(socket_, ba::buffer(reply_, bytes_transferred),
boost::bind(&client::handle_read, this, bap::error, bap::bytes_transferred));
I'd expect something like
ba::async_read(socket_, ba::buffer(reply_, reply.size()), // assuming array<>, see previous
boost::bind(&client::handle_read, this, bap::error, bap::bytes_transferred));
Consider composed operations over read_some again. read_some may not read a complete request. Consider adding a framing protocol or sending request length up front.
Avoid code duplication: async_accept is coded twice. Instead make it a separate function and call it twice:
void do_accept() {
session::ptr new_session = boost::make_shared<session>(io_service_, context_);
acceptor_.async_accept(new_session->socket(), boost::bind(&server::handle_accept, this, new_session, bap::error));
}
BONUS
Add a deadline to the accept so we can stop the server at a certain idle time interval
Since you are using smart pointers now (aren't you?) it's easy to add a session shutdown at this place too (session::close())
Let's do two client for the price of one, just for fun
Live On Coliru
//#define BOOST_ASIO_ENABLE_HANDLER_TRACKING 1
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <cstdlib>
#include <iostream>
using boost::asio::ip::tcp;
namespace ba = boost::asio;
namespace bap = boost::asio::placeholders;
namespace ssl = boost::asio::ssl;
namespace bt {
enum { max_length = 1024, idle_timeout_seconds = 2 };
class client {
public:
client(ba::io_service &io_service, tcp::resolver::iterator endpoint_iterator, std::string const& request)
: ctx_(io_service, ssl::context::sslv23),
socket_(io_service, ctx_),
request_(request)
{
ctx_.set_verify_mode(ssl::context::verify_peer);
ctx_.load_verify_file("server.crt");
ba::async_connect(socket_.lowest_layer(), endpoint_iterator,
boost::bind(&client::handle_connect, this, bap::error));
}
void handle_connect(const boost::system::error_code &error) {
std::cout << "handle_connect\n";
if (!error) {
std::cout << "handle_connect No error\n";
socket_.async_handshake(ssl::stream_base::client, boost::bind(&client::handle_handshake, this, bap::error));
} else {
std::cout << "Connect failed: " << error.message() << "\n";
}
}
void handle_handshake(const boost::system::error_code &error) {
std::cout << "client handle_handshake\n";
if (!error) {
ba::async_write(socket_, ba::buffer(request_),
boost::bind(&client::handle_write, this, bap::error, bap::bytes_transferred));
} else {
std::cout << "Handshake failed: " << error.message() << "\n";
}
}
void handle_write(const boost::system::error_code &error, size_t bytes_transferred) {
if (!error) {
ba::async_read(socket_, ba::buffer(reply_, bytes_transferred),
boost::bind(&client::handle_read, this, bap::error, bap::bytes_transferred));
} else {
std::cout << "Write failed: " << error.message() << "\n";
}
}
void handle_read(const boost::system::error_code &error, size_t bytes_transferred) {
if (!error) {
std::cout << "Reply: ";
std::cout.write(reply_.data(), bytes_transferred);
std::cout << "\n";
} else {
std::cout << "Read failed: " << error.message() << "\n";
}
}
private:
ssl::context ctx_;
ssl::stream<tcp::socket> socket_;
std::string request_;
std::array<char, max_length> reply_;
};
class session : public boost::enable_shared_from_this<session> {
public:
using ptr = boost::shared_ptr<session>;
session(ba::io_service &io_service, ssl::context &context) : socket_(io_service, context) {}
typedef ssl::stream<tcp::socket> ssl_socket;
ssl_socket::lowest_layer_type &socket() { return socket_.lowest_layer(); }
void start() {
std::cout << "session start->handshake\n";
socket_.async_handshake(ssl::stream_base::server, boost::bind(&session::handle_handshake, shared_from_this(), bap::error));
}
void handle_handshake(const boost::system::error_code &error) {
std::cout << "session handle_handshake\n";
if (error) return;
socket_.async_read_some(ba::buffer(data_),
boost::bind(&session::handle_read, shared_from_this(), bap::error, bap::bytes_transferred));
}
void handle_read(const boost::system::error_code &error, size_t bytes_transferred) {
if (error) return;
ba::async_write(socket_, ba::buffer(data_, bytes_transferred),
boost::bind(&session::handle_write, shared_from_this(), bap::error));
}
void handle_write(const boost::system::error_code &error) {
if (error) return;
socket_.async_read_some(ba::buffer(data_),
boost::bind(&session::handle_read, shared_from_this(), bap::error, bap::bytes_transferred));
}
void close() {
socket_.get_io_service().post([this] {
std::cout << "session::close()\n";
socket_.lowest_layer().cancel();
socket_.lowest_layer().close();
});
}
~session() { std::cout << "Deleting session\n"; }
private:
ssl_socket socket_;
std::array<char, max_length> data_;
};
class server {
public:
server(ba::io_service &io_service, unsigned short port)
: io_service_(io_service), acceptor_(io_service, tcp::endpoint(tcp::v4(), port)),
context_(io_service, ssl::context::sslv23),
deadline_(io_service)
{
// std::cout << "server()\n";
context_.set_options(ssl::context::default_workarounds | ssl::context::no_sslv2 | ssl::context::single_dh_use);
context_.set_password_callback(boost::bind(&server::get_password, this));
context_.use_certificate_chain_file("server.crt");
context_.use_private_key_file("server.crt", ssl::context::pem);
context_.use_tmp_dh_file("dh2048.pem");
do_accept();
}
~server() { std::cout << "Deleting server\n"; }
std::string get_password() const { return "test"; }
void do_accept() {
session::ptr new_session = boost::make_shared<session>(io_service_, context_);
deadline_.expires_from_now(boost::posix_time::seconds(idle_timeout_seconds));
deadline_.async_wait(boost::bind(&server::handle_deadline, this, bap::error()));
acceptor_.async_accept(new_session->socket(), boost::bind(&server::handle_accept, this, new_session, bap::error));
}
void handle_accept(session::ptr new_session, const boost::system::error_code &error) {
std::cout << "server() handle_accept\n";
if (!error) {
std::cout << "server() handle_accept ok\n";
sessions_.push_back(new_session);
new_session->start();
do_accept();
} else {
std::cout << "server() handle_accept error:" << error.message() << std::endl;
}
}
void handle_deadline(boost::system::error_code ec) {
if (!ec) {
io_service_.post([this] {
// assuming 1 thread runs io_service, no more locking required
std::cout << "server() shutdown after idle timeout\n";
acceptor_.cancel();
acceptor_.close();
for (auto weak_sess : sessions_)
if (auto sess = weak_sess.lock())
sess->close();
});
}
}
private:
ba::io_service &io_service_;
tcp::acceptor acceptor_;
ssl::context context_;
ba::deadline_timer deadline_;
std::vector<boost::weak_ptr<session> > sessions_;
};
} // namespace bt
void accept_ssl_connection_1() {
ba::io_service svc;
int port = 6767;
std::cout << "Port is:" << port << std::endl;
// server
bt::server s(svc, port);
// client
tcp::resolver resolver(svc);
bt::client c(svc, resolver.resolve({"127.0.0.1", std::to_string(port)}), "Hello, I'm Bob");
bt::client d(svc, resolver.resolve({"127.0.0.1", std::to_string(port)}), "Hello, I'm Cindy");
svc.run();
}
int main() {
accept_ssl_connection_1();
}
Prints
Port is:6767
server() handle_accept
server() handle_accept ok
session start->handshake
handle_connect
handle_connect No error
handle_connect
handle_connect No error
server() handle_accept
server() handle_accept ok
session start->handshake
session handle_handshake
client handle_handshake
session handle_handshake
client handle_handshake
Reply: Hello, I'm Bob
Reply: Hello, I'm Cindy
server() shutdown after idle timeout
server() handle_accept
server() handle_accept error:Operation canceled
Deleting session
session::close()
session::close()
Deleting session
Deleting session
Deleting server
Error code 111 (ECONNREFUSED) means (in Linux):
"The target address was not listening for connections or refused the
connection request."
It usually occurs when a client try to connect to a server, and no one is listening the port. Possible reasons:
the server program is not running
the server program uses different TCP port number than the client
the server program is still starting. The port is not yet bound, when client try to connect.
In your case, the problem could be the option #3. Because you face the problem when the client and the server are started almost the same time.
I didn't check all of your code, is it really possible that client try connect before the server is ready.
I have the following code, trying to code an asynchronous client.
The problem is that in main(), the Client gets deleted in the try-catch block, because execution leaves the scope.
I've tried to come up with a solution to this problem, like adding a while(true), but I don't like this approach. Also, I don't prefer a getchar().
Due to the asynchronous nature of the calls, both connect() and loop() returns immediately.
How can I fix this?
#include <iostream>
#include <thread>
#include <string>
#include <boost\asio.hpp>
#include <Windows.h>
#define DELIM "\r\n"
using namespace boost;
class Client {
public:
Client(const std::string& raw_ip_address, unsigned short port_num) :
m_ep(asio::ip::address::from_string(raw_ip_address), port_num), m_sock(m_ios)
{
m_work.reset(new asio::io_service::work(m_ios));
m_thread.reset(new std::thread([this]() {
m_ios.run();
}));
m_sock.open(m_ep.protocol());
}
void connect()
{
m_sock.async_connect(m_ep, [this](const system::error_code& ec)
{
if (ec != 0) {
std::cout << "async_connect() error: " << ec.message() << " (" << ec.value() << ") " << std::endl;
return;
}
std::cout << "Connection to server has been established." << std::endl;
});
}
void loop()
{
std::thread t = std::thread([this]()
{
recv();
});
t.join();
}
void recv()
{
asio::async_read_until(m_sock, buf, DELIM, [this](const system::error_code& ec, std::size_t bytes_transferred)
{
if (ec != 0) {
std::cout << "async_read_until() error: " << ec.message() << " (" << ec.value() << ") " << std::endl;
return;
}
std::istream is(&buf);
std::string req;
std::getline(is, req, '\r');
is.get(); // discard newline
std::cout << "Received: " << req << std::endl;
if (req == "alive") {
recv();
}
else if (req == "close") {
close();
return;
}
else {
send(req + DELIM);
}
});
}
void send(std::string resp)
{
auto s = std::make_shared<std::string>(resp);
asio::async_write(m_sock, asio::buffer(*s), [this, s](const system::error_code& ec, std::size_t bytes_transferred)
{
if (ec != 0) {
std::cout << "async_write() error: " << ec.message() << " (" << ec.value() << ") " << std::endl;
return;
}
else {
recv();
}
});
}
void close()
{
m_sock.close();
m_work.reset();
m_thread->join();
}
private:
asio::io_service m_ios;
asio::ip::tcp::endpoint m_ep;
asio::ip::tcp::socket m_sock;
std::unique_ptr<asio::io_service::work> m_work;
std::unique_ptr<std::thread> m_thread;
asio::streambuf buf;
};
int main()
{
const std::string raw_ip_address = "127.0.0.1";
const unsigned short port_num = 8001;
try {
Client client(raw_ip_address, port_num);
client.connect();
client.loop();
}
catch (system::system_error &err) {
std::cout << "main() error: " << err.what() << " (" << err.code() << ") " << std::endl;
return err.code().value();
}
return 0;
}
You've not really understood how asio works. Typically in the main thread(s) you will call io_service::run() (which will handle all the asynchronous events.)
To ensure the lifetime of the Client, use a shared_ptr<> and ensure this shared pointer is used in the handlers. For example..
io_service service;
{
// Create the client - outside of this scope, asio will manage
// the life time of the client
auto client = make_shared<Client>(service);
client->connect(); // setup the connect operation..
}
// Now run the io service event loop - this will block until there are no more
// events to handle
service.run();
Now you need to refactor your Client code:
class Client : public std::enable_shared_from_this<Client> {
Client(io_service& service): socket_(service) ...
{ }
void connect() {
// By copying the shared ptr to the lambda, the life time of
// Client is guaranteed
socket_.async_connect(endpoint_, [self = this->shared_from_this()](auto ec)
{
if (ec) {
return;
}
// Read
self->read(self);
});
}
void read(shared_ptr<Client> self) {
// By copying the shared ptr to the lambda, the life time of
// Client is guaranteed
asio::async_read_until(socket_, buffer_, DELIM, [self](auto ec, auto size)
{
if (ec) {
return;
}
// Handle the data
// Setup the next read operation
self->read(self)
});
}
};
You have a thread for the read operation - which is not necessary. That will register one async read operation and return immediately. You need to register a new read operation to continue reading the socket (as I've sketched out..)
You can post any function to io_service via post(Handler)
http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/reference/io_service/post.html
Then in the main() do something like:
while (!exit) {
io_service.run_one();
}
Or call io_service::run_one or io_service::run in the main()
Currently I'm looking into sending data with Boost ASIO. I understand that io_service is a abstraction of the underlying OS-dependend IO functionality and that the run() call will poll all outstanding handles in the asio queue and finishs afterwards.
But looking at the SSL example of asio (see below for code) I don't know how I can manage to keep the connection (or session, or whatever) open and read and write data over this connection outside of the normal 'workflow' of the client.
That means: if I call my send() method of the client right in handle_handshake the message is send. But if I try to call c.send() from my main method, nothing happens after Client Handshake success.
Why is send() handled different in the descriped scenario? Is io_service already finished?
Note: Please do not scare about the amount of code below. It is the example code provided with ASIO standalone. I only added a custom send() method to the client and a main to execute client and server in one running example.
Edit
I also tried using asio::io_service::work work(io_service); but the client still does nothing after the handshake.
class session{
public:
session(asio::io_service& io_service,
asio::ssl::context& context)
: socket_(io_service, context) { }
ssl_socket::lowest_layer_type& socket(){
return socket_.lowest_layer();
}
void start(){
std::cout << " Session start "<< "\n";
socket_.async_handshake(asio::ssl::stream_base::server,
std::bind(&session::handle_handshake, this,
std::placeholders::_1));
}
void handle_handshake(const asio::error_code& error){
if (!error){
std::cout << " Session handshake success "<< "\n";
socket_.async_read_some(asio::buffer(data_, max_length),
std::bind(&session::handle_read, this,
std::placeholders::_1, std::placeholders::_2));
}
else {
std::cout << " Session handshake failed "<< "\n";
delete this;
}
}
void handle_read(const asio::error_code& error,
size_t bytes_transferred) {
if (!error) {
std::cout << " Session Read success "<< "\n";
asio::async_write(socket_,
asio::buffer(data_, bytes_transferred),
std::bind(&session::handle_write, this,
std::placeholders::_1));
}
else {
std::cout << " Session Read failed "<< "\n";
delete this;
}
}
void handle_write(const asio::error_code& error){
if (!error){
std::cout << " Write success "<< "\n";
socket_.async_read_some(asio::buffer(data_, max_length),
std::bind(&session::handle_read, this,
std::placeholders::_1,
std::placeholders::_2));
}
else{
delete this;
}
}
private:
ssl_socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class server
{
public:
server(asio::io_service& io_service, unsigned short port)
: io_service_(io_service),
acceptor_(io_service,
asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)),
context_(asio::ssl::context::sslv23) {
context_.set_options(
asio::ssl::context::default_workarounds
| asio::ssl::context::no_sslv2
| asio::ssl::context::single_dh_use);
context_.set_password_callback(std::bind(&server::get_password, this));
context_.use_certificate_chain_file("server.pem");
context_.use_private_key_file("server.pem", asio::ssl::context::pem);
context_.use_tmp_dh_file("dh1024.pem");
start_accept();
}
std::string get_password() const{
return "test";
}
void start_accept(){
std::cout << " Server accept start "<< "\n";
session* new_session = new session(io_service_, context_);
acceptor_.async_accept(new_session->socket(),
std::bind(&server::handle_accept, this, new_session,
std::placeholders::_1));
}
void handle_accept(session* new_session,
const asio::error_code& error){
if (!error){
std::cout << " Server Accept success "<< "\n";
new_session->start();
}
else
{
delete new_session;
}
start_accept();
}
private:
asio::io_service& io_service_;
asio::ip::tcp::acceptor acceptor_;
asio::ssl::context context_;
};
enum { max_length = 1024 };
class client
{
public:
client(asio::io_service& io_service,
asio::ssl::context& context,
asio::ip::tcp::resolver::iterator endpoint_iterator)
: socket_(io_service, context) {
socket_.set_verify_mode(asio::ssl::verify_peer);
socket_.set_verify_callback(
std::bind(&client::verify_certificate, this, std::placeholders::_1, std::placeholders::_2));
asio::async_connect(socket_.lowest_layer(), endpoint_iterator,
std::bind(&client::handle_connect, this,
std::placeholders::_1));
}
bool verify_certificate(bool preverified,
asio::ssl::verify_context& ctx){
return true;
}
void handle_connect(const asio::error_code& error){
if (!error){
std::cout << "Client Connect success "<< "\n";
socket_.async_handshake(asio::ssl::stream_base::client,
std::bind(&client::handle_handshake, this,
std::placeholders::_1));
}
else {
std::cout << "Client Connect failed: " << error.message() << "\n";
}
}
void handle_handshake(const asio::error_code& error){
if (!error) {
std::cout << "Client Handshake success "<< "\n";
//send("test") no send here
}
else{
std::cout << "Client Handshake failed: " << error.message() << "\n";
}
}
void send(char * request_){
std::cout << " Client Sende daten "<< "\n";
size_t request_length = strlen(request_);
asio::async_write(socket_,
asio::buffer(request_, request_length),
std::bind(&client::handle_write, this,
std::placeholders::_1,
std::placeholders::_2));
}
void handle_write(const asio::error_code& error, size_t bytes_transferred){
if (!error)
{
std::cout << " Client Write success "<< "\n";
asio::async_read(socket_,
asio::buffer(reply_, bytes_transferred),
std::bind(&client::handle_read, this,
std::placeholders::_1,
std::placeholders::_2));
}
else {
std::cout << "Client Write failed: " << error.message() << "\n";
}
}
void handle_read(const asio::error_code& error, size_t bytes_transferred) {
if (!error) {
std::cout << "Client Reply: ";
std::cout.write(reply_, bytes_transferred);
std::cout << "\n";
}
else {
std::cout << "Client Read failed: " << error.message() << "\n";
}
}
private:
asio::ssl::stream<asio::ip::tcp::socket> socket_;
char request_[max_length];
char reply_[max_length];
};
using namespace std;
int main(){
std::thread t {
[](){
asio::io_service server_service;
server s(server_service, 8877);
server_service.run();
}
};
std::this_thread::sleep_for(std::chrono::milliseconds(3000));
asio::io_service io_service;
asio::ip::tcp::resolver resolver(io_service);
asio::ip::tcp::resolver::query query("127.0.0.1", std::to_string(8877));
asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);
asio::ssl::context ctx(asio::ssl::context::sslv23);
ctx.load_verify_file("ca.pem");
client c(io_service, ctx, iterator);
c.send("test");
io_service.run();
t.join();
return 0;
}
I've just started working with boost.
I'm writting TCP client-server with async sockets.
The task is the following:
Client send to server a number
Client can send another nubmer before receiving server's answer.
Server receives a number, do some computing with it and send back the result to client.
Multiple clients can be connected to server.
Now works the following
send a number from client to sever
server recieves a number in current thread and computes right in the OnReceive handler (I know this is bad...but how I should start a new thread to do computing in parallel)
server sends answer back but client already disconnected
How can allow client to input numbers from keyboard and to wait an answer from the server at the same time?
And why does my client not wait for the answer from sever?
The client code:
using boost::asio::ip::tcp;
class TCPClient
{
public:
TCPClient(boost::asio::io_service& IO_Service, tcp::resolver::iterator EndPointIter);
void Close();
private:
boost::asio::io_service& m_IOService;
tcp::socket m_Socket;
string m_SendBuffer;
static const size_t m_BufLen = 100;
char m_RecieveBuffer[m_BufLen*2];
void OnConnect(const boost::system::error_code& ErrorCode, tcp::resolver::iterator EndPointIter);
void OnReceive(const boost::system::error_code& ErrorCode);
void OnSend(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), m_SendBuffer("")
{
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)
{
cout << "OnConnect..." << endl;
if (ErrorCode == 0)
{
cin >> m_SendBuffer;
cout << "Entered: " << m_SendBuffer << endl;
m_SendBuffer += "\0";
m_Socket.async_send(boost::asio::buffer(m_SendBuffer.c_str(),m_SendBuffer.length()+1),
boost::bind(&TCPClient::OnSend, 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)
{
cout << "receiving..." << endl;
if (ErrorCode == 0)
{
cout << m_RecieveBuffer << endl;
m_Socket.async_receive(boost::asio::buffer(m_RecieveBuffer, m_BufLen),
boost::bind(&TCPClient::OnReceive, this, boost::asio::placeholders::error));
}
else
{
cout << "ERROR! OnReceive..." << endl;
DoClose();
}
}
void TCPClient::OnSend(const boost::system::error_code& ErrorCode)
{
cout << "sending..." << endl;
if (!ErrorCode)
{
cout << "\""<< m_SendBuffer <<"\" has been sent" << endl;
m_SendBuffer = "";
m_Socket.async_receive(boost::asio::buffer(m_RecieveBuffer, m_BufLen),
boost::bind(&TCPClient::OnReceive, this, boost::asio::placeholders::error));
}
else
{
cout << "OnSend closing" << endl;
DoClose();
}
}
void TCPClient::DoClose()
{
m_Socket.close();
}
int main()
{
try
{
cout << "Client is starting..." << endl;
boost::asio::io_service IO_Service;
tcp::resolver Resolver(IO_Service);
string port = "13";
tcp::resolver::query Query("127.0.0.1", port);
tcp::resolver::iterator EndPointIterator = Resolver.resolve(Query);
TCPClient Client(IO_Service, EndPointIterator);
cout << "Client is started!" << endl;
cout << "Enter a query string " << endl;
boost::thread ClientThread(boost::bind(&boost::asio::io_service::run, &IO_Service));
Client.Close();
ClientThread.join();
}
catch (exception& e)
{
cerr << e.what() << endl;
}
cout << "\nClosing";
getch();
}
Here is output from console
Client is starting...
Client is started!
OnConnect...
12
Entered: 12
sending...
"12" has been sent
receiving...
ERROR! OnReceive...
Closing
Server part
class Session
{
public:
Session(boost::asio::io_service& io_service)
: socket_(io_service)
{
dataRx[0] = '\0';
dataTx[0] = '\0';
}
tcp::socket& socket()
{
return socket_;
}
void start()
{
socket_.async_read_some(boost::asio::buffer(dataRx, max_length),
boost::bind(&Session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_read(const boost::system::error_code& error, size_t bytes_transferred)
{
cout << "reading..." << endl;
cout << "Data: " << dataRx << endl;
if (!error)
{
if (!isValidData())
{
cout << "Bad data!" << endl;
sprintf(dataTx, "Bad data!\0");
dataRx[0] = '\0';
}
else
{
sprintf(dataTx, getFactorization().c_str());
dataRx[0] = '\0';
}
boost::asio::async_write(socket_,
boost::asio::buffer(dataTx, max_length*2),
boost::bind(&Session::handle_write, this,
boost::asio::placeholders::error));
}
else
{
delete this;
}
}
void handle_write(const boost::system::error_code& error)
{
cout << "writing..." << endl;
if (!error)
{
cout << "dataTx sent: " << dataTx << endl;
dataTx[0] = '\0';
socket_.async_read_some(boost::asio::buffer(dataRx, max_length),
boost::bind(&Session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
delete this;
}
}
string getFactorization() const
{
//Do something
}
bool isValidData()
{
locale loc;
for (int i = 0; i < strlen(dataRx); i++)
if (!isdigit(dataRx[i],loc))
return false;
return true;
}
private:
tcp::socket socket_;
static const size_t max_length = 100;
char dataRx[max_length];
char dataTx[max_length*2];
};
class Server
{
public:
Server(boost::asio::io_service& io_service, short port)
: io_service_(io_service),
acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
{
Session* new_session = new Session(io_service_);
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)
{
new_session->start();
new_session = new Session(io_service_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&Server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
else
{
delete new_session;
}
}
private:
boost::asio::io_service& io_service_;
tcp::acceptor acceptor_;
};
int main(int argc, char* argv[])
{
cout << "Server is runing..." << endl;
try
{
boost::asio::io_service io_service;
int port = 13;
Server s(io_service, port);
cout << "Server is run!" << endl;
io_service.run();
}
catch (boost::system::error_code& e)
{
std::cerr << e << "\n";
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
Server's ouput
Server is runing...
Server is run!
reading...
Data: 12
writing...
dataTx sent: 13 //just send back received ++number
reading...
Data:
Your help will be very appreciated
========
Added
Ok, I understand. But check ErrorCode == boost::asio::error::eof does not works... What have I done wrong?
else if (ErrorCode == boost::asio::error::eof)
{
cout << "boost::asio::error::eof in OnReceive!" << endl;
}
else
{
cout << "ERROR! OnReceive..." << ErrorCode << endl;
DoClose();
}
The print out is ERROR! OnReceive...system:10009 it seems to be my comparison is incorrect
========
Added
I found the root cause. I've stated use async_receive (instead of async_read_some) and swaped the lines in main to
ClientThread.join();
Client.Close();
Now it works fine!
Now I'm trying to read and write data from/to socket at the same time (because the client should be able to sent additional requests before answer from the server is recieved.
In OnConnect function I create boost threads:
boost::thread addMsgThread(boost::bind(&TCPClient::addMsgLoop, this));
boost::thread receivingThread(boost::bind(&TCPClient::startReceiving, this));
boost::thread sendingThread(boost::bind(&TCPClient::startSending, this));
with inplementation
void TCPClient::startReceiving()
{
cout << "receiving..." << endl;
m_RecieveBuffer[0] = '\0';
m_Socket.async_receive(boost::asio::buffer(m_RecieveBuffer, m_BufLen),
boost::bind(&TCPClient::receivingLoop, this, boost::asio::placeholders::error)); //runtime error here
cout << "m_RecieveBuffer = " << m_RecieveBuffer << endl;
}
void TCPClient::receivingLoop(const boost::system::error_code& ErrorCode)
{
cout << "receiving..." << endl;
if (ErrorCode == 0)
{
cout << "m_RecieveBuffer = " << m_RecieveBuffer << endl;
m_Socket.async_receive(boost::asio::buffer(m_RecieveBuffer, m_BufLen),
boost::bind(&TCPClient::receivingLoop, this, boost::asio::placeholders::error));
}
else
{
cout << "ERROR! receivingLoop..." << ErrorCode << endl;
DoClose();
}
}
void TCPClient::addMsgLoop()
{
while (true)
{
string tmp;
cin >> tmp;
cout << "Entered: " << tmp << endl;
tmp += "\0";
try
{
msgQueue.push(tmp);
}
catch(exception &e)
{
cerr << "Canno add msg to send queue... " << e.what() << endl;
}
}
}
The issue is the same with both receive and send threads: runtime error (writing access violation somewhere in boost libraries).
void TCPClient::startReceiving()
{
...
m_Socket.async_receive(); //runtime error here
}
In sequent version all works fine (but I don't know how to implement multiple sending before answer).
Can anybody tell me how to fix the issue or how implement this by another way? May be pooling can help but I'm now sure that it is good way.
boost::asio::ip::tcp::socket::async_read_some as the name suggests is not guaranteed to read complete data. It sets error object to boost::asio::error::eof when client is finished writing.
The error you are getting is because of this:
server part
if (!error)
{
...
}
else
{
delete this;
}
In else block, you are assuming that this is a error case and closing the connection. This is not always the case. Before else you need to check for error == boost::asio::error::eof.
Apart from this in read handler, you should keep collecting whatever is read in a buffer till you hit error == boost::asio::error::eof. Only then you should validate read data and write back to client.
Take a look at HTTP server 1, 2, 3 implementation in examples section.
Update: Answer to updated question
You have thread synchronization issue with the updated code.
msgQueue is simultaneously accessed from two or more threads without any lock.
Read and write on the same socket can be called simultaneously.
If I understood your problem correctly, you want to:
take user input and send that to server.
Keep receiving server's response simultaneously.
You can use two boost::asio::io_service::strands for the two tasks. When using Asio, strands are the way to synchronize your tasks. Asio makes sure that tasks posted in a strand are executed synchronously.
In strand1 post a send task that looks like: read_user_input -> send_to_server -> handle_send -> read_user_input
In strand2 post a read task that looks like: read_some -> handle_read -> read_some
This will make sure msgQueue is not accessed simultaneously from two threads. Use two sockets for read and write to server, to make sure simultaneous read and write is not called on the same socket.