Hi all,
I am trying to figure out a good way to implement a Server class which would not be to hard to reuse and I come up with this:
Server
Code:
template <typename Handler>
class Server
{
public:
//....
private:
//...
Handler & m_handler;
};
There the Handler requires to have two methods:
void init(Server<Handler>&) throw();
void newConnection(boost::asio::ip::tcp::socket*);
But I wonder, is this really a good approach?
If someone didn't exactly understood me so here comes a example:
Server.hpp
Code:
#ifndef H_HEADER_SERVER_H
#define H_HEADER_SERVER_H
#include <boost/thread.hpp>
#include <boost/asio.hpp>
#include <string>
#include <boost/bind.hpp>
#include <boost/unordered_set.hpp> //delete if not used
#include <iostream>
namespace details
{
template <typename T>
class IsRunning
{
private:
T & var;
public:
IsRunning(T & a)
: var(a)
{
var = true;
}
~IsRunning()
{
var = false;
}
};
class ServiceInvoker
{
private:
boost::asio::io_service & service;
public:
ServiceInvoker(boost::asio::io_service &);
void run();
};
ServiceInvoker::ServiceInvoker(boost::asio::io_service & ser)
: service(ser)
{
}
void ServiceInvoker::run()
{
while(true)
{
try
{
service.run();
break;
}
catch(std::exception & exception)
{
std::cerr << "Exception catched:\n" << exception.what() << "\n"; // may add more error handling here later
}
catch(...)
{
std::cerr << "Unkown exception catched\n"; // may add more error handling here later
}
}
}
}
/*
Handler most have the follow methods:
void init(Server<Handler>&) throw();
The server constructor will invoke this method
void newConnection(boost::asio::ip::tcp::socket*);
Every incoming connection will be passed to this method.
The method most then delete the pointer because the Server will not.
*/
template <typename Handler>
class Server
{
public:
Server(Handler &, boost::asio::io_service &);
~Server();
/* set the io_service object */
bool setPort(unsigned short int);
unsigned short int getPort() const;
bool setAddress(const std::string &, const std::string &);
std::string getAddress() const;
bool setPoolsize(unsigned);
unsigned getPoolsize() const;
boost::asio::io_service & getService();
const boost::asio::io_service & getService() const;
boost::asio::ip::tcp::acceptor & getAcceptor();
const boost::asio::ip::tcp::acceptor & getAcceptor() const;
bool isRunning() const;
void stop();
void stopListen();
bool isListen();
void startListen();
void startAccept();
bool isAccept();
void stopAccept();
void secureStop();
void run();
private:
void handle_accept(const boost::system::error_code& e);
std::size_t m_pool_size;
bool m_accept;
bool m_is_running;
boost::asio::ip::tcp::acceptor m_acceptor;
boost::asio::ip::tcp::socket* m_next_socket;
boost::asio::io_service & m_io_service;
Handler & m_handler;
};
template <typename Handler>
Server<Handler>::Server(Handler & handler, boost::asio::io_service & service)
: m_acceptor(service), m_next_socket(new boost::asio::ip::tcp::socket(service)),
m_io_service(service), m_handler(handler), m_accept(false), m_is_running(false),
m_pool_size(boost::thread::hardware_concurrency())
{
m_handler.init(*this);
}
template <typename Handler>
Server<Handler>::~Server()
{
delete m_next_socket;
}
template <typename Handler>
bool Server<Handler>::setPort(unsigned short int p)
{
if(isRunning())
{
return false;
}
m_acceptor.local_endpoint().port(p);
return true;
}
template <typename Handler>
unsigned short int Server<Handler>::getPort() const
{
return m_acceptor.local_endpoint().port();
}
template <typename Handler>
bool Server<Handler>::setAddress(const std::string & address, const std::string &port)
{
if(isRunning())
{
return false;
}
boost::asio::ip::tcp::resolver resolver(m_io_service);
boost::asio::ip::tcp::resolver::query query(address, port);
boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query);
m_acceptor.open(endpoint.protocol());
m_acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); // try without this later
m_acceptor.bind(endpoint);
return true;
}
template <typename Handler>
std::string Server<Handler>::getAddress() const
{
return m_acceptor.local_endpoint().address().to_string();
}
template <typename Handler>
bool Server<Handler>::setPoolsize(unsigned size)
{
if(isRunning())
{
return false;
}
m_pool_size = size;
return true;
}
template <typename Handler>
unsigned Server<Handler>::getPoolsize() const
{
return m_pool_size;
}
template <typename Handler>
boost::asio::io_service & Server<Handler>::getService()
{
return m_io_service;
}
template <typename Handler>
const boost::asio::io_service & Server<Handler>::getService() const
{
return m_io_service;
}
template <typename Handler>
boost::asio::ip::tcp::acceptor & Server<Handler>::getAcceptor()
{
return m_acceptor;
}
template <typename Handler>
const boost::asio::ip::tcp::acceptor & Server<Handler>::getAcceptor() const
{
return m_acceptor;
}
template <typename Handler>
bool Server<Handler>::isRunning() const
{
return m_is_running;
}
template <typename Handler>
void Server<Handler>::stop()
{
m_io_service.stop();
}
template <typename Handler>
void Server<Handler>::stopListen()
{
m_acceptor.close();
}
template <typename Handler>
bool Server<Handler>::isListen()
{
m_acceptor.is_open();
}
template <typename Handler>
void Server<Handler>::startListen()
{
m_acceptor.listen();
}
template <typename Handler>
void Server<Handler>::startAccept()
{
m_accept = true;
m_acceptor.async_accept(*m_next_socket,
boost::bind(&Server::handle_accept, this,
boost::asio::placeholders::error));
}
template <typename Handler>
void Server<Handler>::secureStop()
{
stopListen(); // should maybe be stopAccept(); instead
while(isRunning())
{
boost::thread::yield();
}
}
template <typename Handler>
void Server<Handler>::run()
{
details::IsRunning<bool> isrunning(this->m_is_running);
startListen();
startAccept();
details::ServiceInvoker invoker(this->m_io_service);
std::vector<boost::shared_ptr<boost::thread> > threads;
threads.reserve(m_pool_size-1);
// the serviceinvoker class make sure the io_service will be invoked again if the io_service cast an exception
for(int i = 0; i < m_pool_size-1; i++)
{
threads.push_back(boost::shared_ptr<boost::thread>(new boost::thread(boost::bind(&details::ServiceInvoker::run, &invoker))));
}
invoker.run();
for (std::size_t i = 0; i < threads.size(); ++i)
{
threads[i]->join();
}
return;
}
template <typename Handler>
void Server<Handler>::handle_accept(const boost::system::error_code& error)
{
if(error)
{
return;
}
m_handler.newConnection(m_next_socket);
m_next_socket = new boost::asio::ip::tcp::socket(this->m_acceptor.io_service());
if(m_accept)
{
m_acceptor.async_accept(*m_next_socket,
boost::bind(&Server::handle_accept, this,
boost::asio::placeholders::error));
}
}
#endif
main.cpp
Code:
#include "server.hpp"
#include <iostream>
class ConnectionHandler
{
public:
void init(Server<ConnectionHandler> & server)
{
std::cout << "Initialized" << std::endl;
server.setAddress("localhost","9999");
}
void newConnection(boost::asio::ip::tcp::socket* p)
{
std::cout << "Connection..." << std::endl;
delete p;
}
private:
};
int main(int argc, char* argv[])
{
try
{
const std::string host = "localhost";
const std::string port = "8219";
boost::asio::io_service io_service;
ConnectionHandler handler;
Server<ConnectionHandler> server(handler, io_service);
server.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
std::cin.get();
return 0;
}
Mostly of the code did I wrote when I was tired so there is surely some logical bugs or something, but you get the point.
Thanks in advance!