// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of the Andrey N. Sabelnikov nor the // names of its contributors may be used to endorse or promote products // derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // #pragma once //#include <Winsock2.h> //#include <Ws2tcpip.h> #include <boost/lexical_cast.hpp> #include <iostream> #include <istream> #include <ostream> #include <string> #include <boost/asio.hpp> #include <boost/preprocessor/selection/min.hpp> #include <boost/lambda/bind.hpp> #include <boost/lambda/lambda.hpp> #include <boost/interprocess/detail/atomic.hpp> #include "net/net_utils_base.h" #include "misc_language.h" //#include "profile_tools.h" #include "../string_tools.h" #ifndef MAKE_IP #define MAKE_IP( a1, a2, a3, a4 ) (a1|(a2<<8)|(a3<<16)|(a4<<24)) #endif namespace epee { namespace net_utils { class blocked_mode_client { struct handler_obj { handler_obj(boost::system::error_code& error, size_t& bytes_transferred):ref_error(error), ref_bytes_transferred(bytes_transferred) {} handler_obj(const handler_obj& other_obj):ref_error(other_obj.ref_error), ref_bytes_transferred(other_obj.ref_bytes_transferred) {} boost::system::error_code& ref_error; size_t& ref_bytes_transferred; void operator()(const boost::system::error_code& error, // Result of operation. std::size_t bytes_transferred // Number of bytes read. ) { ref_error = error; ref_bytes_transferred = bytes_transferred; } }; public: inline blocked_mode_client():m_socket(m_io_service), m_initialized(false), m_connected(false), m_deadline(m_io_service), m_shutdowned(0) { m_initialized = true; // No deadline is required until the first socket operation is started. We // set the deadline to positive infinity so that the actor takes no action // until a specific deadline is set. m_deadline.expires_at(boost::posix_time::pos_infin); // Start the persistent actor that checks for deadline expiry. check_deadline(); } inline ~blocked_mode_client() { //profile_tools::local_coast lc("~blocked_mode_client()", 3); shutdown(); } inline void set_recv_timeout(int reciev_timeout) { m_reciev_timeout = reciev_timeout; } inline bool connect(const std::string& addr, int port, unsigned int connect_timeout, unsigned int reciev_timeout, const std::string& bind_ip = "0.0.0.0") { return connect(addr, std::to_string(port), connect_timeout, reciev_timeout, bind_ip); } inline bool connect(const std::string& addr, const std::string& port, unsigned int connect_timeout, unsigned int reciev_timeout, const std::string& bind_ip = "0.0.0.0") { m_connect_timeout = connect_timeout; m_reciev_timeout = reciev_timeout; m_connected = false; if(!m_reciev_timeout) m_reciev_timeout = m_connect_timeout; try { m_socket.close(); // Get a list of endpoints corresponding to the server name. ////////////////////////////////////////////////////////////////////////// boost::asio::ip::tcp::resolver resolver(m_io_service); boost::asio::ip::tcp::resolver::query query(boost::asio::ip::tcp::v4(), addr, port); boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query); boost::asio::ip::tcp::resolver::iterator end; if(iterator == end) { LOG_ERROR("Failed to resolve " << addr); return false; } ////////////////////////////////////////////////////////////////////////// //boost::asio::ip::tcp::endpoint remote_endpoint(boost::asio::ip::address::from_string(addr.c_str()), port); boost::asio::ip::tcp::endpoint remote_endpoint(*iterator); m_socket.open(remote_endpoint.protocol()); if(bind_ip != "0.0.0.0" && bind_ip != "0" && bind_ip != "" ) { boost::asio::ip::tcp::endpoint local_endpoint(boost::asio::ip::address::from_string(addr.c_str()), 0); m_socket.bind(local_endpoint); } m_deadline.expires_from_now(boost::posix_time::milliseconds(m_connect_timeout)); boost::system::error_code ec = boost::asio::error::would_block; //m_socket.connect(remote_endpoint); m_socket.async_connect(remote_endpoint, boost::lambda::var(ec) = boost::lambda::_1); while (ec == boost::asio::error::would_block) { m_io_service.run_one(); } if (!ec && m_socket.is_open()) { m_connected = true; m_deadline.expires_at(boost::posix_time::pos_infin); return true; }else { LOG_PRINT("Some problems at connect, message: " << ec.message(), LOG_LEVEL_3); return false; } } catch(const boost::system::system_error& er) { LOG_PRINT("Some problems at connect, message: " << er.what(), LOG_LEVEL_4); return false; } catch(...) { LOG_PRINT("Some fatal problems.", LOG_LEVEL_4); return false; } return true; } inline bool disconnect() { try { if(m_connected) { m_connected = false; m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both); } } catch(const boost::system::system_error& /*er*/) { //LOG_ERROR("Some problems at disconnect, message: " << er.what()); return false; } catch(...) { //LOG_ERROR("Some fatal problems."); return false; } return true; } inline bool send(const std::string& buff) { try { m_deadline.expires_from_now(boost::posix_time::milliseconds(m_reciev_timeout)); // Set up the variable that receives the result of the asynchronous // operation. The error code is set to would_block to signal that the // operation is incomplete. Asio guarantees that its asynchronous // operations will never fail with would_block, so any other value in // ec indicates completion. boost::system::error_code ec = boost::asio::error::would_block; // Start the asynchronous operation itself. The boost::lambda function // object is used as a callback and will update the ec variable when the // operation completes. The blocking_udp_client.cpp example shows how you // can use boost::bind rather than boost::lambda. boost::asio::async_write(m_socket, boost::asio::buffer(buff), boost::lambda::var(ec) = boost::lambda::_1); // Block until the asynchronous operation has completed. while (ec == boost::asio::error::would_block) { m_io_service.run_one(); } if (ec) { LOG_PRINT_L3("Problems at write: " << ec.message()); m_connected = false; return false; }else { m_deadline.expires_at(boost::posix_time::pos_infin); } } catch(const boost::system::system_error& er) { LOG_ERROR("Some problems at connect, message: " << er.what()); return false; } catch(...) { LOG_ERROR("Some fatal problems."); return false; } return true; } inline bool send(const void* data, size_t sz) { try { /* m_deadline.expires_from_now(boost::posix_time::milliseconds(m_reciev_timeout)); // Set up the variable that receives the result of the asynchronous // operation. The error code is set to would_block to signal that the // operation is incomplete. Asio guarantees that its asynchronous // operations will never fail with would_block, so any other value in // ec indicates completion. boost::system::error_code ec = boost::asio::error::would_block; // Start the asynchronous operation itself. The boost::lambda function // object is used as a callback and will update the ec variable when the // operation completes. The blocking_udp_client.cpp example shows how you // can use boost::bind rather than boost::lambda. boost::asio::async_write(m_socket, boost::asio::buffer(data, sz), boost::lambda::var(ec) = boost::lambda::_1); // Block until the asynchronous operation has completed. while (ec == boost::asio::error::would_block) { m_io_service.run_one(); } */ boost::system::error_code ec; size_t writen = m_socket.write_some(boost::asio::buffer(data, sz), ec); if (!writen || ec) { LOG_PRINT_L3("Problems at write: " << ec.message()); m_connected = false; return false; }else { m_deadline.expires_at(boost::posix_time::pos_infin); } } catch(const boost::system::system_error& er) { LOG_ERROR("Some problems at send, message: " << er.what()); m_connected = false; return false; } catch(...) { LOG_ERROR("Some fatal problems."); return false; } return true; } bool is_connected() { return m_connected && m_socket.is_open(); //TRY_ENTRY() //return m_socket.is_open(); //CATCH_ENTRY_L0("is_connected", false) } inline bool recv(std::string& buff) { try { // Set a deadline for the asynchronous operation. Since this function uses // a composed operation (async_read_until), the deadline applies to the // entire operation, rather than individual reads from the socket. m_deadline.expires_from_now(boost::posix_time::milliseconds(m_reciev_timeout)); // Set up the variable that receives the result of the asynchronous // operation. The error code is set to would_block to signal that the // operation is incomplete. Asio guarantees that its asynchronous // operations will never fail with would_block, so any other value in // ec indicates completion. //boost::system::error_code ec = boost::asio::error::would_block; // Start the asynchronous operation itself. The boost::lambda function // object is used as a callback and will update the ec variable when the // operation completes. The blocking_udp_client.cpp example shows how you // can use boost::bind rather than boost::lambda. boost::system::error_code ec = boost::asio::error::would_block; size_t bytes_transfered = 0; handler_obj hndlr(ec, bytes_transfered); char local_buff[10000] = {0}; //m_socket.async_read_some(boost::asio::buffer(local_buff, sizeof(local_buff)), hndlr); boost::asio::async_read(m_socket, boost::asio::buffer(local_buff, sizeof(local_buff)), boost::asio::transfer_at_least(1), hndlr); // Block until the asynchronous operation has completed. while (ec == boost::asio::error::would_block && !boost::interprocess::ipcdetail::atomic_read32(&m_shutdowned)) { m_io_service.run_one(); } if (ec) { LOG_PRINT_L4("READ ENDS: Connection err_code " << ec.value()); if(ec == boost::asio::error::eof) { LOG_PRINT_L4("Connection err_code eof."); //connection closed there, empty return true; } LOG_PRINT_L3("Problems at read: " << ec.message()); m_connected = false; return false; }else { LOG_PRINT_L4("READ ENDS: Success. bytes_tr: " << bytes_transfered); m_deadline.expires_at(boost::posix_time::pos_infin); } /*if(!bytes_transfered) return false;*/ buff.assign(local_buff, bytes_transfered); return true; } catch(const boost::system::system_error& er) { LOG_ERROR("Some problems at read, message: " << er.what()); m_connected = false; return false; } catch(...) { LOG_ERROR("Some fatal problems at read."); return false; } return false; } inline bool recv_n(std::string& buff, int64_t sz) { try { // Set a deadline for the asynchronous operation. Since this function uses // a composed operation (async_read_until), the deadline applies to the // entire operation, rather than individual reads from the socket. m_deadline.expires_from_now(boost::posix_time::milliseconds(m_reciev_timeout)); // Set up the variable that receives the result of the asynchronous // operation. The error code is set to would_block to signal that the // operation is incomplete. Asio guarantees that its asynchronous // operations will never fail with would_block, so any other value in // ec indicates completion. //boost::system::error_code ec = boost::asio::error::would_block; // Start the asynchronous operation itself. The boost::lambda function // object is used as a callback and will update the ec variable when the // operation completes. The blocking_udp_client.cpp example shows how you // can use boost::bind rather than boost::lambda. buff.resize(static_cast<size_t>(sz)); boost::system::error_code ec = boost::asio::error::would_block; size_t bytes_transfered = 0; handler_obj hndlr(ec, bytes_transfered); //char local_buff[10000] = {0}; boost::asio::async_read(m_socket, boost::asio::buffer((char*)buff.data(), buff.size()), boost::asio::transfer_at_least(buff.size()), hndlr); // Block until the asynchronous operation has completed. while (ec == boost::asio::error::would_block && !boost::interprocess::ipcdetail::atomic_read32(&m_shutdowned)) { m_io_service.run_one(); } if (ec) { LOG_PRINT_L3("Problems at read: " << ec.message()); m_connected = false; return false; }else { m_deadline.expires_at(boost::posix_time::pos_infin); } if(bytes_transfered != buff.size()) { LOG_ERROR("Transferred missmatch with transfer_at_least value: m_bytes_transferred=" << bytes_transfered << " at_least value=" << buff.size()); return false; } return true; } catch(const boost::system::system_error& er) { LOG_ERROR("Some problems at read, message: " << er.what()); m_connected = false; return false; } catch(...) { LOG_ERROR("Some fatal problems at read."); return false; } return false; } bool shutdown() { m_deadline.cancel(); boost::system::error_code ignored_ec; m_socket.cancel(ignored_ec); m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignored_ec); m_socket.close(ignored_ec); boost::interprocess::ipcdetail::atomic_write32(&m_shutdowned, 1); m_connected = false; return true; } void set_connected(bool connected) { m_connected = connected; } boost::asio::io_service& get_io_service() { return m_io_service; } boost::asio::ip::tcp::socket& get_socket() { return m_socket; } private: void check_deadline() { // Check whether the deadline has passed. We compare the deadline against // the current time since a new asynchronous operation may have moved the // deadline before this actor had a chance to run. if (m_deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now()) { // The deadline has passed. The socket is closed so that any outstanding // asynchronous operations are cancelled. This allows the blocked // connect(), read_line() or write_line() functions to return. LOG_PRINT_L3("Timed out socket"); m_connected = false; m_socket.close(); // There is no longer an active deadline. The expiry is set to positive // infinity so that the actor takes no action until a new deadline is set. m_deadline.expires_at(boost::posix_time::pos_infin); } // Put the actor back to sleep. m_deadline.async_wait(boost::bind(&blocked_mode_client::check_deadline, this)); } protected: boost::asio::io_service m_io_service; boost::asio::ip::tcp::socket m_socket; int m_connect_timeout; int m_reciev_timeout; bool m_initialized; bool m_connected; boost::asio::deadline_timer m_deadline; volatile uint32_t m_shutdowned; }; /************************************************************************/ /* */ /************************************************************************/ class async_blocked_mode_client: public blocked_mode_client { public: async_blocked_mode_client():m_send_deadline(blocked_mode_client::m_io_service) { // No deadline is required until the first socket operation is started. We // set the deadline to positive infinity so that the actor takes no action // until a specific deadline is set. m_send_deadline.expires_at(boost::posix_time::pos_infin); // Start the persistent actor that checks for deadline expiry. check_send_deadline(); } ~async_blocked_mode_client() { m_send_deadline.cancel(); } bool shutdown() { blocked_mode_client::shutdown(); m_send_deadline.cancel(); return true; } inline bool send(const void* data, size_t sz) { try { /* m_send_deadline.expires_from_now(boost::posix_time::milliseconds(m_reciev_timeout)); // Set up the variable that receives the result of the asynchronous // operation. The error code is set to would_block to signal that the // operation is incomplete. Asio guarantees that its asynchronous // operations will never fail with would_block, so any other value in // ec indicates completion. boost::system::error_code ec = boost::asio::error::would_block; // Start the asynchronous operation itself. The boost::lambda function // object is used as a callback and will update the ec variable when the // operation completes. The blocking_udp_client.cpp example shows how you // can use boost::bind rather than boost::lambda. boost::asio::async_write(m_socket, boost::asio::buffer(data, sz), boost::lambda::var(ec) = boost::lambda::_1); // Block until the asynchronous operation has completed. while(ec == boost::asio::error::would_block) { m_io_service.run_one(); }*/ boost::system::error_code ec; size_t writen = m_socket.write_some(boost::asio::buffer(data, sz), ec); if (!writen || ec) { LOG_PRINT_L3("Problems at write: " << ec.message()); return false; }else { m_send_deadline.expires_at(boost::posix_time::pos_infin); } } catch(const boost::system::system_error& er) { LOG_ERROR("Some problems at connect, message: " << er.what()); return false; } catch(...) { LOG_ERROR("Some fatal problems."); return false; } return true; } private: boost::asio::deadline_timer m_send_deadline; void check_send_deadline() { // Check whether the deadline has passed. We compare the deadline against // the current time since a new asynchronous operation may have moved the // deadline before this actor had a chance to run. if (m_send_deadline.expires_at() <= boost::asio::deadline_timer::traits_type::now()) { // The deadline has passed. The socket is closed so that any outstanding // asynchronous operations are cancelled. This allows the blocked // connect(), read_line() or write_line() functions to return. LOG_PRINT_L3("Timed out socket"); m_socket.close(); // There is no longer an active deadline. The expiry is set to positive // infinity so that the actor takes no action until a new deadline is set. m_send_deadline.expires_at(boost::posix_time::pos_infin); } // Put the actor back to sleep. m_send_deadline.async_wait(boost::bind(&async_blocked_mode_client::check_send_deadline, this)); } }; } }