summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog3
-rw-r--r--src/Makefile.am2
-rw-r--r--src/utils/tokencollector.cpp126
-rw-r--r--src/utils/tokencollector.hpp344
4 files changed, 210 insertions, 265 deletions
diff --git a/ChangeLog b/ChangeLog
index 22c66ab3..d8248203 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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