mirror of
https://github.com/etlegacy/etlegacy-tools.git
synced 2024-11-24 13:01:42 +00:00
etlist: multiple packets received until a timeout
This commit is contained in:
parent
a89d06c8fd
commit
fe18c88311
6 changed files with 256 additions and 190 deletions
|
@ -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})
|
|
@ -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<std::string>(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<std::string, std::string> 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 <std::string, std::string>::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));
|
||||
}
|
|
@ -17,15 +17,15 @@
|
|||
* You should have received a copy of the GNU General Public License
|
||||
* along with ET: Legacy. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef CONNECTION_H
|
||||
#define CONNECTION_H
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <iomanip> // using 'setw'
|
||||
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/deadline_timer.hpp>
|
||||
#include <boost/program_options.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time_types.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
|
@ -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
|
||||
|
|
125
etlist/etparser.cpp
Normal file
125
etlist/etparser.cpp
Normal file
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* ET: Legacy
|
||||
* Copyright (C) 2012 Jan Simek <mail@etlegacy.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <iomanip> // 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<std::string, std::string> 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 <std::string, std::string>::iterator it;
|
||||
for (it = recv_tokens.begin(); it != recv_tokens.end(); ++it)
|
||||
{
|
||||
std::cout << std::setw(22) << it->first << ": " << it->second <<
|
||||
std::endl;
|
||||
}
|
||||
}
|
35
etlist/etparser.h
Normal file
35
etlist/etparser.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* ET: Legacy
|
||||
* Copyright (C) 2012 Jan Simek <mail@etlegacy.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#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
|
|
@ -26,6 +26,7 @@
|
|||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#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<std::string>()->default_value("getstatus"),
|
||||
"message to be sent")
|
||||
("timeout,t",
|
||||
boost::program_options::value<float>()->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<std::string>(),
|
||||
var_map["port"].as<unsigned int>(),
|
||||
var_map["message"].as<std::string>());
|
||||
boost::asio::io_service io_service;
|
||||
Connection client(io_service, var_map["server"].as<std::string>(),
|
||||
var_map["port"].as<unsigned int>(),
|
||||
var_map["message"].as<std::string>(),
|
||||
var_map["timeout"].as<float>());
|
||||
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;
|
||||
|
|
Loading…
Reference in a new issue