I am trying to connect to a secure websocket using asio.
This example will work for an ip address:
#include <iostream>
#include <asio.hpp>
int main() {
asio::error_code ec;
asio::io_context context;
asio::io_context::work idleWork(context);
asio::ip::tcp::endpoint endpoint(asio::ip::make_address("51.38.81.49", ec), 80);
asio::ip::tcp::socket socket(context);
socket.connect(endpoint, ec);
if (!ec) {
std::cout << "Connected!" << std::endl;
} else {
std::cout << "Failed to connect to address: \n" << ec.message() << std::endl;
}
return 0;
}
but how would I change it so I connect to "wss://api2.example.com"?
EDIT:
Thanks for your answer karastojko - it seems to get me some of the way. I would though like to know if I am actually connected to the server, so I have updated my example with your input, added a working WSS which I know will answer and created read and write.
#include <asio.hpp>
#include <asio/ts/buffer.hpp>
std::vector<char> vBuffer(1 * 1024);
// This should output the received data
void GrabSomeData(asio::ip::tcp::socket& socket) {
socket.async_read_some(asio::buffer(vBuffer.data(), vBuffer.size()),
[&](std::error_code ec, std::size_t lenght) {
if (!ec) {
std::cout << "\n\nRead " << lenght << " bytes\n\n";
for (int i = 0; i < lenght; i++)
std::cout << vBuffer[i];
GrabSomeData(socket);
}
}
);
}
int main() {
asio::error_code ec;
asio::io_context context;
asio::io_context::work idleWork(context);
std::thread thrContext = std::thread([&]() { context.run(); });
// I hope this is what you meant
asio::ip::tcp::resolver res(context);
asio::ip::tcp::socket socket(context);
asio::connect(socket, res.resolve("echo.websocket.org", std::to_string(443)));
// Check the socket is open
if (socket.is_open()) {
// Start to output incoming data
GrabSomeData(socket);
// Send data to the websocket, which should be sent back
std::string sRequest = "Echo";
socket.write_some(asio::buffer(sRequest.data(), sRequest.size()), ec);
// Wait some time, so the data is received
using namespace std::chrono_literals;
std::this_thread::sleep_for(20000ms);
context.stop();
if (thrContext.joinable()) thrContext.join();
}
return 0;
}
For that purpose use the resolver class:
tcp::resolver res(context);
asio::ip::tcp::socket socket(context);
boost::asio::connect(socket, res.resolve("api2.example.com", 80));
Related
I am building a programme with C++ in which I want to run "two threads looking at two sources for data asynchronously". I do not know if this is possible or not. I have so far been able to read data from one source with a single thread. I want to read data from two threads asynchronously. Has anyone done something like this in the past that can point me in the right direction? I am using the asio C++ standalone version. Or is this something that is just not possible?
I will really appreciate you advice.
This is my code so far.
#include <iostream>
#define ASIO_STANDALONE
#include <asio.hpp>
#include <asio/ts/buffer.hpp>
#include <asio/ts/internet.hpp>
std::vector<char> vBuffer(2 * 1024);
void getData1(asio::ip::tcp::socket& socket1)
{
socket1.async_read_some(asio::buffer(vBuffer.data(), vBuffer.size()),
[&](std::error_code ec, std::size_t length)
{
if (!ec)
{
std::cout << "\n\nRead " << length << " bytes\n\n";
for (int i = 0; i < length; i++)
std::cout << vBuffer[i];
getData1(socket1);
}
}
);
}
//Make it read from two sources
void getData2(asio::ip::tcp::socket& socket1)
{
socket1.async_read_some(asio::buffer(vBuffer.data(), vBuffer.size()),
[&](std::error_code ec, std::size_t length)
{
if (!ec)
{
std::cout << "\n\nRead " << length << " bytes\n\n";
for (int i = 0; i < length; i++)
std::cout << vBuffer[i];
getData2(socket1);
}
}
);
}
int main() {
asio::error_code ec;
asio::io_context context1;
asio::io_context context2; //This to read data fron another source, eg another ip address and port
//Allow asio to do some fake tasks so that the context doesn't finish
asio::io_context::work idleWork(context1);
//Start the context
std::thread thrContext = std::thread([&]() {context1.run(); });
std::thread thrContext2 = std::thread([&]() {context2.run();});
//Get the address of somewhere we wish to connect to
asio::ip::tcp::endpoint endpoint(asio::ip::make_address("51.38.81.49", ec), 80); //ip exists
asio::ip::tcp::endpoint endpoint2(asio::ip::make_address("127.0.0.1", ec), 80); //ip exists
//Create a socket the, the context will deliver the implementation
asio::ip::tcp::socket socket1(context1);
asio::ip::tcp::socket socket2(context2);
//Tell the socket to connect to the first address specified
socket1.connect(endpoint, ec);
//Tell the socket to connect to the second address specified
socket2.connect(endpoint2, ec);
//Here we can check the error code to see if it was successful
if (!ec)
{
std::cout << "Connected successfully" << std::endl;
}
else
{
std::cout << "Failed to connect to address:\n" << ec.message() << std::endl;
}
//Here we check if socket is open or not with an if statement
if (socket1.is_open())
{
getData1(socket1); //Here we are calling our function as against calling it at the buttom part
std::string sRequest =
"GET /index.html HTTP/1.1\r\n"
"Host: example.com\r\n\r\n";
socket1.write_some(asio::buffer(sRequest.data(), sRequest.size()), ec);
//Stop programme from exiting prematurely
using namespace std::chrono_literals;
std::this_thread::sleep_for(2000ms);
}
system("pause");
return 0;
}
I want to send a struct from client to server using boost::asio. I followed boost tutorial link https://www.boost.org/doc/libs/1_47_0/doc/html/boost_asio/examples.html#boost_asio.examples.serialization. I slighty modified the code in server.cpp and client.cpp. With the new code, after a connection is established, client.cpp writes the struct stock to server and reads stock information at server side. (in the tutorial version, after a connection established, the server writes stock struct to client and client reads them. This version works for me.)
My problem is that after a connection is established, the async_write in client.cpp causes error
Error in write: An existing connection was forcibly closed by the remote host
and the async_read in server.cpp causes error
Error in read:The network connection was aborted by the local system.
As suggested by some forum answers, I changed this pointers in function handlers of async_write and async_read to shared_from_this. Still the problem exists.
I am not able to identify whether the client or the server side is causing problem. Please help.
server.cpp
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <vector>
#include "connection.h" // Must come before boost/serialization headers.
#include <boost/serialization/vector.hpp>
#include <boost/enable_shared_from_this.hpp>
#include "stock.h"
namespace s11n_example
{
/// Serves stock quote information to any client that connects to it.
class server : public boost::enable_shared_from_this<server>
{
private:
/// The acceptor object used to accept incoming socket connections.
boost::asio::ip::tcp::acceptor acceptor_;
/// The data to be sent to each client.
std::vector<stock> stocks_;
public:
/// Constructor opens the acceptor and starts waiting for the first incoming
/// connection.
server(boost::asio::io_service& io_service, unsigned short port):
acceptor_(io_service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
{
// Start an accept operation for a new connection.
connection_ptr new_conn(new connection(acceptor_.get_io_service()));
acceptor_.async_accept(new_conn->socket(),
boost::bind(&server::handle_accept, this,boost::asio::placeholders::error, new_conn));
}
/// Handle completion of a accept operation.
void handle_accept(const boost::system::error_code& e, connection_ptr conn)
{
if (!e)
{
std::cout << "Received a connection" <<std::endl;
conn->async_read(stocks_,
boost::bind(&server::handle_read, shared_from_this(),boost::asio::placeholders::error));
}
// Start an accept operation for a new connection.
connection_ptr new_conn(new connection(acceptor_.get_io_service()));
acceptor_.async_accept(new_conn->socket(),
boost::bind(&server::handle_accept, this,boost::asio::placeholders::error, new_conn));
}
/// Handle completion of a read operation.
void handle_read(const boost::system::error_code& e)
{
if (!e)
{
// Print out the data that was received.
for (std::size_t i = 0; i < stocks_.size(); ++i)
{
std::cout << "Stock number " << i << "\n";
std::cout << " code: " << stocks_[i].code << "\n";
std::cout << " name: " << stocks_[i].name << "\n";
std::cout << " open_price: " << stocks_[i].open_price << "\n";
std::cout << " high_price: " << stocks_[i].high_price << "\n";
std::cout << " low_price: " << stocks_[i].low_price << "\n";
std::cout << " last_price: " << stocks_[i].last_price << "\n";
std::cout << " buy_price: " << stocks_[i].buy_price << "\n";
std::cout << " buy_quantity: " << stocks_[i].buy_quantity << "\n";
std::cout << " sell_price: " << stocks_[i].sell_price << "\n";
std::cout << " sell_quantity: " << stocks_[i].sell_quantity << "\n";
}
}
else
{
// An error occurred.
std::cerr << "Error in read:" << e.message() << std::endl;
}
}
};
} // namespace s11n_example
int main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if (argc != 2)
{
std::cerr << "Usage: server <port>" << std::endl;
return 1;
}
unsigned short port = boost::lexical_cast<unsigned short>(argv[1]);
boost::asio::io_service io_service;
boost::shared_ptr<s11n_example::server> server(new s11n_example::server(io_service, port));
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
client.cpp
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <vector>
#include "connection.h" // Must come before boost/serialization headers.
#include <boost/serialization/vector.hpp>
#include <boost/enable_shared_from_this.hpp>
#include "stock.h"
namespace s11n_example {
/// Downloads stock quote information from a server.
class client : public boost::enable_shared_from_this<client>
{
private:
/// The connection to the server.
connection connection_;
/// The data received from the server.
std::vector<stock> stocks_;
public:
/// Constructor starts the asynchronous connect operation.
client(boost::asio::io_service& io_service, const std::string& host, const std::string& service)
: connection_(io_service)
{
// Resolve the host name into an IP address.
boost::asio::ip::tcp::resolver resolver(io_service);
boost::asio::ip::tcp::resolver::query query(host, service);
boost::asio::ip::tcp::resolver::iterator endpoint_iterator =
resolver.resolve(query);
// Start an asynchronous connect operation.
boost::asio::async_connect(connection_.socket(), endpoint_iterator,
boost::bind(&client::handle_connect, this,boost::asio::placeholders::error));
}
/// Handle completion of a connect operation.
void handle_connect(const boost::system::error_code& e) //, connection_ptr conn
{
if (!e)
{
std::cout << "Connected to server!" << std::endl;
// Create the data to be sent to each client.
stock s;
s.code = "ABC";
s.name = "A Big Company";
s.open_price = 4.56;
s.high_price = 5.12;
s.low_price = 4.33;
s.last_price = 4.98;
s.buy_price = 4.96;
s.buy_quantity = 1000;
s.sell_price = 4.99;
s.sell_quantity = 2000;
stocks_.push_back(s);
s.code = "DEF";
s.name = "Developer Entertainment Firm";
s.open_price = 20.24;
s.high_price = 22.88;
s.low_price = 19.50;
s.last_price = 19.76;
s.buy_price = 19.72;
s.buy_quantity = 34000;
s.sell_price = 19.85;
s.sell_quantity = 45000;
stocks_.push_back(s);
// Successfully established connection. Start operation to write the list
// of stocks.
connection_.async_write(stocks_,
boost::bind(&client::handle_write, shared_from_this(),boost::asio::placeholders::error)); //,&conn )
}
else
{
// An error occurred. Log it and return.
std::cerr << "Error in connecting to server" << e.message() << std::endl;
}
}
/// Handle completion of a write operation.
void handle_write(const boost::system::error_code& e)//, connection* conn
{
if (!e)
{
std::cout << "Finished writing to server" << std::endl;
}
else
{
// An error occurred. Log it and return. Since we are not starting a new
// operation the io_service will run out of work to do and the client will
// exit.
std::cerr << "Error in write: " << e.message() << std::endl;
}
// Nothing to do. The socket will be closed automatically when the last
// reference to the connection object goes away.
}
};
} // namespace s11n_example
int main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if (argc != 3)
{
std::cerr << "Usage: client <host> <port>" << std::endl;
return 1;
}
boost::asio::io_service io_service;
//s11n_example::client client(io_service, argv[1], argv[2]);
boost::shared_ptr<s11n_example::client> client(new s11n_example::client(io_service, argv[1], argv[2]));
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
Thanks.
You need to pass conn to handle read otherwise it will be destructed at the end of the handle_accept method. When it's destructed the socket it contains will also be destructed and the connection will close.
conn->async_read(stocks_,
boost::bind(&server::handle_read, shared_from_this(), conn, boost::asio::placeholders::error));
Lambdas make this easier to read than using bind:
auto self = shared_from_this();
conn->async_read(stocks_,
[self, this, conn] (boost::system::error_code ec) { handle_read(ec); });
The variables listed in the capture list will be copied so the shared pointers will be kept alive.
I have the following RESTServer implemented using boost::beast. The way the server is started is using
void http_server(tcp::acceptor& acceptor, tcp::socket& socket).
Logically acceptor and socket should logically belong to http_connection class,instead of as a separate function outside. What is the reason it is implemented like this?
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio.hpp>
#include <chrono>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <memory>
#include <string>
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
namespace net = boost::asio; // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace my_program_state
{
std::size_t
request_count()
{
static std::size_t count = 0;
return ++count;
}
std::time_t
now()
{
return std::time(0);
}
}
class http_connection : public std::enable_shared_from_this<http_connection>
{
public:
http_connection(tcp::socket socket)
: socket_(std::move(socket))
{
}
// Initiate the asynchronous operations associated with the connection.
void
start()
{
read_request();
check_deadline();
}
private:
// The socket for the currently connected client.
tcp::socket socket_;
// The buffer for performing reads.
beast::flat_buffer buffer_{8192};
// The request message.
http::request<http::dynamic_body> request_;
// The response message.
http::response<http::dynamic_body> response_;
// The timer for putting a deadline on connection processing.
net::steady_timer deadline_{
socket_.get_executor(), std::chrono::seconds(60)};
// Asynchronously receive a complete request message.
void
read_request()
{
auto self = shared_from_this();
http::async_read(
socket_,
buffer_,
request_,
[self](beast::error_code ec,
std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
if(!ec)
self->process_request();
});
}
// Determine what needs to be done with the request message.
void
process_request()
{
response_.version(request_.version());
response_.keep_alive(false);
switch(request_.method())
{
case http::verb::get:
response_.result(http::status::ok);
response_.set(http::field::server, "Beast");
create_response();
break;
default:
// We return responses indicating an error if
// we do not recognize the request method.
response_.result(http::status::bad_request);
response_.set(http::field::content_type, "text/plain");
beast::ostream(response_.body())
<< "Invalid request-method '"
<< std::string(request_.method_string())
<< "'";
break;
}
write_response();
}
// Construct a response message based on the program state.
void
create_response()
{
if(request_.target() == "/count")
{
response_.set(http::field::content_type, "text/html");
beast::ostream(response_.body())
<< "<html>\n"
<< "<head><title>Request count</title></head>\n"
<< "<body>\n"
<< "<h1>Request count</h1>\n"
<< "<p>There have been "
<< my_program_state::request_count()
<< " requests so far.</p>\n"
<< "</body>\n"
<< "</html>\n";
}
else if(request_.target() == "/time")
{
response_.set(http::field::content_type, "text/html");
beast::ostream(response_.body())
<< "<html>\n"
<< "<head><title>Current time</title></head>\n"
<< "<body>\n"
<< "<h1>Current time</h1>\n"
<< "<p>The current time is "
<< my_program_state::now()
<< " seconds since the epoch.</p>\n"
<< "</body>\n"
<< "</html>\n";
}
else
{
response_.result(http::status::not_found);
response_.set(http::field::content_type, "text/plain");
beast::ostream(response_.body()) << "File not found\r\n";
}
}
// Asynchronously transmit the response message.
void
write_response()
{
auto self = shared_from_this();
response_.set(http::field::content_length, response_.body().size());
http::async_write(
socket_,
response_,
[self](beast::error_code ec, std::size_t)
{
self->socket_.shutdown(tcp::socket::shutdown_send, ec);
self->deadline_.cancel();
});
}
// Check whether we have spent enough time on this connection.
void
check_deadline()
{
auto self = shared_from_this();
deadline_.async_wait(
[self](beast::error_code ec)
{
if(!ec)
{
// Close socket to cancel any outstanding operation.
self->socket_.close(ec);
}
});
}
};
// "Loop" forever accepting new connections.
void
http_server(tcp::acceptor& acceptor, tcp::socket& socket)
{
acceptor.async_accept(socket,
[&](beast::error_code ec)
{
if(!ec)
std::make_shared<http_connection>(std::move(socket))->start();
http_server(acceptor, socket);
});
}
int
main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if(argc != 3)
{
std::cerr << "Usage: " << argv[0] << " <address> <port>\n";
std::cerr << " For IPv4, try:\n";
std::cerr << " receiver 0.0.0.0 80\n";
std::cerr << " For IPv6, try:\n";
std::cerr << " receiver 0::0 80\n";
return EXIT_FAILURE;
}
auto const address = net::ip::make_address(argv[1]);
unsigned short port = static_cast<unsigned short>(std::atoi(argv[2]));
net::io_context ioc{1};
tcp::acceptor acceptor{ioc, {address, port}};
tcp::socket socket{ioc};
http_server(acceptor, socket);
ioc.run();
}
catch(std::exception const& e)
{
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
}
One reason would be the author wanted to separate the logic.
Moreover i think it would complicate the procedure to create new sessions if you would move the listener code into the client session.
The acceptor and socket object should stay independent, you may reference it in your client and use it if you want but since these a more "global" and unique objects, it should stay outside of the session. Instead it can be also put into a separate class.
Roughly speaking the acceptor should just listen for incoming connection attempts from remote hosts and create the sessions accordingly.
I have a http server (boost beast) taken from here Boost Beast HTTP Server. The function void
http_server(tcp::acceptor& acceptor, tcp::socket& socket) keeps the server always running. I want to know if there is a graceful way to shutdown the server.
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio.hpp>
#include <chrono>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <memory>
#include <string>
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
namespace net = boost::asio; // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace my_program_state
{
std::size_t
request_count()
{
static std::size_t count = 0;
return ++count;
}
std::time_t
now()
{
return std::time(0);
}
}
class http_connection : public std::enable_shared_from_this<http_connection>
{
public:
http_connection(tcp::socket socket)
: socket_(std::move(socket))
{
}
// Initiate the asynchronous operations associated with the connection.
void
start()
{
read_request();
check_deadline();
}
private:
// The socket for the currently connected client.
tcp::socket socket_;
// The buffer for performing reads.
beast::flat_buffer buffer_{8192};
// The request message.
http::request<http::dynamic_body> request_;
// The response message.
http::response<http::dynamic_body> response_;
// The timer for putting a deadline on connection processing.
net::steady_timer deadline_{
socket_.get_executor(), std::chrono::seconds(60)};
// Asynchronously receive a complete request message.
void
read_request()
{
auto self = shared_from_this();
http::async_read(
socket_,
buffer_,
request_,
[self](beast::error_code ec,
std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
if(!ec)
self->process_request();
});
}
// Determine what needs to be done with the request message.
void
process_request()
{
response_.version(request_.version());
response_.keep_alive(false);
switch(request_.method())
{
case http::verb::get:
response_.result(http::status::ok);
response_.set(http::field::server, "Beast");
create_response();
break;
default:
// We return responses indicating an error if
// we do not recognize the request method.
response_.result(http::status::bad_request);
response_.set(http::field::content_type, "text/plain");
beast::ostream(response_.body())
<< "Invalid request-method '"
<< std::string(request_.method_string())
<< "'";
break;
}
write_response();
}
// Construct a response message based on the program state.
void
create_response()
{
if(request_.target() == "/count")
{
response_.set(http::field::content_type, "text/html");
beast::ostream(response_.body())
<< "<html>\n"
<< "<head><title>Request count</title></head>\n"
<< "<body>\n"
<< "<h1>Request count</h1>\n"
<< "<p>There have been "
<< my_program_state::request_count()
<< " requests so far.</p>\n"
<< "</body>\n"
<< "</html>\n";
}
else if(request_.target() == "/time")
{
response_.set(http::field::content_type, "text/html");
beast::ostream(response_.body())
<< "<html>\n"
<< "<head><title>Current time</title></head>\n"
<< "<body>\n"
<< "<h1>Current time</h1>\n"
<< "<p>The current time is "
<< my_program_state::now()
<< " seconds since the epoch.</p>\n"
<< "</body>\n"
<< "</html>\n";
}
else
{
response_.result(http::status::not_found);
response_.set(http::field::content_type, "text/plain");
beast::ostream(response_.body()) << "File not found\r\n";
}
}
// Asynchronously transmit the response message.
void
write_response()
{
auto self = shared_from_this();
response_.set(http::field::content_length, response_.body().size());
http::async_write(
socket_,
response_,
[self](beast::error_code ec, std::size_t)
{
self->socket_.shutdown(tcp::socket::shutdown_send, ec);
self->deadline_.cancel();
});
}
// Check whether we have spent enough time on this connection.
void
check_deadline()
{
auto self = shared_from_this();
deadline_.async_wait(
[self](beast::error_code ec)
{
if(!ec)
{
// Close socket to cancel any outstanding operation.
self->socket_.close(ec);
}
});
}
};
// "Loop" forever accepting new connections.
void
http_server(tcp::acceptor& acceptor, tcp::socket& socket)
{
acceptor.async_accept(socket,
[&](beast::error_code ec)
{
if(!ec)
std::make_shared<http_connection>(std::move(socket))->start();
http_server(acceptor, socket);
});
}
int
main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if(argc != 3)
{
std::cerr << "Usage: " << argv[0] << " <address> <port>\n";
std::cerr << " For IPv4, try:\n";
std::cerr << " receiver 0.0.0.0 80\n";
std::cerr << " For IPv6, try:\n";
std::cerr << " receiver 0::0 80\n";
return EXIT_FAILURE;
}
auto const address = net::ip::make_address(argv[1]);
unsigned short port = static_cast<unsigned short>(std::atoi(argv[2]));
net::io_context ioc{1};
tcp::acceptor acceptor{ioc, {address, port}};
tcp::socket socket{ioc};
http_server(acceptor, socket);
ioc.run();
}
catch(std::exception const& e)
{
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
Call the stop method on your io_context object to make it break out of the run loop.
That is:
ioc.stop();
https://www.boost.org/doc/libs/1_72_0/doc/html/boost_asio/reference/io_context/stop.html
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;
}