ARIA-Users
Date Index
Thread Index
Re: [Aria-users] Using ArClientBase and ArServerBase with UDP protocol
- To: Marko Budisic <XXXXXXXX>
- Subject: Re: [Aria-users] Using ArClientBase and ArServerBase with UDP protocol
- From: Matt LaFary <XXXXXXXX>
- Date: Wed, 19 Apr 2006 12:53:35 -0400
- Cc: XXXXXXXX
- Content-Type: multipart/mixed;boundary="------------080902020803050806020101"
- Delivered-To: XXXXXXXX
- Delivered-To: XXXXXXXX
- In-Reply-To: <XXXXXXXX>
- Old-Return-Path: <XXXXXXXX>
- References: <XXXXXXXX> <XXXXXXXX> <XXXXXXXX>
- Resent-Date: Wed, 19 Apr 2006 12:49:38 -0400 (EDT)
- Resent-From: XXXXXXXX
- Resent-Message-ID: <XXXXXXXX>
- Resent-Reply-To: XXXXXXXX
- Resent-Sender: XXXXXXXX
- User-Agent: Thunderbird 1.5 (Windows/20051201)
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");
}