diff options
-rw-r--r-- | ChangeLog | 3 | ||||
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/utils/tokencollector.cpp | 126 | ||||
-rw-r--r-- | src/utils/tokencollector.hpp | 344 |
4 files changed, 210 insertions, 265 deletions
@@ -5,6 +5,9 @@ * src/game-server/gamehandler.cpp, src/game-server/gamehandler.hpp, src/game-server/accountconnection.cpp: Handled multiple connections by allowing clients to take over characters. + * src/utils/tokencollector.cpp, src/utils/tokencollector.hpp, + src/Makefile.am: Simplified TokenCollector and fixed a few bugs. Moved + its implementation outside the header file. 2007-08-16 Guillaume Melquiond <guillaume.melquiond@gmail.com> diff --git a/src/Makefile.am b/src/Makefile.am index 6ddc3a11..7d19c95f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -64,6 +64,7 @@ tmwserv_account_SOURCES = \ utils/stringfilter.h \ utils/stringfilter.cpp \ utils/tokencollector.hpp \ + utils/tokencollector.cpp \ utils/tokendispenser.hpp \ utils/tokendispenser.cpp @@ -148,6 +149,7 @@ tmwserv_game_SOURCES = \ utils/timer.h \ utils/timer.cpp \ utils/tokencollector.hpp \ + utils/tokencollector.cpp \ utils/tokendispenser.hpp \ utils/tokendispenser.cpp \ utils/xml.hpp \ diff --git a/src/utils/tokencollector.cpp b/src/utils/tokencollector.cpp new file mode 100644 index 00000000..304caf8e --- /dev/null +++ b/src/utils/tokencollector.cpp @@ -0,0 +1,126 @@ +/* + * The Mana World Server + * Copyright 2007 The Mana World Development Team + * + * This file is part of The Mana World. + * + * The Mana World 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 any later version. + * + * The Mana World 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 The Mana World; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * $Id$ + */ + +#include "utils/tokencollector.hpp" + +/* We are optimistic and we assume that clients connect as soon as possible. + It means that pending data are mainly outdated ones, except for the newer + ones. So we search the lists backward (from newer to older) for already + inserted data. On the contrary, data are removed when there was a network + failure, especially a timeout. So we search the lists forward when removing + data, in order to start from the older ones. */ + +void TokenCollectorBase::insertClient(std::string const &token, intptr_t data) +{ + for (std::list<Item>::reverse_iterator it = mPendingConnects.rbegin(), + it_end = mPendingConnects.rend(); it != it_end; ++it) + { + if (it->token == token) + { + foundMatch(data, it->data); + mPendingConnects.erase(--it.base()); + return; + } + } + + time_t current = time(NULL); + + Item item; + item.token = token; + item.data = data; + item.timeStamp = current; + mPendingClients.push_back(item); + + removeOutdated(current); +} + +void TokenCollectorBase::insertConnect(std::string const &token, intptr_t data) +{ + for (std::list<Item>::reverse_iterator it = mPendingClients.rbegin(), + it_end = mPendingClients.rend(); it != it_end; ++it) + { + if (it->token == token) + { + foundMatch(it->data, data); + mPendingClients.erase(--it.base()); + return; + } + } + + time_t current = time(NULL); + + Item item; + item.token = token; + item.data = data; + item.timeStamp = current; + mPendingConnects.push_back(item); + + removeOutdated(current); +} + +void TokenCollectorBase::removeClient(intptr_t data) +{ + for (std::list<Item>::iterator it = mPendingClients.begin(), + it_end = mPendingClients.end(); it != it_end; ++it) + { + if (it->data == data) + { + mPendingClients.erase(it); + return; + } + } +} + +void TokenCollectorBase::removeOutdated(time_t current) +{ + // Timeout happens after 30 seconds. Much longer may actually pass, though. + time_t threshold = current - 30; + if (threshold < mLastCheck) return; + + std::list<Item>::iterator it; + + it = mPendingConnects.begin(); + while (it != mPendingConnects.end() && it->timeStamp < threshold) + { + removedConnect(it->data); + it = mPendingConnects.erase(it); + } + + it = mPendingClients.begin(); + while (it != mPendingClients.end() && it->timeStamp < threshold) + { + removedClient(it->data); + it = mPendingClients.erase(it); + } + + mLastCheck = current; +} + +TokenCollectorBase::TokenCollectorBase(): + mLastCheck(time(NULL)) +{ +} + +TokenCollectorBase::~TokenCollectorBase() +{ + // Not declared inline, as the list destructors are not trivial. +} diff --git a/src/utils/tokencollector.hpp b/src/utils/tokencollector.hpp index b94643be..a37dfaae 100644 --- a/src/utils/tokencollector.hpp +++ b/src/utils/tokencollector.hpp @@ -27,300 +27,114 @@ #include <list> #include <time.h> -#include "utils/logger.h" - -#define NB_ACTIONS_BETWEEN_OUTDATED_CHECKS 100 - -/** - * Because the timeStamp is not updated for every new list item and the - * removeOutdated function is not called on regular intervals, a list item - * might persist significantly longer then this. - */ -#define TIMEOUT 10 // in seconds - - -/** - * A structure used for the list items, - * prefixed with 'TC_' to avoid any conflicts. - */ -template <typename T> -struct TC_ListItem -{ - /** The magic_token aka passToken */ - std::string token; - - /** The actual data, which will be sent back when matched */ - T payload; - - /** - * The amount of seconds since TokenCollector was running, - * at the last outdated check. - */ - time_t timeStamp; -}; - -// Prototype - /** - * \brief A class for storing and matching magic_tokens - * - * T is used for the class of the owner, U is the client payload, - * V is the connect payload - * - * The owning class must implement as it's member functions; - * - destroyPendingClient(U clientPayload), - * - destroyPendingConnect(V connectPayload) and - * - tokenMatched(U clientPayload, V connectPayload), - * in a way that TokenCollector can reach them - * (public, or declare TokenCollector a friend class). + * Base class containing the generic implementation of TokenCollector. */ -template <class T, class U, class V> -class TokenCollector +class TokenCollectorBase { - public: - /** - * Constructor. - */ - TokenCollector(T * owner); - - /** - * Destructor. - */ - ~TokenCollector(); + struct Item + { + std::string token; /**< Cookie used by the client. */ + intptr_t data; /**< User data. */ + time_t timeStamp; /**< Creation time. */ + }; /** - * \brief Adds a pending client - * - * Searches the pending connects for a match. - * Calls mOwner->tokenMatched when a match is found. - * Inserts in mPendingClients if no match is found. + * List containing client already connected. Newer clients are at the + * back of the list. */ - void addPendingClient(const std::string & token, U clientPayload); + std::list<Item> mPendingClients; /** - * \brief Removes a pending client from the list - * - * Searches the pending client for the payload. + * List containing server data waiting for clients. Newer data are at + * the back of the list. */ - void deletePendingClient(U clientPayload); + std::list<Item> mPendingConnects; /** - * \brief Adds a pending connect - * - * Searches the pending clients for a match. - * Calls mOwner->tokenMatched when a match is found. - * Inserts in mPendingConnects if no match is found. + * Time at which the TokenCollector performed its last check. */ - void addPendingConnect(const std::string & token, V connectPayload); - - private: + time_t mLastCheck; + + protected: + + virtual void removedClient(intptr_t) = 0; + virtual void removedConnect(intptr_t) = 0; + virtual void foundMatch(intptr_t client, intptr_t connect) = 0; + TokenCollectorBase(); + virtual ~TokenCollectorBase(); + void insertClient(std::string const &, intptr_t); + void removeClient(intptr_t); + void insertConnect(std::string const &, intptr_t); + void removeOutdated(time_t); +}; - /** - * \brief A simple list of all pending clients - * - * On a well setup system, this list is mostly empty. - * The servers send the magic_token to the other server, before they - * send it to the client. In a well setup system, the route - * server1 -> server2 is faster then server1 -> client -> server2. - * - * A std::list is used because the basic work cycle for one client - * (search PendingClients, insert PendingConnects, search - * PendingConnects, remove pendingConnect) will run in (allmost) - * constant time, when the size of PendingClients is small. - * This can not be said for a map or sorted vector. - * - * The list items are pointers to void, because a list can not be - * instantiationised with an incomplete type. - */ - std::list< void* > mPendingClients; +/** + * Compile-time check to ensure that Client and ServerData are simple enough + * for TokenCollector. + */ +template< class T > struct _TC_CheckData; +template<> struct _TC_CheckData< int > {}; +template< class T > struct _TC_CheckData< T * > {}; - /** - * \brief A simple list of all pending clients. - * - * See mPendingClients for details. - */ - std::list< void* > mPendingConnects; +/** + * A class for storing and matching tokens. + * + * The Handler class must provide three member functions: + * - deletePendingClient(Client), + * - deletePendingConnect(ServerData), and + * - tokenMatched(Client, ServerData). + * + * The delete members will be called whenever the collector considers that a + * token has become obsolete and it is about to remove it. + */ +template< class Handler, class Client, class ServerData > +class TokenCollector: private TokenCollectorBase +{ - /** - * \brief The number of actions since the last time that the lists - * where checked for outdated pendingClients or - * pendingConnects. - */ - int mNumberOfActions; + public: - /** - * \brief The time that TokenCollector was created, used for keeping - * the variable times low numbers. - */ - time_t mTimeStart; + TokenCollector(Handler *h): mHandler(h) + { + _TC_CheckData<Client> ClientMustBeSimple; + (void)&ClientMustBeSimple; + _TC_CheckData<ServerData> ServerDataMustBeSimple; + (void)&ServerDataMustBeSimple; + } /** - * \brief The number of seconds between the creation of TokenCollector - * and the last removeOutdated. + * Checks if the server expected this client token. If so, calls + * Handler::tokenMatched. Otherwise marks the client as pending. */ - time_t mTimeNow; + void addPendingClient(std::string const &token, Client data) + { insertClient(token, (intptr_t)data); } /** - * \brief Pointer to the owner of this TokenCollector object. + * Checks if a client already registered this token. If so, calls + * Handler::tokenMatched. Otherwise marks the data as pending. */ - T * mOwner; + void addPendingConnect(std::string const &token, ServerData data) + { insertConnect(token, (intptr_t)data); } /** - * \brief Removes outdated entries. + * Removes a pending client. + * @note Does not call destroyPendingClient. */ - void removeOutdated(); -}; - -// Implementation - -template <class T, class U, class V> -TokenCollector<T, U, V>:: -TokenCollector(T * owner): - mNumberOfActions(0), mTimeStart(time(NULL)), mTimeNow(0), mOwner(owner) -{ -} - -template <class T, class U, class V> -TokenCollector<T, U, V>:: -~TokenCollector() -{ - if (mPendingClients.size()) - LOG_INFO("TokenCollector deleted with " << - mPendingClients.size() << - " clients still pending."); - - if (mPendingConnects.size()) - LOG_INFO("TokenCollector deleted with " << - mPendingConnects.size() << - " connects still pending."); -} - -template <class T, class U, class V> -void TokenCollector<T, U, V>:: -addPendingClient(const std::string & token, U clientPayload) -{ - // Find could also be used for a list, but because new items are - // inserted at the top, we want to start looking there. - for (std::list< void* >::iterator it = mPendingConnects.begin(), - it_end = mPendingConnects.end(); it != it_end; it++) - { - // Because the pointer to the listItem was stored as a pointer to - // void, we have to cast it back to a pointer to a listItem. - // Examples: it = void**; *it = void*; - // ((TC_ListItem< V >*)*it) = TC_ListItem< V >*; - // --------------------------------------------- - if (((TC_ListItem< V >*)*it)->token == token) // Found a match - { - TC_ListItem< V >* tempConnect = (TC_ListItem< V >*)*it; - mPendingConnects.erase(it); - mOwner->tokenMatched(clientPayload, tempConnect->payload); - delete tempConnect; - return; // Done - } - } + void deletePendingClient(Client data) + { removeClient((intptr_t)data); } - TC_ListItem< U >* listItem = new TC_ListItem< U >; - listItem->token = token; - listItem->payload = clientPayload; - listItem->timeStamp = mTimeNow; - - mPendingClients.push_front((void*)listItem); - - if (!(++mNumberOfActions < NB_ACTIONS_BETWEEN_OUTDATED_CHECKS)) - removeOutdated(); -} - -template <class T, class U, class V> -void TokenCollector<T, U, V>:: -deletePendingClient(U clientPayload) -{ - // Find could also be used for a list, but because new items are - // inserted at the top, we want to start looking there. - for (std::list< void* >::iterator it = mPendingClients.begin(), - it_end = mPendingClients.end(); it != it_end; it++) - { - // Because the pointer to the listItem was stored as a pointer to - // void, we have to cast it back to a pointer to a listItem. - // Examples: it = void**; *it = void*; - // ((TC_ListItem< U >*)*it) = TC_ListItem< U >*; - // --------------------------------------------- - if (((TC_ListItem< U >*)*it)->payload == clientPayload) // Found a match - { - delete ((TC_ListItem< U >*)*it); - mPendingConnects.erase(it); - return; // Done - } - } -} - -template <class T, class U, class V> -void TokenCollector<T, U, V>:: -addPendingConnect(const std::string & token, V connectPayload) -{ - // Find could also be used for a list, but because new items are - // inserted at the top, we want to start looking there. - for (std::list< void* >::iterator it = mPendingClients.begin(), - it_end = mPendingClients.end(); it != it_end; it++) - { - // Because the pointer to the listItem was stored as a pointer to - // void, we have to cast it back to a pointer to a listItem. - // Examples: it = void**; *it = void*; - // ((TC_ListItem< U >*)*it) = TC_ListItem< U >*; - // --------------------------------------------- - if (((TC_ListItem< U >*)*it)->token == token) // Found a match - { - TC_ListItem< U >* tempClient = (TC_ListItem< U >*)*it; - mPendingClients.erase(it); - mOwner->tokenMatched(tempClient->payload, connectPayload); - delete tempClient; - return; // Done - } - } - - TC_ListItem< V >* listItem = new TC_ListItem< V >; - listItem->token = token; - listItem->payload = connectPayload; - listItem->timeStamp = mTimeNow; - - mPendingConnects.push_front((void*)listItem); - - if (!(++mNumberOfActions < NB_ACTIONS_BETWEEN_OUTDATED_CHECKS)) - removeOutdated(); -} - -template <class T, class U, class V> -void TokenCollector<T, U, V>:: -removeOutdated() -{ - time_t eraseTime = (mTimeNow > (time_t)TIMEOUT) ? - (mTimeNow - (time_t)TIMEOUT) : (time_t) 0; + private: - // See addPendingClient for a comment about the casting. - while ((mPendingClients.size()) && - (((TC_ListItem< U >*)(mPendingClients.back()))->timeStamp < eraseTime)) - { - mOwner->deletePendingClient( - ((TC_ListItem< U >*)(mPendingClients.back()))->payload); - delete ((TC_ListItem< U >*)(mPendingClients.back())); - mPendingClients.pop_back(); - } + void removedClient(intptr_t data) + { mHandler->deletePendingClient((Client)data); } - while ((mPendingConnects.size()) && - (((TC_ListItem< V >*)(mPendingConnects.back()))->timeStamp < eraseTime)) - { - mOwner->deletePendingConnect( - ((TC_ListItem< V >*)(mPendingConnects.back()))->payload); - delete ((TC_ListItem< U >*)(mPendingConnects.back())); - mPendingConnects.pop_back(); - } + void removedConnect(intptr_t data) + { mHandler->deletePendingConnect((ServerData)data); } - /** - * Change the timeStap after the check, else everything that was just - * inserted might be thrown away. - */ - mTimeNow = time(NULL) - mTimeStart; + void foundMatch(intptr_t client, intptr_t data) + { mHandler->tokenMatched((Client)client, (ServerData)data); } - mNumberOfActions = 0; // Reset the counter. -} + Handler *mHandler; +}; #endif // _TMW_TOKENCOLLECTOR_HPP |