From fe18c8831167d8be1f9633ff68673fc92f73333c Mon Sep 17 00:00:00 2001 From: Radegast Date: Mon, 3 Sep 2012 00:11:28 +0200 Subject: [PATCH] etlist: multiple packets received until a timeout --- etlist/CMakeLists.txt | 2 +- etlist/connection.cpp | 202 +++++++++++------------------------------- etlist/connection.h | 41 +++++---- etlist/etparser.cpp | 125 ++++++++++++++++++++++++++ etlist/etparser.h | 35 ++++++++ etlist/main.cpp | 41 ++++----- 6 files changed, 256 insertions(+), 190 deletions(-) create mode 100644 etlist/etparser.cpp create mode 100644 etlist/etparser.h diff --git a/etlist/CMakeLists.txt b/etlist/CMakeLists.txt index 14fc82e..3d02160 100644 --- a/etlist/CMakeLists.txt +++ b/etlist/CMakeLists.txt @@ -5,6 +5,6 @@ project(etlist) Find_Package(Boost REQUIRED COMPONENTS system program_options) # Specifying 'asio' doesn't work Find_Package(Threads) # asio dependency -add_executable(etlist connection.cpp main.cpp) +add_executable(etlist main.cpp connection.cpp etparser.cpp) target_link_libraries(etlist ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) \ No newline at end of file diff --git a/etlist/connection.cpp b/etlist/connection.cpp index d8c91b3..f254286 100644 --- a/etlist/connection.cpp +++ b/etlist/connection.cpp @@ -20,175 +20,81 @@ #include "connection.h" -Connection::Connection(std::string server_name, int server_port /*=27960*/, - std::string message /*=getstatus*/) - : socket_(io_service_), resolver_(io_service_), deadline_(io_service_) +Connection::Connection(boost::asio::io_service& io_service, + std::string server_name /*=etlegacy.com*/, + int server_port /*=27960*/, + std::string message /*=getstatus*/, + float timeout /*=1.5*/) + : io_service_(io_service), socket_(io_service, udp::v4()), timer_(io_service) { + udp::resolver resolver(io_service_); udp::resolver::query query(udp::v4(), server_name, boost::lexical_cast(server_port)); - udp::endpoint receiver_endpoint = *resolver_.resolve(query); + receiver_endpoint_ = *resolver.resolve(query); - socket_.open(udp::v4()); - socket_.send_to(boost::asio::buffer(wrap_message(message), 1024), - receiver_endpoint); + socket_.async_send_to(boost::asio::buffer(wrap_message(message)), + receiver_endpoint_, + boost::bind(&Connection::HandleSend, this)); - // Set to positive infinity so that there's no action until a specific - // deadline is set. - deadline_.expires_at(boost::posix_time::pos_infin); - - // Start the persistent actor that checks for deadline expiry. - check_deadline(); + // How long should we wait for the server to respond + timer_.expires_from_now(boost::posix_time::seconds(timeout)); + timer_.async_wait(boost::bind(&Connection::close, this)); } -/* +void Connection::close() +{ + socket_.close(); +} + +/** * @brief Wraps messages into the Quake III protocol format */ std::string Connection::wrap_message(std::string message) { // NOTE: master server doesn't react to a message terminated with 0xfa - return std::string(4, 0xff) + message + std::string(1, 0xfa); + return std::string(4, 0xff) + message; } -std::size_t Connection::ReceiveMessage( - const boost::asio::mutable_buffer& buffer, - boost::posix_time::time_duration timeout, - boost::system::error_code& ec) +std::string Connection::get_response() { - deadline_.expires_from_now(timeout); - ec = boost::asio::error::would_block; - std::size_t length = 0; - - // Start the asynchronous operation itself. The handle_receive function - // used as a callback will update the ec and length variables. - socket_.async_receive(boost::asio::buffer(buffer), - boost::bind(&Connection::handle_receive, _1, _2, - &ec, &length)); - - // Block until the asynchronous operation has completed. - do - io_service_.run_one(); - while (ec == boost::asio::error::would_block); - - return length; + return response_; } -void Connection::handle_receive( - const boost::system::error_code& ec, std::size_t length, - boost::system::error_code *out_ec, std::size_t *out_length) +/** + * @brief Calls itself after every received packet. + * @note It hangs after the last received packet until it is stopped. + */ +void Connection::HandleReceive(const boost::system::error_code& error, + size_t bytes_recvd) { - *out_ec = ec; - *out_length = length; -} - -void Connection::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 (deadline_.expires_at() <= boost::asio::deadline_timer::traits_type::now()) + if (!error && bytes_recvd > 0) { - // The deadline has passed. The outstanding asynchronous operation needs - // to be cancelled so that the blocked receive() function will return. - // - // Please note that cancel() has portability issues on some versions of - // Microsoft Windows, and it may be necessary to use close() instead. - // Consult the documentation for cancel() for further information. - socket_.cancel(); + socket_.async_receive_from( + boost::asio::buffer(data_, max_length), receiver_endpoint_, + boost::bind(&Connection::HandleReceive, this, + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred)); - // 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. - deadline_.expires_at(boost::posix_time::pos_infin); + // std::cout.write(data_, bytes_recvd) << std::endl; + response_ += data_; } - - // Put the actor back to sleep. - deadline_.async_wait(boost::bind(&Connection::check_deadline, this)); + else if (error) + { + std::cout << "Receive error: " << error.message() << std::endl; + } + // io_service will quit when it has no more work to do } -void Connection::ParseMessage(std::string recv_msg) +/** + * @brief Calls HandleReceive for the first packet. + * + * Separate from the HandleReceive method to avoid adding whitespace to response_ + */ +void Connection::HandleSend() { -// recv_msg.erase(recv_msg.find('\0'), recv_msg.npos); - size_t headerEnd = recv_msg.find('\n'); - - // Omit OOB from the packet name - std::cout << "Parsing " << - recv_msg.substr(4, headerEnd - 4) << " packet.... "; - - std::map recv_tokens; - - std::string key, value; - size_t tokenStart = 0; - size_t tokenEnd = 0; - - for (;; ) - { - /* - * Search for a key - */ - tokenStart = recv_msg.find('\\', tokenEnd++); - tokenEnd = recv_msg.find('\\', ++tokenStart); - - // No more keys - if (tokenStart == std::string::npos) - { - break; - } - - // Key without a value - if (tokenEnd == std::string::npos) - { - key = recv_msg.substr(tokenStart, - recv_msg.length() - tokenStart); - recv_tokens[key] = ""; - std::cout << "Warning: adding a key with empty value." << std::endl; - break; - } - - key = recv_msg.substr(tokenStart, tokenEnd - tokenStart); - - /* - * Search for a value - */ - tokenStart = recv_msg.find('\\', tokenEnd++); - tokenEnd = recv_msg.find('\\', ++tokenStart); - - // No more values - if (tokenStart == std::string::npos) - { - break; - } - - // Value is not at the end - if (tokenEnd != std::string::npos) - { - value = recv_msg.substr(tokenStart, tokenEnd - tokenStart); - } - else - { - // Last value - value = recv_msg.substr(tokenStart, recv_msg.length() - tokenStart); - } - - /* - * Store key->value pair in a map - */ - recv_tokens[key] = value; - - // FIXME: This should not happen, but it does. Why? - if (tokenStart >= recv_msg.length() || tokenEnd >= recv_msg.length()) - { - break; - } - } - - /* - * Display key->value pairs - */ - std::cout << recv_tokens.size() << " variables paired" << std::endl << std::endl; - - std::map ::iterator it; - for (it = recv_tokens.begin(); it != recv_tokens.end(); ++it) - { - std::cout << std::setw(22) << it->first << ": " << it->second << - std::endl; - } -} + socket_.async_receive_from( + boost::asio::buffer(data_, max_length), receiver_endpoint_, + boost::bind(&Connection::HandleReceive, this, + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred)); +} \ No newline at end of file diff --git a/etlist/connection.h b/etlist/connection.h index b9a0c8d..7b321fe 100644 --- a/etlist/connection.h +++ b/etlist/connection.h @@ -17,15 +17,15 @@ * You should have received a copy of the GNU General Public License * along with ET: Legacy. If not, see . */ +#ifndef CONNECTION_H +#define CONNECTION_H #include #include -#include -#include // using 'setw' #include #include -#include +#include #include #include @@ -34,24 +34,27 @@ using boost::asio::ip::udp; class Connection { public: - Connection(std::string server_name = "etlegacy.com", + Connection(boost::asio::io_service& io_service, + std::string server_name = "etlegacy.com", int server_port = 27960, - std::string message = "getstatus"); - - std::size_t ReceiveMessage(const boost::asio::mutable_buffer& buffer, - boost::posix_time::time_duration timeout, - boost::system::error_code& ec); - void ParseMessage(std::string recv_msg); + std::string message = "getstatus", + float timeout = 1.5); + void close(); + std::string get_response(); private: - boost::asio::io_service io_service_; - boost::asio::ip::udp::socket socket_; - boost::asio::deadline_timer deadline_; - boost::asio::ip::udp::resolver resolver_; + boost::asio::deadline_timer timer_; + boost::asio::io_service &io_service_; + udp::socket socket_; + udp::endpoint receiver_endpoint_; + + enum { max_length = 2048 }; + char data_[max_length]; + std::string response_; + + void HandleReceive(const boost::system::error_code& error, size_t bytes_recvd); + void HandleSend(); std::string wrap_message(std::string message); - - void check_deadline(); - static void handle_receive( - const boost::system::error_code& ec, std::size_t length, - boost::system::error_code *out_ec, std::size_t *out_length); }; + +#endif // CONNECTION_H diff --git a/etlist/etparser.cpp b/etlist/etparser.cpp new file mode 100644 index 0000000..b7331c4 --- /dev/null +++ b/etlist/etparser.cpp @@ -0,0 +1,125 @@ +/* + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ET: Legacy. If not, see . + */ + +#include +#include +#include +#include // using 'setw' + +#include "etparser.h" + +ETParser::ETParser() +{ + +} + +ETParser::~ETParser() +{ + +} + +void ETParser::ParseMessage(std::string recv_msg) +{ +// recv_msg.erase(recv_msg.find('\0'), recv_msg.npos); + + // Omit OOB from the packet name + size_t headerEnd = recv_msg.find('\n'); + std::cout << "Parsing " << + recv_msg.substr(4, headerEnd - 4) << " packet.... "; + + std::map recv_tokens; + + std::string key, value; + size_t tokenStart = 0; + size_t tokenEnd = 0; + + for (;; ) + { + /* + * Search for a key + */ + tokenStart = recv_msg.find('\\', tokenEnd++); + tokenEnd = recv_msg.find('\\', ++tokenStart); + + // No more keys + if (tokenStart == std::string::npos) + { + break; + } + + // Key without a value + if (tokenEnd == std::string::npos) + { + key = recv_msg.substr(tokenStart, + recv_msg.length() - tokenStart); + recv_tokens[key] = ""; + std::cout << "Warning: adding a key with empty value." << std::endl; + break; + } + + key = recv_msg.substr(tokenStart, tokenEnd - tokenStart); + + /* + * Search for a value + */ + tokenStart = recv_msg.find('\\', tokenEnd++); + tokenEnd = recv_msg.find('\\', ++tokenStart); + + // No more values + if (tokenStart == std::string::npos) + { + break; + } + + // Value is not at the end + if (tokenEnd != std::string::npos) + { + value = recv_msg.substr(tokenStart, tokenEnd - tokenStart); + } + else + { + // Last value + value = recv_msg.substr(tokenStart, recv_msg.length() - tokenStart); + } + + /* + * Store key->value pair in a map + */ + recv_tokens[key] = value; + + // FIXME: This should not happen, but it does. Why? + if (tokenStart >= recv_msg.length() || tokenEnd >= recv_msg.length()) + { + break; + } + } + + /* + * Display key->value pairs + */ + std::cout << recv_tokens.size() << " variables paired" << std::endl << std::endl; + + std::map ::iterator it; + for (it = recv_tokens.begin(); it != recv_tokens.end(); ++it) + { + std::cout << std::setw(22) << it->first << ": " << it->second << + std::endl; + } +} \ No newline at end of file diff --git a/etlist/etparser.h b/etlist/etparser.h new file mode 100644 index 0000000..d07ba4c --- /dev/null +++ b/etlist/etparser.h @@ -0,0 +1,35 @@ +/* + * ET: Legacy + * Copyright (C) 2012 Jan Simek + * + * This file is part of ET: Legacy - http://www.etlegacy.com + * + * ET: Legacy 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 3 of the License, or + * (at your option) any later version. + * + * ET: Legacy is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ET: Legacy. If not, see . + */ +#ifndef ETPARSER_H +#define ETPARSER_H + +class ETParser +{ +public: + ETParser(); + virtual ~ETParser(); + + void SplitIntoParts(std::string msg_to_split); + void ParseMessage(std::string recv_msg); +private: + +}; + +#endif // ETPARSER_H diff --git a/etlist/main.cpp b/etlist/main.cpp index 3aafbc6..b2722dc 100644 --- a/etlist/main.cpp +++ b/etlist/main.cpp @@ -26,6 +26,7 @@ #include #include "connection.h" +#include "etparser.h" using boost::asio::ip::udp; @@ -33,7 +34,7 @@ int main(int argc, char *argv[]) { try { - /** + /* * Program options */ boost::program_options::options_description desc("[OPTIONS]"); @@ -47,6 +48,9 @@ int main(int argc, char *argv[]) ("message,m", boost::program_options::value()->default_value("getstatus"), "message to be sent") + ("timeout,t", + boost::program_options::value()->default_value(1.5), + "seconds to wait for the server to respond") ("raw,r", "don't parse the server response") ; boost::program_options::variables_map var_map; @@ -65,44 +69,37 @@ int main(int argc, char *argv[]) return 1; } - /** + /* * Send the request */ - Connection client(var_map["server"].as(), - var_map["port"].as(), - var_map["message"].as()); + boost::asio::io_service io_service; + Connection client(io_service, var_map["server"].as(), + var_map["port"].as(), + var_map["message"].as(), + var_map["timeout"].as()); + io_service.run(); - char data[1024]; - boost::system::error_code ec; - std::size_t n = client.ReceiveMessage(boost::asio::buffer(data), - boost::posix_time::seconds(10), - ec); + /* + * Parse the response + */ + ETParser parser; - if (ec) + if (var_map.count("raw")) { - std::cout << "Receive error: " << ec.message() << std::endl; + std::cout << client.get_response() << std::endl; } else { - if (var_map.count("raw")) - { - std::cout.write(data, n); - } - else - { - client.ParseMessage(std::string(data, n)); - } + parser.ParseMessage(client.get_response()); } } catch (std::exception& e) { std::cerr << "Exception caught: " << e.what() << std::endl; -// return 1; } catch (...) { std::cerr << "Exception of unknown type!" << std::endl; -// return 1; } return EXIT_SUCCESS;