From bb777dd249f15a7494036b3939e63e3ec3a3601c Mon Sep 17 00:00:00 2001 From: Pauli Nieminen Date: Mon, 16 Jun 2008 21:09:02 +0000 Subject: [PATCH] Comiting scratch implementation for boost::asio based networking --- src/network_boost.cpp | 437 ++++++++++++++++++++++++++++++++++++++++++ src/network_boost.hpp | 216 +++++++++++++++++++++ src/network_new.cpp | 226 ++++++++++++++++++++++ src/network_new.hpp | 150 +++++++++++++++ 4 files changed, 1029 insertions(+) create mode 100644 src/network_boost.cpp create mode 100644 src/network_boost.hpp create mode 100644 src/network_new.cpp create mode 100644 src/network_new.hpp diff --git a/src/network_boost.cpp b/src/network_boost.cpp new file mode 100644 index 00000000000..13b4fb67653 --- /dev/null +++ b/src/network_boost.cpp @@ -0,0 +1,437 @@ +/* $Id$ */ +/* + Copyright (C) 2008 by Pauli Nieminen + Part of the Battle for Wesnoth Project http://www.wesnoth.org/ + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + or at your option any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY. + + See the COPYING file for more details. +*/ + +#include "global.hpp" +#include "config.hpp" +#include "network_boost.hpp" +#include "time.hpp" +#include "filesystem.hpp" +#include "scoped_resource.hpp" + +#include +#include +#include +#include + +#include +#include + +#define DBG_NW LOG_STREAM(debug, network) +#define LOG_NW LOG_STREAM(info, network) +#define WRN_NW LOG_STREAM(warn, network) +#define ERR_NW LOG_STREAM(err, network) + +namespace network_boost { + + + typedef boost::shared_ptr buffer_ptr; + typedef std::deque buffer_queue; + + + class connection::impl : public boost::noncopyable { + connection_id id_; + protected: + manager* manager_; + statistics stats_; + + public: + impl(manager* manager) : manager_(manager) {} + virtual ~impl() + { + } + + virtual void connect(const std::string&, const size_t, mode) = 0; + virtual const connection::statistics get_statistics() const + { + return stats_; + } + + virtual void send_buffer(buffer_ptr) = 0; + virtual void disconnect(bool) = 0; + virtual bool check_connection() = 0; + virtual const std::string& get_ip_address() const = 0; + + connection_id get_id() const + { + return id_; + } + + void set_id(connection_id id) + { + id_ = id; + } + + }; + + class old_data_connection : public connection::impl { + ip_address address_; + size_t port_; + enum connection_state { + RESOLVE_HOST, + CONNECT, + CONNECTED, + OPEN, + DISCONNECT, + LOST + }; + connection_state state_; + time_t last_activity_; + buffer_queue send_queue; + public: + old_data_connection(manager* manager) : connection::impl(manager) + { + } + + virtual ~old_data_connection() + { + } + + virtual void connect(const std::string&, const size_t, connection::mode) + { + } + + virtual void send_buffer(buffer_ptr) + { + } + virtual void disconnect(bool) + { + } + virtual bool check_connection() + { + return false; + } + virtual const ip_address& get_ip_address() const + { + return address_; + } + + }; + + class data_connection : public old_data_connection { + }; + + + // It could be possible to implement administrative + // connection to server that use special client to + // monitore and command server thought tcp connection +// class admin_data_connection : public data_connection { +// }; + + class listen_connection : public connection::impl { + }; + + connection::connection(pimpl implementation) : impl_(implementation) + { + } + + void connection::connect(const std::string& host, const size_t port, mode prefered_mode) + { + assert(impl_); + impl_->connect(host, port, prefered_mode); + } + + void connection::send_buffer(buffer_ptr buffer) + { + assert(impl_); + impl_->send_buffer(buffer); + } + + void connection::disconnect(bool force) + { + assert(impl_); + impl_->disconnect(force); + } + + bool connection::check_connection() + { + assert(impl_); + return impl_->check_connection(); + } + + const ip_address& connection::get_ip_address() const + { + assert(impl_); + return impl_->get_ip_address(); + } + + connection_id connection::get_id() const + { + assert(impl_); + return impl_->get_id(); + } + + void connection::set_id(connection_id id) + { + assert(impl_); + impl_->set_id(id); + } + + const connection::statistics connection::get_statistics() const + { + assert(impl_); + return impl_->get_statistics(); + } + + size_t connection::statistics::get_total_send() const + { + return total_send; + } + + size_t connection::statistics::get_total_recv() const + { + return total_recv; + } + + size_t connection::statistics::get_send() const + { + return send; + } + + size_t connection::statistics::get_recv() const + { + return recv; + } + + size_t connection::statistics::get_send_limit() const + { + return send_limit; + } + + size_t connection::statistics::get_recv_limit() const + { + return recv_limit; + } + + void connection::statistics::send_start(size_t len) + { + send = 0; + send_limit = len; + } + + void connection::statistics::recv_start(size_t len) + { + total_recv += recv; + recv = 0; + recv_limit = len; + } + + void connection::statistics::send_transfered(size_t bytes) + { + total_send += bytes; + send += bytes; + } + + void connection::statistics::recv_transfered(size_t bytes) + { + total_recv += bytes; + recv += bytes; + } + + file_buffer::file_buffer(const std::string filename) : + filestream_(istream_file(filename)) + { + } + + file_buffer::~file_buffer() { + } + + void file_buffer::process_network(connection&) { + } + + void file_buffer::get_config(config& cfg) { + } + + void file_buffer::get_raw_buffer(std::vector& buf) { + } + + config_buffer::config_buffer(const config& cfg) : cfg_(new config(cfg)) + { + } + + config_buffer::~config_buffer() { + delete cfg_; + } + + void config_buffer::process_network(connection&) { + } + + void config_buffer::get_config(config& cfg) { + } + + void config_buffer::get_raw_buffer(std::vector& buf) { + } + + + class connection_array : + public std::vector + { + typedef std::vector connection_ids; + connection_ids free_ids; + public: + size_t insert(const connection_ptr& val) + { + if (!free_ids.empty()) + { + size_t id = free_ids.back(); + free_ids.pop_back(); + operator[](id) = val; + return id; + } + else + push_back(val); + return size() - 1; + } + + void erase(size_t index) + { + if (index == size() - 1) + pop_back(); + else + free_ids.push_back(index); + } + + size_t active_size() const + { + return size() - free_ids.size(); + } + }; + + + class manager::impl : public boost::noncopyable { + connection_array connections_; + connection_array new_connections_; + buffer_queue recv_queue_; + manager* manager_; + + connection_ptr create_connection() + { + connection::pimpl new_conn_details(new old_data_connection(manager_)); + connection_ptr new_conn(new connection(new_conn_details)); + + return new_conn; + } + + public: + impl(manager* manager) : manager_(manager) + { + } + + connection_ptr connect(const std::string& host, const size_t port, connection::mode prefered_mode) + { + connection_ptr new_conn(create_connection()); + + new_conn->set_id(connections_.insert(new_conn)); + + new_conn->connect(host, port, prefered_mode); + + return new_conn;; + } + + void handle_network(size_t) + { + + } + + bool accept(connection_ptr conn) + { + if (new_connections_.empty()) + return false; + + conn = new_connections_.back(); + conn->set_id(connections_.insert(conn)); + new_connections_.pop_back(); + return true; + } + + connection_ptr listen(size_t port) + { + return connection_ptr(); + } + + bool receive_data(connection_ptr con, buffer_ptr buffer) + { + return false; + } + connection_ptr get_connection(connection_id id) const + { + return connections_[id]; + } + struct call_disconnect { + void operator()(connection_ptr& con) + { + con->disconnect(); + } + }; + + void disconnect_all() + { + std::for_each(connections_.begin(), connections_.end(), call_disconnect()); + } + size_t nconnections() const + { + return connections_.active_size(); + } + }; + + manager* manager::manager_ = 0; + + manager::manager() : pimpl(new manager::impl(this)) + { + manager_ = this; + } + + manager::~manager() + { + manager_ = 0; + delete pimpl; + } + + connection_ptr manager::connect(const std::string& host, const size_t port, connection::mode prefered_mode) + { + return pimpl->connect(host, port, prefered_mode); + } + + void manager::handle_network(size_t timeslice) + { + pimpl->handle_network(timeslice); + } + + connection_ptr manager::listen(const size_t port) + { + return pimpl->listen(port); + } + + bool manager::accept(connection_ptr con) + { + return pimpl->accept(con); + } + + bool manager::receive_data(connection_ptr con, buffer_ptr buffer) + { + return pimpl->receive_data(con,buffer); + } + connection_ptr manager::get_connection(connection_id id) const + { + return pimpl->get_connection(id ); + } + + size_t manager::nconnections() const + { + return pimpl->nconnections(); + } + void manager::disconnect_all() + { + pimpl->disconnect_all(); + } +} diff --git a/src/network_boost.hpp b/src/network_boost.hpp new file mode 100644 index 00000000000..5ff521bd747 --- /dev/null +++ b/src/network_boost.hpp @@ -0,0 +1,216 @@ +/* $Id$ */ +/* + Copyright (C) 2008 by Pauli Nieminen + Part of the Battle for Wesnoth Project http://www.wesnoth.org/ + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + or at your option any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY. + + See the COPYING file for more details. +*/ + +#ifndef NETWORK_NEW_HPP_INCLUDED +#define NETWORK_NEW_HPP_INCLUDED + +#include "filesystem.hpp" + +#include +#include +#include +//#include + +class config; + +namespace network_boost { + + class buffer; + typedef boost::shared_ptr buffer_ptr; + class manager; + + typedef size_t connection_id; + typedef std::string ip_address; + + /** + * Holds per connection data + * and implements connection handling + **/ + class connection : public boost::noncopyable { + public: + class impl; + typedef boost::shared_ptr pimpl; + private: + pimpl impl_; + public: + /** + * Used in networking so numbers are freezed + * Only modes that are + **/ + enum mode { + BINARY = 1, + GZIP = 2, + ZLIB = 3, + PLAIN_TEXT = 4 + }; + /** + * Makes it possible to use custom connection + * implementations to make special connections + * like server sockets. + **/ + connection(pimpl implementation); + + /** + * connects to server and tries to use prefered mode + * for compression + **/ + void connect(const std::string& host, const size_t port, mode prefered_mode); + /** + * queues buffer for sending + **/ + void send_buffer(buffer_ptr buffer); + /** + * Flags this connection for disconnection + */ + void disconnect(bool force = false); + /** + * Periodic checks on connection + * It is used to send pings to clients + * @return true if next connection hould be checked too + **/ + bool check_connection(); + + /** + * @return remote hosts ip address + **/ + const ip_address& get_ip_address() const; + + connection_id get_id() const; + void set_id(connection_id); + + class statistics { + size_t total_send; + size_t total_recv; + size_t send; + size_t recv; + size_t send_limit; + size_t recv_limit; + public: + size_t get_total_send() const; + size_t get_total_recv() const; + size_t get_send() const; + size_t get_recv() const; + size_t get_send_limit() const; + size_t get_recv_limit() const; + protected: + void send_start(size_t len); + void recv_start(size_t len); + void send_transfered(size_t bytes); + void recv_transfered(size_t bytes); + friend class connection::impl; + }; + const statistics get_statistics() const; + }; + + typedef boost::shared_ptr connection_ptr; + + class buffer { + public: + void set_mode(connection::mode); + virtual ~buffer() + {} + /** + * Do actual network operations + **/ + virtual void process_network(connection&) = 0; + /** + * Converts data to config + **/ + virtual void get_config(config&) = 0; + /** + * Get raw network data + **/ + virtual void get_raw_buffer(std::vector&) = 0; + }; + + class file_buffer : public buffer { + scoped_istream filestream_; + public: + file_buffer(const std::string filename); + virtual ~file_buffer(); + virtual void process_network(connection&); + /** + * Converts data to config + **/ + virtual void get_config(config&); + /** + * Get raw network data + **/ + virtual void get_raw_buffer(std::vector&); + }; + + class config_buffer : public buffer { + config* cfg_; + public: + config_buffer(const config&); + virtual ~config_buffer(); + virtual void process_network(connection&); + virtual void get_config(config&); + virtual void get_raw_buffer(std::vector&); + }; + + /** + * is public interface to network operations + **/ + class manager : public boost::noncopyable { + class impl; + impl* pimpl; + static manager* manager_; + public: + manager(); + ~manager(); + + static manager* get_manager() + { + assert(manager_); + return manager_; + } + + /** + * Creates a new connection + **/ + connection_ptr connect(const std::string& host, const size_t port, connection::mode prefered_mode); + + /** + * Runs networking code that has to happen in + * main thread; + * for example sending ping to passive connection + **/ + void handle_network(size_t timeslice = 0); + + /** + * creates a server socket that aceppts connection + **/ + connection_ptr listen(const size_t port); + + /** + * Queries allready established connection + * @return true if there was new connection pending + **/ + bool accept(connection_ptr); + + /** + * returns queued network data + **/ + bool receive_data(connection_ptr, buffer_ptr); + + connection_ptr get_connection(connection_id) const; + + size_t nconnections() const; + + void disconnect_all(); + }; + +} +#endif diff --git a/src/network_new.cpp b/src/network_new.cpp new file mode 100644 index 00000000000..557a07aaf05 --- /dev/null +++ b/src/network_new.cpp @@ -0,0 +1,226 @@ +/* $Id$ */ +/* + Copyright (C) 2003 - 2008 by David White + Part of the Battle for Wesnoth Project http://www.wesnoth.org/ + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + or at your option any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY. + + See the COPYING file for more details. +*/ + +//! @file network.cpp +//! Networking + +#include "global.hpp" + +#include "SDL.h" + +#include "serialization/binary_wml.hpp" +#include "config.hpp" +#include "gettext.hpp" +#include "log.hpp" +#include "network_new.hpp" +#include "network_boost.hpp" + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#if defined(_WIN32) || defined(__WIN32__) || defined (WIN32) +#undef INADDR_ANY +#undef INADDR_BROADCAST +#undef INADDR_NONE +#include +#else +#include +#include +#include +#include // for TCP_NODELAY +#ifdef __BEOS__ +#include +#else +#include +#endif +#define SOCKET int +#endif + +#define DBG_NW LOG_STREAM(debug, network) +#define LOG_NW LOG_STREAM(info, network) +#define WRN_NW LOG_STREAM(warn, network) +#define ERR_NW LOG_STREAM(err, network) +// Only warnings and not errors to avoid DoS by log flooding + + + +namespace { + +} // end anon namespace + +namespace network { + +network_boost::connection::statistics get_connection_stats(connection connection_num) +{ + return network_boost::manager::get_manager()->get_connection(connection_num)->get_statistics(); +} + +network_boost::manager* manager::boost_manager_ = 0; + +manager::manager(size_t /*min_threads*/, size_t /*max_threads*/) : free_(true) +{ + // If the network is already being managed + if(!boost_manager_) { + free_ = false; + return; + } + + boost_manager_ = new network_boost::manager(); + +} + +manager::~manager() +{ + if(free_) { + delete boost_manager_; + boost_manager_ = 0; + } +} + +void set_raw_data_only() +{ +} + +network_boost::connection_id server_connection; + +server_manager::server_manager(int port, CREATE_SERVER create_server) : free_(false) +{ + if(create_server != NO_SERVER) { + server_connection = network_boost::manager::get_manager()->listen(port)->get_id(); + free_ = true; + } +} + +server_manager::~server_manager() +{ + stop(); +} + +void server_manager::stop() +{ + if(free_) { + network_boost::manager::get_manager()->get_connection(server_connection)->disconnect(); + free_ = false; + } +} + +bool server_manager::is_running() const +{ + return !free_; +} + +size_t nconnections() +{ + return network_boost::manager::get_manager()->nconnections(); +} + + +connection connect(const std::string& host, int port) +{ + return network_boost::manager::get_manager()->connect(host,port, network_boost::connection::GZIP)->get_id(); +} + +connection connect(const std::string& host, int port, threading::waiter& /*waiter*/) +{ + return connect(host, port); +} + +connection accept_connection() +{ + network_boost::connection_ptr new_con; + if (network_boost::manager::get_manager()->accept(new_con)) + { + return new_con->get_id(); + } + return 0; +} + +bool disconnect(connection s, bool force) +{ + if(s == 0) { + network_boost::manager::get_manager()->disconnect_all(); + } + else + { + network_boost::manager::get_manager()->get_connection(s)->disconnect(force); + } + + return true; +} + +void queue_disconnect(network::connection sock) +{ + network_boost::manager::get_manager()->get_connection(sock)->disconnect(); +} + + +connection receive_data(config& cfg, connection /*connection_num*/, bool*) +{ + network_boost::connection_ptr connection; + network_boost::buffer_ptr buffer; + if (network_boost::manager::get_manager()->receive_data(connection, buffer)) + { + buffer->get_config(cfg); + return connection->get_id(); + } + return 0; +} + +connection receive_data(std::vector& buf) +{ + network_boost::connection_ptr connection; + network_boost::buffer_ptr buffer; + if (network_boost::manager::get_manager()->receive_data(connection, buffer)) + { + buffer->get_raw_buffer(buf); + return connection->get_id(); + } + return 0; +} + +void send_file(const std::string& filename, connection connection_num) +{ + network_boost::buffer_ptr buffer(new network_boost::file_buffer(filename)); + network_boost::manager::get_manager()->get_connection(connection_num)->send_buffer(buffer); +} + +//! @todo Note the gzipped parameter should be removed later, we want to send +//! all data gzipped. This can be done once the campaign server is also updated +//! to work with gzipped data. +void send_data(const config& cfg, connection connection_num, const bool /*gzipped*/) +{ + network_boost::buffer_ptr buffer(new network_boost::config_buffer(cfg)); + network_boost::manager::get_manager()->get_connection(connection_num)->send_buffer(buffer); +} + +void process_send_queue(connection, size_t) +{ + network_boost::manager::get_manager()->handle_network(); +} + +std::string ip_address(connection connection_num) +{ + return network_boost::manager::get_manager()->get_connection(connection_num)->get_ip_address(); +} + +} // end namespace network diff --git a/src/network_new.hpp b/src/network_new.hpp new file mode 100644 index 00000000000..56f19d58213 --- /dev/null +++ b/src/network_new.hpp @@ -0,0 +1,150 @@ +/* $Id$ */ +/* + Copyright (C) 2003 - 2008 by David White + Part of the Battle for Wesnoth Project http://www.wesnoth.org/ + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + or at your option any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY. + + See the COPYING file for more details. +*/ + +//! @file network.hpp +//! + +#ifndef NETWORK_HPP_INCLUDED +#define NETWORK_HPP_INCLUDED + +class config; + +#include "network_boost.hpp" + + +#include +#include + +namespace threading +{ + class waiter; +} + +// This module wraps the network interface. + +namespace network { + +// A network manager must be created before networking can be used. +// It must be destroyed only after all networking activity stops. + +// min_threads is the maximum number we allow to wait, +// if more threads attempt to wait, they will die. +// If min_threads == 0 no thread will ever be destroyed, +// and we will stay at the max number of threads ever needed. + +// max_threads is the overall max number of helper threads. +// If we have that many threads already running, we will never create more. +// If max_threads == 0 we will always create a thread if we need it. +struct manager { + explicit manager(size_t min_threads = 1,size_t max_threads = 0); + ~manager(); + +private: + bool free_; + static network_boost::manager* boost_manager_; + + manager(const manager&); + void operator=(const manager&); +}; + +void set_raw_data_only(); + +//! A server manager causes listening on a given port +//! to occur for the duration of its lifetime. +struct server_manager { + + //! Parameter to pass to the constructor. + enum CREATE_SERVER { MUST_CREATE_SERVER, //!< Will throw exception on failure + TRY_CREATE_SERVER, //!< Will swallow failure + NO_SERVER }; //!< Won't try to create a server at all + + // Throws error. + server_manager(int port, CREATE_SERVER create_server=MUST_CREATE_SERVER); + ~server_manager(); + + bool is_running() const; + void stop(); + +private: + bool free_; +}; + +typedef int connection; + +connection const null_connection = 0; + +//! The number of peers we are connected to. +size_t nconnections(); + +//! Function to attempt to connect to a remote host. +//! Returns the new connection on success, or 0 on failure. +//! Throws error. +connection connect(const std::string& host, int port=15000); + +connection connect(const std::string& host, int port, threading::waiter& waiter); + +//! Function to accept a connection from a remote host. +//! If no host is attempting to connect, it will return 0 immediately. +//! Otherwise returns the new connection. +//! Throws error. +connection accept_connection(); + +//! Function to disconnect from a certain host, +//! or close all connections if connection_num is 0. +//! Returns true if the connection was disconnected. +//! Returns false on failure to disconnect, since the socket is +//! in the middle of sending/receiving data. +//! The socket will be closed when it has finished its send/receive. +bool disconnect(connection connection_num=0, bool force=false); + +//! Function to queue a disconnection. +//! Next time receive_data is called, it will generate an error +//! on the given connection (and presumably then the handling of the error +//! will include closing the connection). +void queue_disconnect(connection connection_num); + +//! Function to receive data from either a certain connection, +//! or all connections if connection_num is 0. +//! Will store the data received in cfg. +//! Times out after timeout milliseconds. +//! Returns the connection that data was received from, +//! or 0 if timeout occurred. +//! Throws error if an error occurred. +connection receive_data(config& cfg, connection, bool* g = 0); +//connection receive_data(std::vector& buf); + +void send_file(const std::string&, connection); + +//! Function to send data down a given connection, +//! or broadcast to all peers if connection_num is 0. +//! Throws error. +void send_data(const config& cfg, connection connection_num /*= 0*/, const bool gzipped); + +void send_raw_data(const char* buf, int len, connection connection_num); + +//! Function to send any data that is in a connection's send_queue, +//! up to a maximum of 'max_size' bytes -- +//! or the entire send queue if 'max_size' bytes is 0. +void process_send_queue(connection connection_num=0, size_t max_size=0); + +//! Function to send data to all peers except 'connection_num'. +//void send_data_all_except(const config& cfg, connection connection_num, const bool gzipped); + +//! Function to get the remote ip address of a socket. +std::string ip_address(connection connection_num); + +} // network namespace + + +#endif