wesnoth/src/network_asio.cpp
2011-09-29 21:37:58 +00:00

204 lines
5.0 KiB
C++

/* $Id$ */
/*
Copyright (C) 2011 by Sergey Popov <loonycyborg@gmail.com>
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 as published by
the Free Software Foundation; either version 2 of the License, 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 <boost/bind.hpp>
#include <boost/ref.hpp>
#include <boost/cstdint.hpp>
#include <boost/version.hpp>
#include "log.hpp"
#include "network_asio.hpp"
#include "serialization/parser.hpp"
static lg::log_domain log_network("network");
#define DBG_NW LOG_STREAM(debug, log_network)
#define LOG_NW LOG_STREAM(info, log_network)
#define WRN_NW LOG_STREAM(warn, log_network)
#define ERR_NW LOG_STREAM(err, log_network)
namespace network_asio {
using boost::system::system_error;
connection::connection(const std::string& host, const std::string& service)
: io_service_()
, resolver_(io_service_)
, socket_(io_service_)
, done_(false)
, write_buf_()
, read_buf_()
, handshake_response_()
, bytes_to_write_(0)
, bytes_written_(0)
, bytes_to_read_(0)
, bytes_read_(0)
{
resolver_.async_resolve(
boost::asio::ip::tcp::resolver::query(host, service),
boost::bind(&connection::handle_resolve, this, _1, _2)
);
LOG_NW << "Resolving hostname: " << host << '\n';
}
void connection::handle_resolve(
const boost::system::error_code& ec,
resolver::iterator iterator
)
{
if(ec)
throw system_error(ec);
connect(iterator);
}
void connection::connect(resolver::iterator iterator)
{
socket_.async_connect(*iterator, boost::bind(
&connection::handle_connect, this, _1, iterator)
);
LOG_NW << "Connecting to " << iterator->endpoint().address() << '\n';
}
void connection::handle_connect(
const boost::system::error_code& ec,
resolver::iterator iterator
)
{
if(ec) {
WRN_NW << "Failed to connect to " <<
iterator->endpoint().address() << ": " <<
ec.message() << '\n';
socket_.close();
if(++iterator == resolver::iterator()) {
ERR_NW << "Tried all IPs. Giving up\n";
throw system_error(ec);
} else
connect(iterator);
} else {
LOG_NW << "Connected to " << iterator->endpoint().address() << '\n';
handshake();
}
}
void connection::handshake()
{
static const boost::uint32_t handshake = 0;
boost::asio::async_write(socket_,
boost::asio::buffer(reinterpret_cast<const char*>(&handshake), 4),
boost::bind(&connection::handle_write, this, _1, _2)
);
boost::asio::async_read(socket_,
boost::asio::buffer(&handshake_response_.binary, 4),
boost::bind(&connection::handle_handshake, this, _1)
);
}
void connection::handle_handshake(
const boost::system::error_code& ec
)
{
if(ec)
throw system_error(ec);
done_ = true;
}
void connection::transfer(const config& request, config& response)
{
io_service_.reset();
done_ = false;
std::ostream os(&write_buf_);
write_gz(os, request);
bytes_to_write_ = write_buf_.size();
bytes_written_ = 0;
std::size_t size = htonl(bytes_to_write_);
boost::asio::write(socket_, boost::asio::buffer(reinterpret_cast<const char*>(&size), 4));
boost::asio::async_write(socket_, write_buf_,
boost::bind(&connection::is_write_complete, this, _1, _2),
boost::bind(&connection::handle_write, this, _1, _2)
);
boost::asio::async_read(socket_, read_buf_,
boost::bind(&connection::is_read_complete, this, _1, _2),
boost::bind(&connection::handle_read, this, _1, _2, boost::ref(response))
);
}
std::size_t connection::is_write_complete(
const boost::system::error_code& ec,
std::size_t bytes_transferred
)
{
if(ec)
throw system_error(ec);
bytes_written_ = bytes_transferred;
#if BOOST_VERSION >= 103700
return bytes_to_write_ - bytes_transferred;
#else
return bytes_to_write_ == bytes_transferred;
#endif
}
void connection::handle_write(
const boost::system::error_code& ec,
std::size_t bytes_transferred
)
{
DBG_NW << "Written " << bytes_transferred << " bytes.\n";
if(ec)
throw system_error(ec);
}
std::size_t connection::is_read_complete(
const boost::system::error_code& ec,
std::size_t bytes_transferred
)
{
if(ec)
throw system_error(ec);
bytes_read_ = bytes_transferred;
if(bytes_transferred < 4) {
return 4;
} else {
if(!bytes_to_read_) {
std::istream is(&read_buf_);
union { char binary[4]; boost::uint32_t num; } data_size;
is.read(data_size.binary, 4);
bytes_to_read_ = ntohl(data_size.num) + 4;
}
#if BOOST_VERSION >= 103700
return bytes_to_read_ - bytes_transferred;
#else
return bytes_to_read_ == bytes_transferred;
#endif
}
}
void connection::handle_read(
const boost::system::error_code& ec,
std::size_t bytes_transferred,
config& response
)
{
DBG_NW << "Read " << bytes_transferred << " bytes.\n";
bytes_to_read_ = 0;
bytes_to_write_ = 0;
done_ = true;
if(ec && ec != boost::asio::error::eof)
throw system_error(ec);
std::istream is(&read_buf_);
read_gz(response, is);
}
}