I've implemented simple boost::asio program that starts tcp connection.
It works perfect on linux (ubuntu 12.04, boost 1_48, gcc 4.6.4),
but not on Win7 (boost 1_55, vs2008express).
After accepting few (from 3 to 20) connections it hangs up, and doesn't accept connections anymore.
Is it some windows protection problem? I turn off firewall and antivirus.
Here is the code for reference:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
class Session : public boost::enable_shared_from_this<Session>
{
public:
Session(boost::asio::io_service &io_) : tcpSocket(io_)
{
std::cerr << "session ctor" << std::endl;
}
~Session()
{
std::cerr << "session Dtor" << std::endl;
}
boost::asio::ip::tcp::socket& getTcpSocket() { return this->tcpSocket; }
private:
boost::asio::ip::tcp::socket tcpSocket;
};
class Server
{
public:
static const unsigned int defaultPort = 55550;
Server();
void start();
private:
boost::asio::io_service io;
boost::asio::ip::tcp::acceptor acceptor;
void startAccept();
void handleAccept(boost::shared_ptr<Session> s_,
const boost::system::error_code& e_);
};
Server::Server()
: acceptor(io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), defaultPort))
{
}
void Server::start()
{
this->startAccept();
this->io.run();
}
void Server::startAccept()
{
boost::shared_ptr<Session> s(new Session(io));
acceptor.async_accept(s->getTcpSocket(), boost::bind(&Server::handleAccept,
this, s, boost::asio::placeholders::error));
}
void Server::handleAccept(boost::shared_ptr<Session> s_,
const boost::system::error_code& e_)
{
std::cerr << "handleAccept" << std::endl;
if(e_)
std::cerr << e_ << std::endl;
this->startAccept();
}
int main(int, char**)
{
Server server;
server.start();
}
EDIT:
Problem solved. It was Cygwin problem.
It could be that the Win7 acceptor requires the option to reuse the address: i.e. SO_REUSEADDR.
Try changing your server constructor to:
Server::Server()
: acceptor(io)
{
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), defaultPort);
acceptor.open(endpoint.protocol());
acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
boost::system::error_code ec;
acceptor.bind(endpoint, ec);
if(ec)
// raise an exception or log this error, etc.
}
Related
I try to open a UDP server. A baby example works (I receive what I expect and what wireshark also shows):
Baby example:
int main(int argc, char* const argv[])
{
try
{
boost::asio::io_context io_context;
boost::asio::ip::udp::endpoint ep(boost::asio::ip::udp::v4(), 60001);
boost::asio::ip::udp::socket sock(io_context, ep);
UDPServer server(std::move(sock), callbackUDP);
io_context.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
UDPServer.hpp:
#include <boost/asio.hpp>
#include <functional>
#include <vector>
#include <thread>
#define BUFFERSIZE 1501
class UDPServer
{
public:
explicit UDPServer(boost::asio::ip::udp::socket socket, std::function<void(const std::vector<char>&)> callbackFunction);
virtual ~UDPServer();
private:
void read();
boost::asio::ip::udp::socket socket_;
boost::asio::ip::udp::endpoint endpoint_;
std::function<void(const std::vector<char>&)> callbackFunction_;
char data_[1500 + 1]; // 1500 bytes is safe limit as it is max of ethernet frame, +1 is for \0 terminator
};
UDPServer.cpp:
#include <iostream>
#include "UDPServer.h"
UDPServer::UDPServer(boost::asio::ip::udp::socket socket, std::function<void(const std::vector<char>&)> callbackFunction):
socket_(std::move(socket)),
callbackFunction_(callbackFunction)
{
read();
}
UDPServer::~UDPServer()
{
}
void UDPServer::read()
{
socket_.async_receive_from(boost::asio::buffer(data_, 1500), endpoint_,
[this](boost::system::error_code ec, std::size_t length)
{
if (ec)
{
return;
}
data_[length] = '\0';
if (strcmp(data_, "\n") == 0)
{
return;
}
std::vector<char> dataVector(data_, data_ + length);
callbackFunction_(dataVector);
read();
}
);
}
Now what I want to convert this to is a class with as constructor only the port and a callback function (let forget about the latter and just print the message for now, adding the callback is normally no problem).
I tried the following, but it doesn't work:
int main(int argc, char* const argv[])
{
UDPServer server(60001);
}
UDPServer.h:
#include <boost/asio.hpp>
#include <functional>
#include <vector>
#include <thread>
#define BUFFERSIZE 1501
class UDPServer
{
public:
explicit UDPServer(uint16_t port);
virtual ~UDPServer();
private:
boost::asio::io_context io_context_;
boost::asio::ip::udp::socket socket_;
boost::asio::ip::udp::endpoint endpoint_;
std::array<char, BUFFERSIZE> recv_buffer_;
std::thread thread_;
void run();
void start_receive();
void handle_reply(const boost::system::error_code& error, std::size_t bytes_transferred);
};
UDPServer.cpp:
#include <iostream>
#include "UDPServer.h"
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <iostream>
UDPServer::UDPServer(uint16_t port):
endpoint_(boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port)),
io_context_(),
socket_(io_context_, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port)),
thread_(&UDPServer::run, this)
{
start_receive();
}
UDPServer::~UDPServer()
{
io_context_.stop();
thread_.join();
}
void UDPServer::start_receive()
{
socket_.async_receive_from(boost::asio::buffer(recv_buffer_), endpoint_,
boost::bind(&UDPServer::handle_reply, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void UDPServer::handle_reply(const boost::system::error_code& error, std::size_t bytes_transferred)
{
if (!error)
{
try {
std::string string(recv_buffer_.data(), recv_buffer_.data() + bytes_transferred);
std::cout << "Message received: " << std::to_string(bytes_transferred) << ", " << string << std::endl;
}
catch (std::exception ex) {
std::cout << "handle_reply: Error parsing incoming message:" << ex.what() << std::endl;
}
catch (...)
{
std::cout << "handle_reply: Unknown error while parsing incoming message" << std::endl;
}
}
else
{
std::cout << "handle_reply: error: " << error.message() << std::endl;
}
start_receive();
}
void UDPServer::run()
{
try {
io_context_.run();
} catch( const std::exception& e )
{
std::cout << "Server network exception: " << e.what() << std::endl;
}
catch(...)
{
std::cout << "Unknown exception in server network thread" << std::endl;
}
std::cout << "Server network thread stopped" << std::endl;
};
When running I get "Server network thread stopped". io_context doesn't seem to start and doesn't block. Someone an idea what I do wrong? Thanks a lot!
EDIT tried this after comment, same result (except that message comes after 1 second)
UDPServer::UDPServer(uint16_t port):
endpoint_(boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port)),
io_context_(),
socket_(io_context_, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port))
{
start_receive();
std::this_thread::sleep_for (std::chrono::seconds(1));
thread_ = std::thread(&UDPServer::run, this);
}
Your destructor explicitly tells the service to stop:
UDPServer::~UDPServer() {
io_context_.stop();
thread_.join();
}
That's part of your problem. The other part is as pointed out in the comment: you have a race condition where the thread exits before you even post your first async operation.
Solve it by adding a work guard:
boost::asio::io_context io_;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> work_ {io_.get_executor()};
Now the destructor can be:
UDPServer::~UDPServer() {
work_.reset(); // allow service to run out of work
thread_.join();
}
Other notes:
avoid chaining back to start_receive when there was an error
std::to_string was redundant
the order of initialization for members is defined by the order of their declaration, not their initializers in the initializer list. Catch these bug sources with -Wall -Wextra -pedantic
= handle exceptions in your service thread (see Should the exception thrown by boost::asio::io_service::run() be caught?)
I'd suggest std::bind over boost::bind:
std::bind(&UDPServer::handle_reply, this,
std::placeholders::_1,
std::placeholders::_2));
Or just use a lambda:
[this](error_code ec, size_t xfer) { handle_reply(ec, xfer); });
LIVE DEMO
Compiler Explorer
#include <boost/asio.hpp>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <thread>
#include <vector>
using boost::asio::ip::udp;
using boost::system::error_code;
using boost::asio::io_context;
#define BUFFERSIZE 1501
class UDPServer {
public:
explicit UDPServer(uint16_t port);
virtual ~UDPServer();
private:
io_context io_;
boost::asio::executor_work_guard<io_context::executor_type> work_ {io_.get_executor()};
udp::endpoint endpoint_;
udp::socket socket_;
std::array<char, BUFFERSIZE> recv_buffer_;
std::thread thread_;
void run();
void start_receive();
void handle_reply(const error_code& error, size_t transferred);
};
UDPServer::UDPServer(uint16_t port)
: endpoint_(udp::endpoint(udp::v4(), port)),
socket_(io_, endpoint_),
thread_(&UDPServer::run, this) {
start_receive();
}
UDPServer::~UDPServer() {
work_.reset(); // allow service to run out of work
thread_.join();
}
void UDPServer::start_receive() {
socket_.async_receive_from(boost::asio::buffer(recv_buffer_), endpoint_,
#if 0
std::bind(&UDPServer::handle_reply, this,
std::placeholders::_1,
std::placeholders::_2));
#else
[this](error_code ec, size_t xfer) { handle_reply(ec, xfer); });
#endif
}
void UDPServer::handle_reply(const error_code& error, size_t transferred) {
if (!error) {
try {
std::string_view s(recv_buffer_.data(), transferred);
std::cout << "Message received: " << transferred << ", "
<< std::quoted(s) << "\n";
} catch (std::exception const& ex) {
std::cout << "handle_reply: Error parsing incoming message:"
<< ex.what() << "\n";
} catch (...) {
std::cout
<< "handle_reply: Unknown error while parsing incoming message\n";
}
start_receive();
} else {
std::cout << "handle_reply: error: " << error.message() << "\n";
}
}
void UDPServer::run() {
while (true) {
try {
if (io_.run() == 0u) {
break;
}
} catch (const std::exception& e) {
std::cout << "Server network exception: " << e.what() << "\n";
} catch (...) {
std::cout << "Unknown exception in server network thread\n";
}
}
std::cout << "Server network thread stopped\n";
}
int main() {
std::cout << std::unitbuf;
UDPServer server(60001);
}
Testing with random words:
sort -R /etc/dictionaries-common/words | while read w; do sleep 1; netcat -u localhost 60001 -w 0 <<<"$w"; done
Live output:
I was doing an echo-server example in boost::asio. But using boost::make_shard will cause "unknown exception" while std::make_shared will not. See the commented line.
I am using visual studio 2017 and boost 1.67.The commented part will crash while the uncommented part will not. It seems the boost lib is complied with vs141 in the boost/stage/lib.
#include <iostream>
#include <boost/asio.hpp>
#include <boost/make_shared.hpp>
using boost::asio::ip::tcp;
using std::cout;
using std::endl;
using std::size_t;
class session :public std::enable_shared_from_this<session> {
public:
session(tcp::socket socket) :socket_(std::move(socket)) {
}
void start() {
do_read();
}
private:
void do_read() {
auto self(shared_from_this());
socket_.async_read_some(boost::asio::buffer(buffer_, maxlen),
[this, self](boost::system::error_code error, size_t read_len) {
if (!error) {
do_write(read_len);
}
else {
cout << error.message() << endl;
}
});
}
void do_write(size_t len_write) {
auto self(shared_from_this());
boost::asio::async_write(socket_, boost::asio::buffer(buffer_, len_write),
[this, self](boost::system::error_code error, size_t byte_transferred) {
if (!error) {
do_read();
}
else {
cout << "error 3,only " << byte_transferred << " bytes writen" << endl;
}
});
}
tcp::socket socket_;
enum { maxlen = 1024 };
char buffer_[maxlen];
};
class server {
public:
server(boost::asio::io_context& io_context, short port)
:acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) {
start();
}
void start() {
acceptor_.async_accept(
[this](boost::system::error_code ec, tcp::socket socket)
{
if (!ec)
{
std::make_shared<session>(std::move(socket))->start();
// boost::make_shared<session>(std::move(socket))->start();
}
start();
});
}
private:
tcp::acceptor acceptor_;
};
int main(int argc, char **argv) {
if (argc != 2) {
cout << "usage: server.exe Port" << endl;
}
try {
boost::asio::io_context io_context;
server server(io_context, std::atoi(argv[1]));
io_context.run();
}
catch (std::exception e) {
cout << e.what() << endl;
}
}
When you want to use boost shared pointers
boost::make_shared<session>(std::move(socket))->start();
You must inherit your class from boost adapter class like below:
class session :public boost::enable_shared_from_this<session> {
I did the boost tutorial : An asynchronous TCP daytime server http://think-async.com/Asio/asio-1.1.1/doc/asio/tutorial/tutdaytime3.html
When I want to test it, the server is running so that's good but if I do nc -C localhost 4242 the client got the message of the server but the client is directly disconnected after.
Here my code :
#include "server.h"
#include "connection.h"
Server::Server(boost::asio::io_service& io_service) : accept(io_service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 4242))
{
wait_connection();
}
Server::~Server()
{
}
void Server::wait_connection()
{
std::cout << "wait_connection" << std::endl;
boost::shared_ptr<Connection> new_connection =
Connection::start_connection(accept.get_io_service());
accept.async_accept(new_connection->getSocket(), boost::bind(&Server::callback_accept, this, new_connection, boost::asio::placeholders::error));
}
void Server::callback_accept(boost::shared_ptr<Connection> new_connection, const boost::system::error_code &error)
{
if (!error)
{
new_connection->send_message_to_client();
wait_connection();
}
}
Connection::Connection(boost::asio::io_service& io_service) : socket(io_service)
{
}
Connection::~Connection()
{
std::cout << "destructeur Connection" << std::endl;
}
boost::shared_ptr<Connection> Connection::start_connection(boost::asio::io_service& io_service)
{
return (boost::shared_ptr<Connection>(new Connection(io_service)));
}
boost::asio::ip::tcp::socket& Connection::getSocket()
{
return (this->socket);
}
void Connection::send_message_to_client()
{
message = "Bienvenue!\n";
boost::asio::async_write(socket, boost::asio::buffer(message), boost::bind(&Connection::callback_send, shared_from_this()));
}
void Connection::callback_send()
{
}
int main()
{
try {
boost::asio::io_service io_service;
Server server(io_service);
io_service.run();
}
catch (std::exception& e) {
std::cerr << e.what() << std::endl;
}
return (0);
}
#ifndef SERVER_H
#define SERVER_H
#include <iostream>
#include <string>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>
#include "connection.h"
class Server {
private:
boost::asio::ip::tcp::acceptor accept;
public:
Server (boost::asio::io_service&);
~Server ();
void wait_connection();
void callback_accept(boost::shared_ptr<Connection> new_connection, const boost::system::error_code& error);
};
#endif /* end of include guard: SERVER_H */
#ifndef CONNECTION_H
#define CONNECTION_H
#include <iostream>
#include <string>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>
class Connection : public boost::enable_shared_from_this<Connection>
{
private:
boost::asio::ip::tcp::socket socket;
std::string message;
public:
Connection (boost::asio::io_service&);
~Connection ();
static boost::shared_ptr<Connection> start_connection(boost::asio::io_service&);
boost::asio::ip::tcp::socket& getSocket();
void send_message_to_client();
void callback_send();
};
#endif /* end of include guard: CONNECTION_H */
Crux: Shared pointers employ keep the object alive until the reference count reaches zero.
You write the message to the client here. When it's complete, you will execute callback_send:
boost::asio::async_write(socket, boost::asio::buffer(message),
boost::bind(&Connection::callback_send, shared_from_this()));
So what do we do next?
void Connection::callback_send() {}
Oh. That's... not a lot. So. Nothing?
Well. Almost nothing.
It's a case of "not doing something is also doing something". By not posting another operation that keeps the socket/connection alive, this means that the connection is going to be released.
Because nothing else keeps the shared_ptr to the connection, shared_ptr will delete the connection (invoking the destructor, which you could see because it prints destructeur Connection every time).
So. What is the solution? We Don't Know. It's up to you what you want to do after you said "welcome". In most likely-hood you will want to wait for some kind of message from the client. This would involve some async_read* call which happily keeps the connection alive (shared_from_this() again).
Demo
Let's assume you want to keep receiving lines, and you send the same lines back, reversed:
void Connection::callback_send() {
boost::asio::async_read_until(socket, request, "\n",
boost::bind(&Connection::on_request_received, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void Connection::on_request_received(boost::system::error_code ec, size_t n) {
if (ec && !((ec == boost::asio::error::eof) && n))
std::cout << "Receive error: " << ec.message() << "\n";
else
{
std::cout << "Received request\n";
{
std::istream is(&request);
std::getline(is, message);
}
std::reverse(message.begin(), message.end());
std::cout << "Sending response: " << message << "\n";
message += '\n';
if (!ec) boost::asio::async_write(socket, boost::asio::buffer(message),
boost::bind(&Connection::callback_send, shared_from_this()));
}
}
I want to create an application that implements one-thread-per-connection model. But each connection must be stoppable. I have tried this boost.asio example which implements the blocking version of what I want. But after a little bit questioning I've found out that there is no reliable way to stop the session of that example. So I've tried to implement my own. I had to use asynchronous functions. Since I want to make a thread to manage only one connection and there is no way to control which asynchronous job is employed to which thread, I decided to use io_service for each connection/socket/thread.
So is it a good approach, do you know a better approach?
My code is here so you can examine and review it:
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/array.hpp>
#include <boost/thread.hpp>
#include <boost/scoped_ptr.hpp>
#include <list>
#include <iostream>
#include <string>
#include <istream>
namespace ba = boost::asio;
namespace bs = boost::system;
namespace b = boost;
typedef ba::ip::tcp::acceptor acceptor_type;
typedef ba::ip::tcp::socket socket_type;
const short PORT = 11235;
class Server;
// A connection has its own io_service and socket
class Connection {
protected:
ba::io_service service;
socket_type sock;
b::thread *thread;
ba::streambuf stream_buffer; // for reading etc
Server *server;
void AsyncReadString() {
ba::async_read_until(
sock,
stream_buffer,
'\0', // null-char is a delimiter
b::bind(&Connection::ReadHandler, this,
ba::placeholders::error,
ba::placeholders::bytes_transferred));
}
void AsyncWriteString(const std::string &s) {
std::string newstr = s + '\0'; // add a null char
ba::async_write(
sock,
ba::buffer(newstr.c_str(), newstr.size()),
b::bind(&Connection::WriteHandler, this,
ba::placeholders::error,
ba::placeholders::bytes_transferred));
}
virtual void Session() {
AsyncReadString();
service.run(); // run at last
}
std::string ExtractString() {
std::istream is(&stream_buffer);
std::string s;
std::getline(is, s, '\0');
return s;
}
virtual void ReadHandler(
const bs::error_code &ec,
std::size_t bytes_transferred) {
if (!ec) {
std::cout << (ExtractString() + "\n");
std::cout.flush();
AsyncReadString(); // read again
}
else {
// do nothing, "this" will be deleted later
}
}
virtual void WriteHandler(
const bs::error_code &ec,
std::size_t bytes_transferred) {
}
public:
Connection(Server *s) :
service(),
sock(service),
server(s),
thread(NULL)
{ }
socket_type& Socket() {
return sock;
}
void Start() {
if (thread) delete thread;
thread = new b::thread(
b::bind(&Connection::Session, this));
}
void Join() {
if (thread) thread->join();
}
void Stop() {
service.stop();
}
void KillMe();
virtual ~Connection() {
}
};
// a server also has its own io_service but it's only used for accepting
class Server {
public:
std::list<Connection*> Connections;
protected:
ba::io_service service;
acceptor_type acc;
b::thread *thread;
virtual void AcceptHandler(const bs::error_code &ec) {
if (!ec) {
Connections.back()->Start();
Connections.push_back(new Connection(this));
acc.async_accept(
Connections.back()->Socket(),
b::bind(&Server::AcceptHandler,
this,
ba::placeholders::error));
}
else {
// do nothing
// since the new session will be deleted
// automatically by the destructor
}
}
virtual void ThreadFunc() {
Connections.push_back(new Connection(this));
acc.async_accept(
Connections.back()->Socket(),
b::bind(&Server::AcceptHandler,
this,
ba::placeholders::error));
service.run();
}
public:
Server():
service(),
acc(service, ba::ip::tcp::endpoint(ba::ip::tcp::v4(), PORT)),
thread(NULL)
{ }
void Start() {
if (thread) delete thread;
thread = new b::thread(
b::bind(&Server::ThreadFunc, this));
}
void Stop() {
service.stop();
}
void Join() {
if (thread) thread->join();
}
void StopAllConnections() {
for (auto c : Connections) {
c->Stop();
}
}
void JoinAllConnections() {
for (auto c : Connections) {
c->Join();
}
}
void KillAllConnections() {
for (auto c : Connections) {
delete c;
}
Connections.clear();
}
void KillConnection(Connection *c) {
Connections.remove(c);
delete c;
}
virtual ~Server() {
delete thread;
// connection should be deleted by the user (?)
}
};
void Connection::KillMe() {
server->KillConnection(this);
}
int main() {
try {
Server s;
s.Start();
std::cin.get(); // wait for enter
s.Stop(); // stop listening first
s.StopAllConnections(); // interrupt ongoing connections
s.Join(); // wait for server, should return immediately
s.JoinAllConnections(); // wait for ongoing connections
s.KillAllConnections(); // destroy connection objects
// at the end of scope, Server will be destroyed
}
catch (std::exception &e) {
std::cerr << "Exception: " << e.what() << std::endl;
return 1;
}
return 0;
}
No. Using an io_service object per connection is definitely a smell. Especially since you're also running each connection on a dedicated thread.
At this point you have to ask yourself what did asynchrony buy you? You can have all the code synchronous and have exactly the same number of threads etc.
Clearly you want to multiplex the connections onto a far smaller number of services. In practice there are a few sensible models like
a single io_service with a single service thread (this is usually good). No tasks queued on the service may ever block for significant time or the latency will suffer
a single io_service with a number of threads executing handlers. The number of threads in the pool should be enough to service the max. number of simultaneous CPU intensive tasks supported (or again, the latency will start to go up)
an io_service per thread, usually one thread per logical core and with thread affinity so that it "sticks" to that core. This can be ideal for cache locality
UPDATE: Demo
Here's a demo that shows the idiomatic style using option 1. from above:
Live On Coliru
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <boost/thread.hpp>
#include <iostream>
#include <istream>
#include <list>
#include <string>
namespace ba = boost::asio;
namespace bs = boost::system;
namespace b = boost;
typedef ba::ip::tcp::acceptor acceptor_type;
typedef ba::ip::tcp::socket socket_type;
const short PORT = 11235;
// A connection has its own io_service and socket
class Connection : public b::enable_shared_from_this<Connection>
{
public:
typedef boost::shared_ptr<Connection> Ptr;
protected:
socket_type sock;
ba::streambuf stream_buffer; // for reading etc
std::string message;
void AsyncReadString() {
std::cout << __PRETTY_FUNCTION__ << "\n";
ba::async_read_until(
sock,
stream_buffer,
'\0', // null-char is a delimiter
b::bind(&Connection::ReadHandler, shared_from_this(),
ba::placeholders::error,
ba::placeholders::bytes_transferred));
}
void AsyncWriteString(const std::string &s) {
std::cout << __PRETTY_FUNCTION__ << "\n";
message = s;
ba::async_write(
sock,
ba::buffer(message.c_str(), message.size()+1),
b::bind(&Connection::WriteHandler, shared_from_this(),
ba::placeholders::error,
ba::placeholders::bytes_transferred));
}
std::string ExtractString() {
std::cout << __PRETTY_FUNCTION__ << "\n";
std::istream is(&stream_buffer);
std::string s;
std::getline(is, s, '\0');
return s;
}
void ReadHandler(
const bs::error_code &ec,
std::size_t bytes_transferred)
{
std::cout << __PRETTY_FUNCTION__ << "\n";
if (!ec) {
std::cout << (ExtractString() + "\n");
std::cout.flush();
AsyncReadString(); // read again
}
else {
// do nothing, "this" will be deleted later
}
}
void WriteHandler(const bs::error_code &ec, std::size_t bytes_transferred) {
std::cout << __PRETTY_FUNCTION__ << "\n";
}
public:
Connection(ba::io_service& svc) : sock(svc) { }
virtual ~Connection() {
std::cout << __PRETTY_FUNCTION__ << "\n";
}
socket_type& Socket() { return sock; }
void Session() { AsyncReadString(); }
void Stop() { sock.cancel(); }
};
// a server also has its own io_service but it's only used for accepting
class Server {
public:
std::list<boost::weak_ptr<Connection> > m_connections;
protected:
ba::io_service _service;
boost::optional<ba::io_service::work> _work;
acceptor_type _acc;
b::thread thread;
void AcceptHandler(const bs::error_code &ec, Connection::Ptr accepted) {
if (!ec) {
accepted->Session();
DoAccept();
}
else {
// do nothing the new session will be deleted automatically by the
// destructor
}
}
void DoAccept() {
auto newaccept = boost::make_shared<Connection>(_service);
_acc.async_accept(
newaccept->Socket(),
b::bind(&Server::AcceptHandler,
this,
ba::placeholders::error,
newaccept
));
}
public:
Server():
_service(),
_work(ba::io_service::work(_service)),
_acc(_service, ba::ip::tcp::endpoint(ba::ip::tcp::v4(), PORT)),
thread(b::bind(&ba::io_service::run, &_service))
{ }
~Server() {
std::cout << __PRETTY_FUNCTION__ << "\n";
Stop();
_work.reset();
if (thread.joinable()) thread.join();
}
void Start() {
std::cout << __PRETTY_FUNCTION__ << "\n";
DoAccept();
}
void Stop() {
std::cout << __PRETTY_FUNCTION__ << "\n";
_acc.cancel();
}
void StopAllConnections() {
std::cout << __PRETTY_FUNCTION__ << "\n";
for (auto c : m_connections) {
if (auto p = c.lock())
p->Stop();
}
}
};
int main() {
try {
Server s;
s.Start();
std::cerr << "Shutdown in 2 seconds...\n";
b::this_thread::sleep_for(b::chrono::seconds(2));
std::cerr << "Stop accepting...\n";
s.Stop();
std::cerr << "Shutdown...\n";
s.StopAllConnections(); // interrupt ongoing connections
} // destructor of Server will join the service thread
catch (std::exception &e) {
std::cerr << __FUNCTION__ << ":" << __LINE__ << "\n";
std::cerr << "Exception: " << e.what() << std::endl;
return 1;
}
std::cerr << "Byebye\n";
}
I modified the main() to run for 2 seconds without user intervention. This is so I can demo it Live On Coliru (of course, it's limited w.r.t the number of client processes).
If you run it with a lot (a lot) of clients, using e.g.
$ time (for a in {1..1000}; do (sleep 1.$RANDOM; echo -e "hello world $RANDOM\\0" | netcat localhost 11235)& done; wait)
You will find that the two second window handles them all:
$ ./test | sort | uniq -c | sort -n | tail
Shutdown in 2 seconds...
Shutdown...
Byebye
2 hello world 28214
2 hello world 4554
2 hello world 6216
2 hello world 7864
2 hello world 9966
2 void Server::Stop()
1000 std::string Connection::ExtractString()
1001 virtual Connection::~Connection()
2000 void Connection::AsyncReadString()
2000 void Connection::ReadHandler(const boost::system::error_code&, std::size_t)
If you really go berserk and raise 1000 to e.g. 100000 there, you'll get things similar to:
sehe#desktop:/tmp$ ./test | sort | uniq -c | sort -n | tail
Shutdown in 2 seconds...
Shutdown...
Byebye
2 hello world 5483
2 hello world 579
2 hello world 5865
2 hello world 938
2 void Server::Stop()
3 hello world 9613
1741 std::string Connection::ExtractString()
1742 virtual Connection::~Connection()
3482 void Connection::AsyncReadString()
3482 void Connection::ReadHandler(const boost::system::error_code&, std::size_t)
On repeated 2-second runs of the server.
This is my current code for the server. I connect to the server using telnet.
#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
class Connection
{
public:
Connection(boost::asio::io_service& io_service) : socket_(io_service)
{
}
void start()
{
AsyncRead();
}
tcp::socket& socket()
{
return socket_;
}
private:
void AsyncRead()
{
boost::asio::async_read(socket_, boost::asio::buffer(data_, max_length),
[this](boost::system::error_code ec, std::size_t length)
{
if (!ec)
{
std::cout << data_ << std::endl;
}
});
}
tcp::socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class server
{
public:
server(boost::asio::io_service& io_service, short port)
: io_service_(io_service),
acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
{
start_accept();
}
private:
void start_accept()
{
Connection* connection = new Connection(io_service_);
acceptor_.async_accept(connection->socket(), [this, connection](boost::system::error_code ec)
{
//std::cout << ec.message() << std::endl;
if(!ec)
{
std::cout << "Connected." << std::endl;
connection->start();
}
else
{
delete connection;
}
start_accept();
});
}
boost::asio::io_service& io_service_;
tcp::acceptor acceptor_;
};
int main(int argc, char* argv[])
{
try
{
boost::asio::io_service io_service;
server s(io_service, 7899);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
Instead of async_read I use async_read_some I can get the first message sent from telnet and thats it.
Any suggestions on what I am doing wrong ?
Thanks.
async_read will read the number of bytes specified in the length parameter. You aren't seeing the first message because async_read is still waiting to read max_length bytes. This question discusses similar behaviour