Archive / / / network.cpp
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)); } }