2007-09-22 17:00:24 UTC
previous
next
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/format.hpp>
#include <exception>
#include <iostream>
#include <algorithm>
#include <functional>
#include "network.h"
#include "log.h"
using boost::asio::ip::tcp;
namespace Network
{
boost::scoped_ptr<Manager> Manager::_instance;
Manager& manager()
{
// TODO: End the program if this check fails
// if (Manager::_instance)
return *Manager::_instance;
// else
}
Manager::Manager()
{
log::debug("Starting network manager thread");
_thread.reset(new boost::thread(boost::bind(&Manager::start, this)));
}
Manager::~Manager()
{
log::debug("Stopping network manager thread");
_io_service_work.reset();
// Thread may not have been created correctly, so check first
if (_thread)
_thread->join();
}
void Manager::start()
{
log::debug("NetworkManager: Started network manager thread");
start_listener_int();
_io_service_work.reset(new boost::asio::io_service::work(_io_service));
_io_service.run();
}
void Manager::start_listener()
{
_io_service.post(boost::bind(&Manager::start_listener_int, this));
}
void Manager::start_listener_int()
{
log::debug("NetworkManager: Starting a new listener thread");
_connections.push_back(boost::shared_ptr<Connection>(new Connection(*this)));
}
void Manager::end_connection(
boost::shared_ptr<Connection> connection)
{
_io_service.post(boost::bind(&Manager::end_connection_int, this, connection));
}
void Manager::end_connection_int(
boost::shared_ptr<Connection> connection)
{
log::debug("NetworkManager: Ending a connection");
// TODO: I'm pretty sure boost has a better way of doing this
_connections.erase(std::remove_if(_connections.begin(), _connections.end(),
std::bind2nd(std::equal_to<boost::shared_ptr<Connection> >(), connection)), _connections.end());
}
Connection::Connection(Manager& manager)
: _socket(_io_service),
_manager(manager)
{
try
{
_acceptor.reset(new tcp::acceptor(_io_service, tcp::v4()));
_acceptor->set_option(boost::asio::socket_base::reuse_address(true));
_acceptor->bind(tcp::endpoint(tcp::v4(), 7111));
_acceptor->listen();
_thread.reset(new boost::thread(boost::bind(&Connection::start_listening, this)));
}
catch (boost::asio::error& e)
{
log::error(boost::str(boost::format("Could not open socket for the following reason: %1%") % e.what()));
}
}
Connection::~Connection()
{
_io_service_work.reset();
_io_service.interrupt();
// Thread may not have been created correctly, so check first
if (_thread)
_thread->join();
}
void Connection::start_listening()
{
log::debug("NetworkConnection: Started new listener thread");
_acceptor->async_accept(_socket, boost::bind(&Connection::accept_connection, this, _1));
_io_service_work.reset(new boost::asio::io_service::work(_io_service));
_io_service.run();
}
void Connection::accept_connection(const boost::asio::error& error)
{
if (error)
{
log::error(boost::str(boost::format("async_accept() failed for the following reason: %1%") % error.what()));
return;
}
try
{
log::debug("Connection: New connection received, closing acceptor");
_acceptor->close();
_acceptor.reset();
}
catch (boost::asio::error& e)
{
log::error(boost::str(boost::format("Could not close acceptor for the following reason: %1%") % e.what()));
return;
}
log::debug("NetworkConnection: Telling manager to create new listener");
manager().start_listener();
try
{
boost::asio::write(_socket, boost::asio::buffer("nova", 4));
std::size_t bytes_transferred = boost::asio::read(_socket,
boost::asio::buffer(_buffer, 4));
if (bytes_transferred != 4 || _buffer[0] != 'n' || _buffer[1] != 'o'
|| _buffer[2] != 'v' || _buffer[3] != 'a')
{
log::error("Received error while accepting new connection: Bad handshake from client");
_manager.end_connection(shared_from_this());
return;
}
std::string my_version = "nova-server SVN";
_buffer[0] = 0;
_buffer[1] = my_version.length();
boost::asio::write(_socket, boost::asio::buffer(_buffer, 2));
boost::asio::write(_socket, boost::asio::buffer(my_version, my_version.length()));
boost::asio::read(_socket, boost::asio::buffer(_buffer, 2));
unsigned short version_length = _buffer[0];
version_length = (version_length << 8) + _buffer[1];
if (version_length > (_buffer.size() - 3))
{
log::error("Received error while accepting new connection: Client version string is too long");
_manager.end_connection(shared_from_this());
return;
}
boost::asio::read(_socket, boost::asio::buffer(_buffer, version_length));
_buffer[version_length] = 0;
log::info(boost::str(boost::format("Received connection from client '%1%'") % _buffer.c_array()));
_socket.async_receive(boost::asio::buffer(_buffer),
boost::bind(&Connection::receive_data, this, _1, _2));
}
catch (std::exception& e)
{
log::error(boost::str(boost::format("Received error while accepting new connection: %1%") % e.what()));
_manager.end_connection(shared_from_this());
}
}
void Connection::receive_data(
const boost::asio::error& error,
std::size_t bytes_transferred)
{
if (error == boost::asio::error::eof || _buffer[0] == 'x')
{
log::debug("NetworkConnection: Client disconnected");
manager().end_connection(shared_from_this());
return;
}
else if (error)
{
log::error(boost::str(boost::format("async_receive() failed for the following reason: %1%") % error.what()));
return;
}
boost::asio::write(_socket, boost::asio::buffer("\nI agree!\n "),
boost::asio::transfer_all(), boost::asio::ignore_error());
boost::asio::write(_socket, boost::asio::buffer(_buffer, bytes_transferred),
boost::asio::transfer_all(), boost::asio::ignore_error());
boost::asio::write(_socket, boost::asio::buffer("is awesome!\n\n>"),
boost::asio::transfer_all(), boost::asio::ignore_error());
_socket.async_receive(boost::asio::buffer(_buffer),
boost::bind(&Connection::receive_data, this, _1, _2));
}
}