ARIA-Users

Date Index Thread Index

Re: [Aria-users] Using ArClientBase and ArServerBase with UDP protocol



I've took the current release and added a requestOnceUdp to 
ArClientBase.  I've attached the files.  I tried it out by changing the 
requestOnces in clientDemo over into udp and making sure they got there 
as UDP.  If things are set up as tcp only to the server then they 
requestOnce will wind up going over tpc (this is only either set 
explicitly or if the UDP ports can't talk to each other).  I didn't 
include the findCommandFromName since I don't have time to test that one 
and I think requestOnceUdp was all you needed.

If you copy these two files over the ones in what you have you should be 
able to use it.   This is the same interface it has in our current 
branch so you shouldn't have to change anything when the next release 
comes out.

Let me know how it goes,

Matt LaFary
MobileRobots Inc
ActivMedia Robotics

Marko Budisic wrote:
> Matt LaFary wrote:
> 
>> Switching the client to server connections may work okay if you're 
>> just trying to move the requests over to UDP, the connection stuff I'd 
>> advise against (go ahead if you want to, but expect problems that I 
>> can't help you with).  I can see a good case for making 'requestOnce' 
>> so that ArClientBase could do the request once over UDP, and I could 
>> put this into the next version if it'd make anyone's life easier (let 
>> me know if it would).  I can see it making sense for requestOnce since 
>> this is how large amounts of data would normally be sent to the 
>> server.  request I don't really see as viable over UDP since its a 
>> very small amount of data and only sent when the interval changes.
>>
> 
> ...
> 
>> If you let me know more about the root problem you're trying to solve 
>> by moving more over to UDP I might be able to offer some better advice 
>> and/or set things up for the next release to address them (I might be 
>> able to send you just a new ArClientBase with those features in it).
>>
> 
> Thank you for the prompt reply!
> 
> You have assumed correctly, I am interested only in moving requestOnce 
> to UDP, and yes, I would greatly appreciate if the next release of ARIA 
> would feature requestOnce with UDP. We are trying to use UDP as much as 
> possible in general communication in an effort to reduce size of packet 
> overhead and avoid resending of non-delivered packets. I am not 
> interested in moving the whole connection protocol to UDP exclusively.
> 
> I will try and modify ArNetworking to be able to compile it and use it 
> correctly and I will certainly report any changes that I make.
> 
> Thank you once again for your quick and elaborate response, I will let 
> you know how it all went when I try it tomorrow.
> 
> Marko Budisic
> 
/*
ActivMedia Robotics Interface for Applications (ARIA)
Copyright (C) 2004,2005 ActivMedia Robotics, LLC


     This program 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
     (at your option) any later version.

     This program 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 this program; if not, write to the Free Software
     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

If you wish to redistribute ARIA under different terms, contact 
ActivMedia Robotics for information about a commercial version of ARIA at 
XXXXXXXX or 
ActivMedia Robotics, 19 Columbia Drive, Amherst, NH 03031; 800-639-9481

*/

#ifndef NLCLIENTBASE_H
#define NLCLIENTBASE_H

#include "Aria.h"
#include "ArNetPacket.h"
#include "ArNetPacketSenderTcp.h"
#include "ArNetPacketReceiverTcp.h"
#include "ArNetPacketReceiverUdp.h"
#include "ArClientData.h"

/**
   @brief The base client class

   You need to connect a client to a server using blockingConnect() with
   the address for the server.  Then you should add handlers to
   the client to recieve information from the server (with addHandler(),
   request(), and requestOnce()).  Then call run() or runAsync()
   (or loopOnce() if you really know what you're doing).
   You can also add a callback that will get called every cycle of the
   client.
   
   User and password information may be required by the server.
   For details on that see ArServerBase.  The most important thing is
   the modification to blockingConnect(), it can now take a user and
   password argument and upon failure you'll want to check wasRejected()
   to determine whether the client couldn't find the server or if the user and
   password were rejected.  Another relevant function is setServerKey()
   to set the key (string of text) we need to connect to the server.
 **/

class ArClientBase : public ArASyncTask
{
public:
  
  /// The state of the connection
  enum ClientState {
    STATE_NO_CONNECTION, ///< The client has not connected
    STATE_FAILED_CONNECTION, ///< The client tried to connect and failed
    STATE_OPENED_SOCKET, ///< Client opened socket, waiting for intro from srvr
    STATE_EXCHANGED_INTROS, ///< Client and server have exchanged introductions
    STATE_REJECTED, ///< Client was rejected by server
    STATE_WAITING_LIST, ///< Client was connected to server, waiting for list
    STATE_CONNECTED, ///< Client is connected to server
    STATE_LOST_CONNECTION ///< Client lost connection to server
  };
  
  /// Constructor
  AREXPORT ArClientBase();
  /// Destructor
  AREXPORT virtual ~ArClientBase();
  
  /// Connect to a server
  AREXPORT bool blockingConnect(const char *host, int port, bool print = true,
				const char *user = NULL, 
				const char *password = NULL);

  /// Disconnect form a server
  AREXPORT bool disconnect(void);

  /// @return true if we're connected to a server
  AREXPORT bool isConnected(void) { return myState == STATE_CONNECTED;}
  
  /// @return true if a server connection attempt failed because the server rejected the username or password, false if the connection failed for another reason, or the username/password were accepted.
  AREXPORT bool wasRejected(void) { return myState == STATE_REJECTED; }

  /// Adds a functor for some particular data
  AREXPORT bool addHandler(const char *name, 
			    ArFunctor1 <ArNetPacket *> *functor);

  /// Removes a functor for some particular data by name
  AREXPORT bool remHandler(const char *name, ArFunctor1<ArNetPacket *> *functor);

  /// Request some data every mSec milliseconds
  AREXPORT bool request(const char *name, long mSec, 
				ArNetPacket *packet = NULL);

  /// Don't want this data anymore
  AREXPORT bool requestStop(const char *name);
  
  /// Request some data (or send a command) just once
  AREXPORT bool requestOnce(const char *name, 
			    ArNetPacket *packet = NULL);

  /// Request some data (or send a command) just once
  AREXPORT bool requestOnceUdp(const char *name, 
			    ArNetPacket *packet = NULL);

  /// Request some data (or send a command) just once with a string as argument
  AREXPORT bool requestOnceWithString(const char *name, const char *str);
  
  /// Sees if this data exists
  AREXPORT bool dataExists(const char *name);
  
  /// Sets the 'key' needed to connect to the server
  AREXPORT void setServerKey(const char *serverKey);
  
  /// Gets the last time a packet was received
  AREXPORT ArTime getLastPacketReceived(void) const;

  /// Runs the client in this thread
  AREXPORT virtual void run(void);
  
  /// Runs the client in its own thread
  AREXPORT virtual void runAsync(void);

  /// Print out or data with descriptions
  AREXPORT void logDataList(void);

  /// Adds a functor to call every cycle
  AREXPORT void addCycleCallback(ArFunctor *functor);
  
  /// Removes a functor called every cycle
  AREXPORT void remCycleCallback(ArFunctor *functor);

  /// Send a packet over TCP
  AREXPORT bool sendPacketTcp(ArNetPacket *packet);
  /// Send a packet over UDP (unless client only wants tcp then sends over tcp)
  AREXPORT bool sendPacketUdp(ArNetPacket *packet);

  /// Sets the time to allow for connection (default 3)
  AREXPORT void setConnectTimeoutTime(int sec);

  /// Gets the time allowed for connection
  AREXPORT int getConnectTimeoutTime(void);

  /// Adds a call for when the server shuts down 
  AREXPORT void addServerShutdownCB(ArFunctor *functor,
			       ArListPos::Pos position = ArListPos::LAST);

  /// Removes a call for when the server shuts down
  AREXPORT void remServerShutdownCB(ArFunctor *functor);

  /// Adds a call for when a disconnection has occured because of error
  AREXPORT void addDisconnectOnErrorCB(ArFunctor *functor,
			       ArListPos::Pos position = ArListPos::LAST);

  /// Removes a call for when a disconnection has occured because of error
  AREXPORT void remDisconnectOnErrorCB(ArFunctor *functor);

  /// Run the loop once
  AREXPORT void loopOnce(void);

  /// Process the packet whever it came from
  AREXPORT void processPacket(ArNetPacket *packet);

  /// Process a packet from udp (just hands off to processPacket)
  AREXPORT void processPacketUdp(ArNetPacket *packet,
				struct sockaddr_in *sin);
  /// Sets it so we only get TCP data from the server not UDP
  AREXPORT void setTcpOnlyFromServer(void);
  /// Sets it so we only send TCP data to the server
  AREXPORT void setTcpOnlyToServer(void);
  /// Gets the name of the data a packet is for
  AREXPORT const char *getName(ArNetPacket *packet);
  /// Gets the name of the data a command is
  AREXPORT const char *getName(unsigned int command);
  // the function for the thread
  AREXPORT virtual void * runThread(void *arg);
  /// Internal function to get the socket (no one should need this)
  AREXPORT struct in_addr *getTcpAddr(void) { return myTcpSocket.inAddr(); }
protected:
  AREXPORT bool setupPacket(ArNetPacket *packet);
  ArTime myLastPacketReceived;
  std::list<ArFunctor *> myServerShutdownCBList;
  std::list<ArFunctor *> myDisconnectOnErrorCBList;
  std::list<ArFunctor *> myCycleCallbacks;
  // does the first part of connection
  bool internalConnect(const char *host, int port, bool obsolete);
  void buildList(ArNetPacket *packet);
  void internalSwitchState(ClientState state);
  ClientState myState;
  ArTime myStateStarted;
  bool myUdpConfirmedFrom;
  bool myUdpConfirmedTo;
  // if we only send tcp
  bool myTcpOnlyTo;
  // if we only receive tcp from the server
  bool myTcpOnlyFrom;

  bool myQuiet;
  std::string myUser;
  std::string myPassword;
  // the time we allow for connections
  int myTimeoutTime;
  // the time we started our connection
  ArTime myStartedConnection;
  
  ArMutex myCycleCallbackMutex;
  // our map of names to ints
  std::map<std::string, unsigned int> myNameIntMap;
  // our map of ints to functors
  std::map<unsigned int, ArClientData *> myIntDataMap;
  
  struct sockaddr_in myUdpSin;
  bool myUdpSinValid;
  // the port the server said it was using
  unsigned int myServerReportedUdpPort;
  // the port the server actually is using
  unsigned int myServerSentUdpPort;
  unsigned int myUdpPort;
  long myAuthKey;
  long myIntroKey;
  std::string myServerKey;
  ArNetPacketSenderTcp myTcpSender;
  ArNetPacketReceiverTcp myTcpReceiver;
  ArNetPacketReceiverUdp myUdpReceiver;
  ArSocket myTcpSocket;
  ArSocket myUdpSocket;
  ArFunctor1C<ArClientBase, ArNetPacket *> myProcessPacketCB;
  ArFunctor2C<ArClientBase, ArNetPacket *, struct sockaddr_in *> myProcessPacketUdpCB;
};

#endif // NLCLIENTBASE_H
/*
ActivMedia Robotics Interface for Applications (ARIA)
Copyright (C) 2004,2005 ActivMedia Robotics, LLC


     This program 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
     (at your option) any later version.

     This program 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 this program; if not, write to the Free Software
     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

If you wish to redistribute ARIA under different terms, contact 
ActivMedia Robotics for information about a commercial version of ARIA at 
XXXXXXXX or 
ActivMedia Robotics, 19 Columbia Drive, Amherst, NH 03031; 800-639-9481

*/

#include "Aria.h"
#include "ArExport.h"
#include "ArClientBase.h"
#include "ArServerCommands.h"
#include "ArClientCommands.h"
#include "md5.h"

AREXPORT ArClientBase::ArClientBase() :
  myProcessPacketCB(this, &ArClientBase::processPacket),
  myProcessPacketUdpCB(this, &ArClientBase::processPacketUdp)

{
  setThreadName("ArClientBase");
  internalSwitchState(STATE_NO_CONNECTION);
  myTcpSender.setSocket(&myTcpSocket);
  myTcpReceiver.setSocket(&myTcpSocket);
  myTcpReceiver.setProcessPacketCB(&myProcessPacketCB);
  myUdpReceiver.setSocket(&myUdpSocket);
  myUdpReceiver.setProcessPacketCB(&myProcessPacketUdpCB);
  myServerReportedUdpPort = 0;
  myUdpConfirmedFrom = false;
  myUdpConfirmedTo = false;
  myTcpOnlyTo = false;
  myTcpOnlyFrom = false;
  myTimeoutTime = 3;
  myQuiet = false;
  myLastPacketReceived.setToNow();
}

AREXPORT ArClientBase::~ArClientBase()
{

}

/**
   cycleCallbacks are called every cycle, and are in no particular
   order, since all they should do is send packets, never any
   processing

   You cannot add a cycle callback from within a cycle or it will deadlock
   @param functor callback to add
**/
AREXPORT void ArClientBase::addCycleCallback(ArFunctor *functor)
{
  myCycleCallbackMutex.lock();
  myCycleCallbacks.push_front(functor);
  myCycleCallbackMutex.unlock();
}

/**
   cycleCallbacks are called every cycle, and are in no particular
   order, since all they should do is send packets, never any
   processing
   You cannot remove a cycle callback from within a cycle or it will deadlock
   @param functor callback to remove
**/
AREXPORT void ArClientBase::remCycleCallback(ArFunctor *functor)
{
  myCycleCallbackMutex.lock();
  myCycleCallbacks.remove(functor);
  myCycleCallbackMutex.unlock();
}


/**
   @param sec This value controls how long the client will give to
   connect, it is the argument in number of seconds... if <= 0 then no
   timeout will take place.
**/
AREXPORT void ArClientBase::setConnectTimeoutTime(int sec)
{
  if (sec < 0)
    myTimeoutTime = 0;
  else
    myTimeoutTime = sec;
}

/**
   @return This value controls how long the client will give to
   connect, it is the argument in number of seconds... if <= 0 then no
   timeout will take place.
**/
AREXPORT int ArClientBase::getConnectTimeoutTime(void)
{
  return myTimeoutTime;
}



/**
   @param host the host to connect to

   @param port the port to connect on
   
   @param print whether to print out our connection information or not
   (you'll usually want to use the default argument of true, only here
   for some more advanced internal things), this carries over to things like
   disconnect printing too

   @param user  user name, or NULL for none.
   @param password password (cleartext), or NULL for none.
 **/
AREXPORT bool ArClientBase::blockingConnect(const char *host, int port, 
					    bool print, const char *user,
					    const char *password)
{
  bool ret = false;
  ArTime startedUdpWait;
  ArNetPacket packet;
  bool debugPrinting = false;
  lock();
  myQuiet = !print;
  myTcpReceiver.setQuiet(myQuiet);
  if (user != NULL)
    myUser = user;
  else
    myUser = "";
  if (password != NULL)
    myPassword = password;
  else
    myPassword = "";

  if (debugPrinting)
    ArLog::log(ArLog::Normal, "0");
  myStartedConnection.setToNow();
  while (1)
  {
    if (debugPrinting)
      ArLog::log(ArLog::Normal, "1");
    loopOnce();
    if (internalConnect(host, port, print))
    {
      if (debugPrinting)
	ArLog::log(ArLog::Normal, "2");
      break;
    }
    else
    {
      // see if we didn't get udp from
      if (myStartedConnection.mSecSince() > myTimeoutTime * .33 * 1000.0)
      {
	if (debugPrinting)
	  ArLog::log(ArLog::Normal, "3");
	unlock();
	internalSwitchState(STATE_FAILED_CONNECTION);
	return false;
      }
      else 
      {
	if (debugPrinting)
	  ArLog::log(ArLog::Normal, "4");
	internalSwitchState(STATE_FAILED_CONNECTION);
	ArUtil::sleep(100);
      }
    }
    
  }
  if (debugPrinting)
    ArLog::log(ArLog::Normal, "5");
  while (myState != STATE_REJECTED && myState != STATE_CONNECTED && 
	 myState != STATE_NO_CONNECTION)
  {
    if (debugPrinting)
      ArLog::log(ArLog::Normal, "6");
    loopOnce();
    if (myTimeoutTime > 0 && 
	myStartedConnection.mSecSince() > myTimeoutTime * 1000.0)
    {
      if (debugPrinting)
	ArLog::log(ArLog::Normal, "7");
      if (!myQuiet)
	ArLog::log(ArLog::Terse, "ArClientBase::blockingConnect: Connection timed out.");
      internalSwitchState(STATE_FAILED_CONNECTION);
      //myState = STATE_NO_CONNECTION;
      unlock();
      return false;
    }
    ArUtil::sleep(1);
  }
  if (myState == STATE_REJECTED)
  {
    return false;
  }
  if (debugPrinting)
    ArLog::log(ArLog::Normal, "8");
  startedUdpWait.setToNow();
  while ((!myUdpConfirmedFrom && !myTcpOnlyFrom) && 
	 (!myUdpConfirmedTo && !myTcpOnlyTo))
  {
    if (debugPrinting)
      ArLog::log(ArLog::Normal, "9");
    loopOnce();
    // see if we didn't get udp from
    if (startedUdpWait.mSecSince() > myTimeoutTime * .66 * 1000.0 &&
	!myUdpConfirmedFrom && !myTcpOnlyFrom)
    {
      packet.empty();
      packet.setCommand(ArClientCommands::TCP_ONLY);
      sendPacketTcp(&packet);
      myTcpOnlyFrom = true;
      if (!myQuiet)
	ArLog::log(ArLog::Normal, "ArClientBase: Didn't get confirmation of UDP from server, switching to TCP.");
    }
    // see if we haven't confirmed udp to
    if (startedUdpWait.mSecSince() > myTimeoutTime * .66 * 1000.0 &&
	!myUdpConfirmedTo && !myTcpOnlyTo)
    {
      packet.empty();
      myTcpOnlyTo = true;
      if (!myQuiet)
	ArLog::log(ArLog::Normal, "ArClientBase: Didn't get confirmation of UDP to server, switching to TCP.");
    }
    if (myTimeoutTime > 0 && 
	myStartedConnection.mSecSince() > myTimeoutTime * 1000.0)
    {
      if (!myQuiet)
	ArLog::log(ArLog::Terse, "ArClientBase::blockingConnect: Connection timed out.");
      //myState = STATE_NO_CONNECTION;
      internalSwitchState(STATE_FAILED_CONNECTION);
    }
    ArUtil::sleep(1);
  }
  if (debugPrinting)
    ArLog::log(ArLog::Normal, "10");
  if (myState == STATE_CONNECTED)
    ret = true;
  unlock();
  return ret;  
}

bool ArClientBase::internalConnect(const char *host, int port, bool obsolete)
{
  if (myState != STATE_NO_CONNECTION && myState != STATE_FAILED_CONNECTION)
  {
    if (!myQuiet)
      ArLog::log(ArLog::Terse, 
	 "ArClientBase: Connection already established or being connected.");
    return false;
  }

  // if we connect tcp then connect up the udp
  if (myTcpSocket.connect(host, port, ArSocket::TCP) && myTcpSocket.setNonBlock())
  {
    // fire up the udp
    if (!myUdpSocket.create(ArSocket::UDP) ||
	!myUdpSocket.findValidPort(10000) ||
	!myUdpSocket.setNonBlock())
    {
      myUdpSocket.setLinger(0);
    if (!myQuiet)
	ArLog::log(ArLog::Terse, 
	     "ArClientBase::connect: Failed to open udp socket, forcing TCP");
		 //internalSwitchState(STATE_FAILED_CONNECTION);
		 //return false;		 
      myTcpOnlyTo = true;
      myTcpOnlyFrom = true;
    }
    myUdpSinValid = false;
    myUdpSin.sin_family=AF_INET;
    myUdpSin.sin_addr = *myTcpSocket.inAddr();
    myUdpPort = myUdpSocket.netToHostOrder(myUdpSocket.inPort());
    if (!myQuiet)
      ArLog::log(ArLog::Verbose, 
	"Opened client connection to %s on tcp port %d with my udp port of %d",
		 host, port, myUdpPort);
    // we can't tell if udp connected or not (because its udp) so just
    // say we're good
    internalSwitchState(STATE_OPENED_SOCKET);
    return true;
  }
  else
  {
    if (!myQuiet)
      ArLog::log(ArLog::Terse,
		 "ArClientBase::connect: Failed to open TCP socket");
    internalSwitchState(STATE_FAILED_CONNECTION);
    return false;
  }
}

AREXPORT bool ArClientBase::disconnect(void)
{
  ArNetPacket packet;
  bool ret;

  if (!myQuiet)
    ArLog::log(ArLog::Normal, "Disconnected from server."); 
  packet.setCommand(ArClientCommands::SHUTDOWN);
  ret = sendPacketTcp(&packet);
  myState = STATE_NO_CONNECTION;
  return ret;
}

AREXPORT  bool ArClientBase::setupPacket(ArNetPacket *packet)
{
  if (myState == STATE_NO_CONNECTION || myState == STATE_FAILED_CONNECTION)
    return false;

  packet->finalizePacket();
  return true;
} 

AREXPORT bool ArClientBase::sendPacketTcp(ArNetPacket *packet)
{
  if (!setupPacket(packet))
    return false;
  else
  {
    myTcpSender.sendPacket(packet);
    return true;
  }
}

AREXPORT bool ArClientBase::sendPacketUdp(ArNetPacket *packet)
{
  if (!setupPacket(packet))
    return false;
  else
  {
    if (myUdpSocket.sendTo(packet->getBuf(), packet->getLength(), &myUdpSin) 
	== packet->getLength())
    {
      return true;
    }
    else
      return false;
  }
}

AREXPORT void ArClientBase::loopOnce(void)
{
  std::list<ArFunctor *>::iterator it;
  if (myState != STATE_NO_CONNECTION && myState != STATE_FAILED_CONNECTION &&
      myState != STATE_LOST_CONNECTION)
  {
    // if we couldn't receive tcp data fail
    if (!myTcpReceiver.readData())
    {
      if (!myQuiet)
	ArLog::log(ArLog::Normal, 
		   "Lost connection to server (couldn't recv).");
      myState = STATE_LOST_CONNECTION;
      stopRunning();
      for (it = myDisconnectOnErrorCBList.begin(); 
	   it != myDisconnectOnErrorCBList.end(); 
	   ++it)
	(*it)->invoke();
    }
    // if we can't receive UDP just make an error log
    else if (!myTcpOnlyFrom && !myUdpReceiver.readData())
    {
      if (!myQuiet)
	ArLog::log(ArLog::Normal, "UDP Troubles, continuing");
    }
    if (!myTcpSender.sendData())
    {
      if (!myQuiet)
	ArLog::log(ArLog::Normal, 
		   "Lost connection to server (couldn't send).");
      myState = STATE_LOST_CONNECTION;
      stopRunning();
      for (it = myDisconnectOnErrorCBList.begin(); 
	   it != myDisconnectOnErrorCBList.end(); 
	   ++it)
	(*it)->invoke();
    }
  }
  myCycleCallbackMutex.lock();
  for (it = myCycleCallbacks.begin(); it != myCycleCallbacks.end(); ++it)
    (*it)->invoke();
  myCycleCallbackMutex.unlock();
}

AREXPORT void ArClientBase::run(void)
{
  runInThisThread();
}

AREXPORT void ArClientBase::runAsync(void)
{
  create(true, false);
}

AREXPORT void *ArClientBase::runThread(void *arg)
{
  threadStarted();
  while (myRunning)
  {
    lock();
    loopOnce();
    unlock();
    ArUtil::sleep(1);
  }
  return NULL;
}


AREXPORT void ArClientBase::processPacket(ArNetPacket *packet)
{
  ArNetPacket retPacket;
  char buf[512];
  char passwordKey[2048];
  std::list<ArFunctor *>::iterator it;

  myLastPacketReceived.setToNow();
  // See if we should close our connection
  if (packet->getCommand() == ArServerCommands::SHUTDOWN)
  {
    if (!myQuiet)
      ArLog::log(ArLog::Normal, "Server shutting down and closed connection.");
    stopRunning();
    disconnect();
    myState = STATE_LOST_CONNECTION;
    for (it = myServerShutdownCBList.begin(); 
	 it != myServerShutdownCBList.end(); 
	 ++it)
      (*it)->invoke();

  }
  /// see if we've opened the socket and this is an intro
  else if (myState == STATE_OPENED_SOCKET && 
      packet->getCommand() == ArServerCommands::INTRODUCTION)
  {
    // first read the data from the packet
    packet->bufToStr(buf, 512);
    myServerReportedUdpPort = packet->bufToUByte2();
    if (!myQuiet)
      ArLog::log(ArLog::Verbose, "Connection to version %s with udp port %u",
	       buf, myServerReportedUdpPort);
    // get the keys
    myAuthKey = packet->bufToUByte4();
    myIntroKey = packet->bufToUByte4();
    packet->bufToStr(passwordKey, sizeof(passwordKey));
    if (myTcpOnlyFrom)
    {
      retPacket.empty();
      retPacket.setCommand(ArClientCommands::TCP_ONLY);
      sendPacketTcp(&retPacket);
    }
    // introduce ourself normally via tcp
    retPacket.empty();
    retPacket.setCommand(ArClientCommands::INTRODUCTION);
    retPacket.uByte2ToBuf(myUdpPort);
    retPacket.strToBuf(myUser.c_str());
    // old simple cleartext way
    //retPacket.strToBuf(myPassword.c_str());
    md5_state_t md5State;
    unsigned char md5Digest[16];
    
    md5_init(&md5State);
    md5_append(&md5State, (unsigned char *)myServerKey.c_str(), 
	       myServerKey.size());
    md5_append(&md5State, (unsigned char *)passwordKey, strlen(passwordKey));
    md5_append(&md5State, (unsigned char *)myPassword.c_str(), 
	       myPassword.size());
    md5_finish(&md5State, md5Digest);

    retPacket.dataToBuf((const char *)md5Digest, 16);
    myPassword = "";
    sendPacketTcp(&retPacket);
    internalSwitchState(STATE_EXCHANGED_INTROS);
  }
  // if we're not at opened socket and got an intro somethings horribly wrong
  else if (myState != STATE_OPENED_SOCKET && 
      packet->getCommand() == ArServerCommands::INTRODUCTION)
  {
    if (!myQuiet)
      ArLog::log(ArLog::Terse, "ArClientBase: introduction received when not in STATE_OPENED_SOCKET");
    return;
  }
  // if we're at opened socket and received something other than intro
  // then somethings horribly wrong
  else if (myState == STATE_OPENED_SOCKET && 
      packet->getCommand() != ArServerCommands::INTRODUCTION)
  {
    if (!myQuiet)
      ArLog::log(ArLog::Terse, "ArClientBase: packet other than introduction received when in STATE_OPENED_SOCKET");
    return;
  }
  // if we've exchanged intros and are waiting for connection or rejection
  else if (myState == STATE_EXCHANGED_INTROS && 
	   packet->getCommand() == ArServerCommands::CONNECTED)
  {
    if (!myQuiet)
      ArLog::log(ArLog::Terse, "Client now connected to server.");
    internalSwitchState(STATE_WAITING_LIST);
    // introduce ourself via udp
    myUdpSin.sin_port = ArSocket::hostToNetOrder(myServerReportedUdpPort);
    retPacket.empty();
    retPacket.setCommand(ArClientCommands::UDP_INTRODUCTION);
    retPacket.uByte4ToBuf(myAuthKey);
    sendPacketUdp(&retPacket);
    return;
  }
  // if we've received a connected and aren't at exchanging intros
  else if (packet->getCommand() == ArServerCommands::CONNECTED)
  {
    ArLog::log(ArLog::Terse, 
 "ArClientBase:: connected packet received not during STATE_EXCHANGED_INTROS");
    return;
  }
  // if we're exchanging intros and were rejected
  else if (myState != STATE_CONNECTED &&
	   packet->getCommand() == ArServerCommands::REJECTED)
  {
    if (!myQuiet)
      ArLog::log(ArLog::Terse, "Server rejected connection, failed.");
    internalSwitchState(STATE_REJECTED);
    return;
  }
  // if we received rejected and aren't at exchanging intros
  else if (packet->getCommand() == ArServerCommands::REJECTED)
  {
    ArLog::log(ArLog::Terse, 
	       "ArClientBase:: rejected packet received after connected");
    return;
  }
  // if we're waiting for our list and get it we're connected
  else if (myState == STATE_WAITING_LIST &&
	   packet->getCommand() == ArServerCommands::LIST)
  {
    internalSwitchState(STATE_CONNECTED);
    buildList(packet);
    return;
  }
  // see if we got any lists when not waiting for them
  else if (packet->getCommand() == ArServerCommands::LIST ||
	   packet->getCommand() == ArServerCommands::LISTSINGLE || 
	   packet->getCommand() == ArServerCommands::LISTARGRET ||
	   packet->getCommand() == ArServerCommands::LISTARGRETSINGLE)
  {
    buildList(packet);
    return;
  }
  // if we're in exhanged intros and receive a packet not for that state
  else if (myState == STATE_EXCHANGED_INTROS && 
	   packet->getCommand() != ArServerCommands::CONNECTED && 
	   packet->getCommand() != ArServerCommands::REJECTED)
  {
    ArLog::log(ArLog::Terse, "ArClientBase: we're in STATE_EXCHANGE_INTROS and recieved something other than connected or rejected.");
    return;
  }
  // if we're connected and got a udp confirmed packet then everything is good
  else if (myState == STATE_CONNECTED && 
	   packet->getCommand() == ArServerCommands::UDP_CONFIRMATION)
  {
    if (!myQuiet)
      ArLog::log(ArLog::Verbose, 
		 "Clients udp connection to server confirmed.");
    myUdpConfirmedTo = true;
    return;
  }
  // if we're not connected and got a udp confirmation it means
  // something is wrong
  else if (packet->getCommand() == ArServerCommands::UDP_CONFIRMATION)
  {
    ArLog::log(ArLog::Terse, 
	"ArClientBase: Udp Confirmation received when not in STATE_CONNECTED");
    return;
  }
  // if we're connected and got a tcp only packet then everything is good
  else if (myState == STATE_CONNECTED && 
	   packet->getCommand() == ArServerCommands::TCP_ONLY)
  {
    if (!myQuiet)
      ArLog::log(ArLog::Normal, "Client told to only use tcp.");
    myTcpOnlyTo = true;
    return;
  }
  // if none of the above were triggered its just a normal packet
  else if (myState == STATE_CONNECTED)
  {
    std::map<unsigned int, ArClientData *>::iterator it;
    ArClientData *clientData;
    // see if we have the command
    if ((it = myIntDataMap.find(packet->getCommand())) == myIntDataMap.end())
    {
      ArLog::log(ArLog::Terse, 
		 "ArClientBase got packet for %d which doesn't exist, packet was %d long", 
		 packet->getCommand(), packet->getLength());
      packet->log();
      int ijk;
      ArLog::log(ArLog::Terse, "Packet: ");
      for (ijk = 0; ijk < packet->getLength(); ijk++)
	ArLog::log(ArLog::Terse, " %d ", 
		   (unsigned char) packet->getBuf()[ijk]);
      
      return;
    }
    clientData = (*it).second;
    if (clientData == NULL)
    {
      ArLog::log(ArLog::Terse,
		 "ArClientBase: null client data for command %d", 
		 packet->getCommand());
      return;
    }
    if (clientData->getFunctorList()->size() == 0)
    {
      ArLog::log(ArLog::Verbose, 
		 "ArClientBase: No functor to handle command %d",
		 packet->getCommand());
      return;
    }
    else
    {
      std::list<ArFunctor1<ArNetPacket *> *>::const_iterator it;
      for (it = clientData->getFunctorList()->begin(); 
	   it != clientData->getFunctorList()->end();
	   it++)
      {
	packet->resetRead();
	(*it)->invoke(packet);
      }
      //clientData->getFunctor()->invoke(packet);
	
    }
    

  }
  else
  {
	  //packet->bufToStr(&str);
    //packet->printHex();
    ArLog::log(ArLog::Verbose, "bogus packet of %u command %d long (probably after close)", 
	       packet->getCommand(),  packet->getLength());
  }

  //sendPacketTcp(packet);
  
}

void ArClientBase::buildList(ArNetPacket *packet)
{
  ArClientData *clientData;
  unsigned int listLen;
  unsigned int i;
  unsigned int command;
  char name[512];
  char description[512];
  char argDesc[512];
  char retDesc[512];

  // if its a single list snag it and run
  if (packet->getCommand() == ArServerCommands::LISTSINGLE)
  {
    command = packet->bufToUByte2();
    packet->bufToStr(name, sizeof(name));
    packet->bufToStr(description, sizeof(description));
    clientData = new ArClientData(name, description, command, NULL);
    ArLog::log(ArLog::Verbose, "ArClientBase: new entry number %d for data %s with description %s", command, name, description);
    if (myIntDataMap.find(command) != myIntDataMap.end())
      ArLog::log(ArLog::Normal, "ArClientBase: is already an entry for number %d as data %d\n, overwriting", command, myIntDataMap[command]->getName());
    myNameIntMap[name] = command;
    myIntDataMap[command] = clientData;
    return;
  }
  // if its a single list snag it and run
  else if (packet->getCommand() == ArServerCommands::LISTARGRETSINGLE)
  {
    command = packet->bufToUByte2();
    clientData = myIntDataMap[command];
    if (clientData == NULL)
    {
      ArLog::log(ArLog::Normal, 
		 "ArClientBase::buildList: Unknown command %s %d", 
		 getName(packet->getCommand()), packet->getCommand());
      return;
    }
    packet->bufToStr(argDesc, sizeof(argDesc));
    packet->bufToStr(retDesc, sizeof(retDesc));
    clientData->setArgRetDescs(argDesc, retDesc);
    return;
  }
  else if (packet->getCommand() == ArServerCommands::LIST)
  {
    listLen = packet->bufToUByte2();
    // otherwise loop and read them all
    for (i = 0; i < listLen; i++)
    {
      command = packet->bufToUByte2();
      packet->bufToStr(name, sizeof(name));
      packet->bufToStr(description, sizeof(description));
      clientData = new ArClientData(name, description, command, NULL);
      ArLog::log(ArLog::Verbose, "ArClientBase: new entry number %d for data %s with description %s", command, name, description);
      if (myIntDataMap.find(command) != myIntDataMap.end() && 
	  strcmp(myIntDataMap[command]->getName(), name))
	ArLog::log(ArLog::Normal, "ArClientBase: is already an entry for number %d as data %d\n, overwriting", command, myIntDataMap[command]->getName());
      myNameIntMap[name] = command;
      myIntDataMap[command] = clientData;
    }
    return;
  }
  else if (packet->getCommand() == ArServerCommands::LISTARGRET)
  {
    listLen = packet->bufToUByte2();
    // otherwise loop and read them all
    for (i = 0; i < listLen; i++)
    {
      command = packet->bufToUByte2();
      clientData = myIntDataMap[command];
      if (clientData == NULL)
      {
	ArLog::log(ArLog::Normal, 
		   "ArClientBase::buildList: Unknown command %s %d", 
		   getName(packet->getCommand()), packet->getCommand());
      }
      packet->bufToStr(argDesc, sizeof(argDesc));
      packet->bufToStr(retDesc, sizeof(retDesc));
      clientData->setArgRetDescs(argDesc, retDesc);
    }
    return;
  }
  else
  {
    ArLog::log(ArLog::Terse, 
	       "ArClientBase::buildList: Unhandled packet type %s %d", 
	       getName(packet->getCommand()), packet->getCommand());
  }
}

AREXPORT void ArClientBase::processPacketUdp(ArNetPacket *packet,
				 struct sockaddr_in *sin)
{
  unsigned char *bytes = (unsigned char *)&sin->sin_addr.s_addr;
  long introKey;
  ArNetPacket retPacket;

  myLastPacketReceived.setToNow();
  // see if its an intro packet and we're connected (or could be but
  // not yet know it)
  if ((myState == STATE_CONNECTED || myState == STATE_EXCHANGED_INTROS ||
      myState == STATE_WAITING_LIST) && 
      packet->getCommand() == ArServerCommands::UDP_INTRODUCTION)
  {
    // see if we've already confirmed the UDP
    if (myUdpConfirmedFrom)
      return;
    introKey = packet->bufToByte4();
    if (myIntroKey != introKey)
    {
      ArLog::log(ArLog::Terse, 
		 "Udp introduction packet received with wrong introKey");
      return;
    }
    if (myServerReportedUdpPort != ArSocket::netToHostOrder(sin->sin_port))
      ArLog::log(ArLog::Verbose,
      "ArClientBase: ports don't match, said from server %d and given was %d",
	     myServerReportedUdpPort, ArSocket::netToHostOrder(sin->sin_port));
    myUdpSin.sin_port = sin->sin_port;
    myUdpConfirmedFrom = true;
    // now confirm to the server we got the UDP packet
    retPacket.empty();
    retPacket.setCommand(ArClientCommands::UDP_CONFIRMATION);
    sendPacketTcp(&retPacket);
    if (!myQuiet)
      ArLog::log(ArLog::Normal, "Server connected to us on udp port %d", 
		 ArSocket::netToHostOrder(myUdpSin.sin_port));
    return;
  }
  // if we receive an intro packet but aren't connected
  else if (packet->getCommand() == ArServerCommands::UDP_INTRODUCTION)
  {
    ArLog::log(ArLog::Terse, 
	       "Udp introduction packet received while not connected");
    return;
  }
  // if its not an intro, make sure it matchs our server
  else if (myUdpSin.sin_port == sin->sin_port &&
	myUdpSin.sin_addr.s_addr == sin->sin_addr.s_addr)
  {
    // TODO make this reject them if they're not in the user area
    processPacket(packet);
  }
  // if it doesn't warn about it
  else
  {
    ArLog::log(ArLog::Normal, "Bogus UDP packet from %d.%d.%d.%d %d", 
	       bytes[0], bytes[1], bytes[2], bytes[3], 
	       ArSocket::netToHostOrder(sin->sin_port));
  }

}

void ArClientBase::internalSwitchState(ClientState state)
{
  myState = state;
  myStateStarted.setToNow();
}

/**
   This adds a functor that will be called with a packet whenever a
   packet for that name arrives 
   
   @param name the name of the data to use with this functor

   @param functor the functor to call with the packet when data with
   this name arrives
   
   @return false is returned if this name doesn't exist in the data or
   if there is already a functor for this name
**/
AREXPORT bool ArClientBase::addHandler(const char *name, 
				       ArFunctor1 <ArNetPacket *> *functor)
{
  ArClientData *clientData;
  // see if we have this data
  if (myNameIntMap.find(name) == myNameIntMap.end())
  {
    ArLog::log(ArLog::Normal, "ArClientBase::addHandler: There is no data by the name \"%s\" to handle", name);
    return false;
  }
  // since we have the data get ye pointer to it
  clientData = myIntDataMap[myNameIntMap[name]];
  // make sure nothings gone wrong
  if (clientData == NULL)
  {
    ArLog::log(ArLog::Normal, "ArClientBase::addHandler: There was no clientData for data \"%s\"", name);
    return false;
  }
  // see if it already has a functor
  if (clientData->getFunctorList()->size() != 0)
  {
    ArLog::log(ArLog::Verbose, "ArClientBase::addHandler: There is already a functor for data \"%s\", adding anyways", name);
    //return false;
  }
  // set the functor
  clientData->addFunctor(functor);
  return true;
}

AREXPORT bool ArClientBase::remHandler(const char *name, 
				       ArFunctor1<ArNetPacket *> *functor)
{
  ArClientData *clientData;
  // see if we have this client data
  if (myNameIntMap.find(name) == myNameIntMap.end())
  {
    ArLog::log(ArLog::Normal, "ArClientBase::remHandler: There is no data \"%s\"", name);
    return false;
  }
  if ((clientData = myIntDataMap[myNameIntMap[name]]) == NULL)
  {
    ArLog::log(ArLog::Normal, "ArClientBase::remHandler: There was no client data for data \"%s\"", name);
    return false;
  }
  // set the functor to NULL
  clientData->remFunctor(functor);
  return true;
}

/**
   This requests data from the server for the given name, at mSec
   milliseconds interval, optionally passing along data for the
   request.

   @param name the name of the data to request

   @param mSec the number of milliseconds we want a refresh on this
   data, if this number is 0 then the server will send the information
   as often as it is available

   @param packet the packet that contains the data to use as argument
 **/
AREXPORT bool ArClientBase::request(const char *name, long mSec, 
				    ArNetPacket *packet)
{
  std::map<std::string, unsigned int>::iterator it;
  ArNetPacket sending;
  unsigned int command;

  if ((it = myNameIntMap.find(name)) == myNameIntMap.end())
  {
    ArLog::log(ArLog::Normal, 
	       "Requesting data for \"%s\" but no data with that name exists",
	       name);
    return false;
  }
  command = (*it).second;
  sending.setCommand(ArClientCommands::REQUEST);
  sending.uByte2ToBuf(command);
  sending.byte4ToBuf(mSec);
  if (packet != NULL)
  {
    packet->resetRead();
    sending.dataToBuf(&packet->getBuf()[packet->getReadLength()],
		      packet->getLength() - packet->getReadLength());
  }
  ArLog::log(ArLog::Verbose, "Requesting data for \"%s\"", name);
  return sendPacketTcp(&sending);
}


/**
   @param name the name to stop sending
**/
AREXPORT bool ArClientBase::requestStop(const char *name)
{
  std::map<std::string, unsigned int>::iterator it;
  ArNetPacket sending;
  
  if ((it = myNameIntMap.find(name)) == myNameIntMap.end())
  {
    ArLog::log(ArLog::Normal, 
       "Requesting stop of data for \"%s\" but no data with that name exists",
	       name);
    return false;
  }
  ArLog::log(ArLog::Verbose, 
	     "Requesting stop data for \"%s\"", name);
  sending.setCommand(ArClientCommands::REQUESTSTOP);
  sending.uByte2ToBuf((*it).second);
  return sendPacketTcp(&sending);
}


/**
   This requests the data from the server, but only once... this is
   useful for things like video data that you don't want to clog up
   the bandwidth with... its also useful for things like sending
   commands

   @param name the name of the data to request
   @param packet the packet that contains the data to use as argument
**/
AREXPORT bool ArClientBase::requestOnce(const char *name, ArNetPacket *packet)
{
  std::map<std::string, unsigned int>::iterator it;

  if ((it = myNameIntMap.find(name)) == myNameIntMap.end())
  {
    ArLog::log(ArLog::Normal, 
	       "Requesting data for \"%s\" but no data with that name exists",
	       name);
    return false;
  }
  ArLog::log(ArLog::Verbose, 
	     "Requesting data once for \"%s\"", name);
  if (packet != NULL)
  {
    packet->setCommand((*it).second);
    return sendPacketTcp(packet);
  }
  else
  {
    ArNetPacket tempPacket;
    tempPacket.setCommand((*it).second);
    return sendPacketTcp(&tempPacket);
  }
}

/**
   This requests the data from the server, but only once... this is
   useful for things like video data that you don't want to clog up
   the bandwidth with... its also useful for things like sending
   commands

   @param name the name of the data to request
   @param packet the packet that contains the data to use as argument
**/
AREXPORT bool ArClientBase::requestOnceUdp(const char *name, ArNetPacket *packet)
{
  std::map<std::string, unsigned int>::iterator it;

  if ((it = myNameIntMap.find(name)) == myNameIntMap.end())
  {
    ArLog::log(ArLog::Normal, 
	       "Requesting data for \"%s\" but no data with that name exists",
	       name);
    return false;
  }
  ArLog::log(ArLog::Verbose, 
	     "Requesting data once for \"%s\"", name);
  if (packet != NULL)
  {
    packet->setCommand((*it).second);
	if (!myTcpOnlyTo)
	  return sendPacketUdp(packet);
	else 
	  return sendPacketTcp(packet);
  }
  else
  {
    ArNetPacket tempPacket;
    tempPacket.setCommand((*it).second);
	if (!myTcpOnlyTo)
	  return sendPacketUdp(&tempPacket);
	else
      return sendPacketTcp(&tempPacket);
  }
}

/**
   This requests the data from the server, but only once... this is
   useful for things like video data that you don't want to clog up
   the bandwidth with... its also useful for things like sending
   commands

   @param name the name of the data to request
   @param str a string to send as the packet argument
**/
AREXPORT bool ArClientBase::requestOnceWithString(const char *name, 
						  const char *str)
{
  std::map<std::string, unsigned int>::iterator it;

  if ((it = myNameIntMap.find(name)) == myNameIntMap.end())
  {
    ArLog::log(ArLog::Normal, 
	       "Requesting data for \"%s\" but no data with that name exists",
	       name);
    return false;
  }
  ArLog::log(ArLog::Verbose, 
	     "Requesting data once for \"%s\"", name);
  
  ArNetPacket tempPacket;
  tempPacket.strToBuf(str);
  tempPacket.setCommand((*it).second);
  return sendPacketTcp(&tempPacket);
}

/**
   @param name the name to stop sending
**/
AREXPORT bool ArClientBase::dataExists(const char *name)
{
  if (myNameIntMap.find(name) != myNameIntMap.end())
    return true;
  else
    return false;
}

AREXPORT void ArClientBase::logDataList(void)
{
  std::map<unsigned int, ArClientData *>::iterator it;
  ArClientData *clientData;

  ArLog::log(ArLog::Terse, "");
  ArLog::log(ArLog::Terse, "Available data:");
  for (it = myIntDataMap.begin(); it != myIntDataMap.end(); it++)
  {
    clientData = (*it).second;
    ArLog::log(ArLog::Terse, "");
    ArLog::log(ArLog::Verbose, "Number %d, %d functors", 
	       clientData->getCommand(),
	       clientData->getFunctorList()->size());
    ArLog::log(ArLog::Terse, "Data: %s", clientData->getName());
    ArLog::log(ArLog::Terse, "\tDescription: %s",
	       clientData->getDescription());
    ArLog::log(ArLog::Terse, "\tArgument: %s",
	       clientData->getArgumentDescription());
    ArLog::log(ArLog::Terse, "\tReturn: %s",
	       clientData->getReturnDescription());

  }
}

/**
    @param functor functor created from ArFunctorC which refers to the 
    function to call.
    @param position whether to place the functor first or last
    @see remDisconnectOnErrorCB
 **/

AREXPORT void ArClientBase::addDisconnectOnErrorCB(ArFunctor *functor, 
						   ArListPos::Pos position)
{
  if (position == ArListPos::FIRST)
    myDisconnectOnErrorCBList.push_front(functor);
  else if (position == ArListPos::LAST)
    myDisconnectOnErrorCBList.push_back(functor);
  else
    ArLog::log(ArLog::Terse, 
	       "ArClientBase::addDisconnectOnErrorCB: Invalid position");
}

/** 
    @param functor the functor to remove from the list of connect callbacks
    @see addDisconnectOnErrorCB
**/
AREXPORT void ArClientBase::remDisconnectOnErrorCB(ArFunctor *functor)
{
  myDisconnectOnErrorCBList.remove(functor);
}

/**
    @param functor functor created from ArFunctorC which refers to the 
    function to call.
    @param position whether to place the functor first or last
    @see remServerShutdownCB
 **/

AREXPORT void ArClientBase::addServerShutdownCB(ArFunctor *functor, 
						   ArListPos::Pos position)
{
  if (position == ArListPos::FIRST)
    myServerShutdownCBList.push_front(functor);
  else if (position == ArListPos::LAST)
    myServerShutdownCBList.push_back(functor);
  else
    ArLog::log(ArLog::Terse, 
	       "ArClientBase::addServerShutdownCB: Invalid position");
}

/** 
    @param functor the functor to remove from the list of connect callbacks
    @see addServerShutdownCB
**/
AREXPORT void ArClientBase::remServerShutdownCB(ArFunctor *functor)
{
  myServerShutdownCBList.remove(functor);
}

AREXPORT void ArClientBase::setTcpOnlyFromServer(void)
{
  ArNetPacket packet;

  packet.setCommand(ArClientCommands::TCP_ONLY);
  sendPacketTcp(&packet);
  myTcpOnlyFrom = true;
}

AREXPORT void ArClientBase::setTcpOnlyToServer(void)
{
  myTcpOnlyTo = true;
}

AREXPORT ArTime ArClientBase::getLastPacketReceived(void) const
{
  return myLastPacketReceived;
}

AREXPORT const char *ArClientBase::getName(ArNetPacket *packet)
{
  return getName(packet->getCommand());
}

AREXPORT const char *ArClientBase::getName(unsigned int command)
{
  std::map<unsigned int, ArClientData *>::iterator it;  
  if ((it = myIntDataMap.find(command)) == myIntDataMap.end())
    return NULL;
  return (*it).second->getName();
}

AREXPORT void ArClientBase::setServerKey(const char *serverKey)
{
  myServerKey = serverKey;
  ArLog::log(ArLog::Normal, "New server key set");
}