#ifndef NLCLIENTBASE_H #define NLCLIENTBASE_H #include "Aria.h" #include "ArClientCommands.h" #include "ArServerCommands.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 receive 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. This class should be thread safe... The only thing you cannot do is call 'addHandler' or 'remHandler' from within a handler for that specific piece of data. **/ 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 }; enum { CLIENT_KEY_LENGTH = 16 }; enum NonBlockingConnectReturn { NON_BLOCKING_CONTINUE, ///< Client didn't connect or fail to connect yet NON_BLOCKING_CONNECTED, ///< Client connected NON_BLOCKING_FAILED ///< Client couldn't connect }; /// Constructor AREXPORT ArClientBase(); /// Destructor AREXPORT virtual ~ArClientBase(); /// Sets the optional name of the connected robot for debugging purposes. AREXPORT virtual void setRobotName(const char *name, bool debugLogging = false, int robotId = 0); /// Returns the optional name of the connected robot for debugging purposes. AREXPORT virtual const char *getRobotName() const; /// Returns the entire string used to prefix log entries for this client base. AREXPORT virtual const char *getLogPrefix() const; /// Returns if we're doing debug logging or not AREXPORT bool getDebugLogging(void); /// Connect to a server AREXPORT bool blockingConnect(const char *host, int port, bool log = true, const char *user = NULL, const char *password = NULL, const char *openOnIP = NULL); /// Disconnect from a server /** * This method blocks while the connection is being shutdown. **/ AREXPORT bool disconnect(void); /// Disconnect from a server next time through the loop /** * This method does not block while the connection is being * shutdown, and the disconnect only happens the next time the * client base is in its loop **/ AREXPORT void disconnectSoon(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; } /// Gets the state of the client AREXPORT ClientState getState(void) { return myState; } /// Adds a functor for some particular data AREXPORT bool addHandler(const char *name, ArFunctor1 *functor); /// Removes a functor for some particular data by name AREXPORT bool remHandler(const char *name, ArFunctor1 *functor); /// Request some data every @a 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, bool quiet = false); /// Request some data (or send a command) just once by UDP AREXPORT bool requestOnceUdp(const char *name, ArNetPacket *packet = NULL, bool quiet = false); /// 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); /// Gets the name of the host we tried to connect to AREXPORT const char *getHost(void); /// Sets the 'key' needed to connect to the server AREXPORT void setServerKey(const char *serverKey, bool log = true); /// Enforces the that the server is using this protocol version AREXPORT void enforceProtocolVersion(const char *protocolVersion, bool log = true); /// Enforces that the robots that connect are this type AREXPORT void enforceType(ArServerCommands::Type type, bool log = true); /// Gets the last time a packet was received AREXPORT ArTime getLastPacketReceived(void); /// Sets the backup timeout AREXPORT void setBackupTimeout(double timeoutInMins); /// Runs the client in this thread AREXPORT virtual void run(void); /// Runs the client in its own thread AREXPORT virtual void runAsync(void); /// Stops the thread and sets the stopped flag AREXPORT virtual void stopRunning(void); /// Returns whether the client has been stopped (regardless of whether its in its own thread) AREXPORT virtual bool isStopped(); /// 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); /// Logs the tracking information (packet and byte counts) AREXPORT void logTracking(bool terse); /// Clears the tracking information (resets counters) AREXPORT void resetTracking(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 wherever it came from AREXPORT void processPacket(ArNetPacket *packet, bool tcp); /// 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); /// Returns whether we only get TCP data from the server not UDP AREXPORT bool isTcpOnlyFromServer(void); /// Returns whether we only send TCP data to the server AREXPORT bool isTcpOnlyToServer(void); /// Gets a (probably) unique key that can be used to identify the client (after connection) AREXPORT void getClientKey(unsigned char key[CLIENT_KEY_LENGTH]); /// Starts the process of disconnecting from the server. /** * This method should be called in threads that should not block * (such as the main GUI thread). The call should be followed by * a delay of one second, followed by a call to * finishNonBlockingDisconnect(). **/ AREXPORT bool startNonBlockingDisconnect(); /// Finishes the process of disconnecting from the server. /** * This method should be used in conjunction with * startNonBlockingDisconnect in threads that should not block * (such as the main GUI thread). **/ AREXPORT void finishNonBlockingDisconnect(); /// Gets the name of the data a packet is for AREXPORT const char *getName(ArNetPacket *packet, bool internalCall = false); /// Gets the name of the data a command is AREXPORT const char *getName(unsigned int command, bool internalCall = false); // 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(); } /// Internal function that'll do the connection (mainly for switch connections) AREXPORT bool internalBlockingConnect( const char *host, int port, bool log, const char *user, const char *password, ArSocket *tcpSocket, const char *openOnIP = NULL); /// Internal function that'll start the non blocking connection (mainly for switch connections) AREXPORT NonBlockingConnectReturn internalNonBlockingConnectStart( const char *host, int port, bool log, const char *user, const char *password, ArSocket *tcpSocket, const char *openOnIP = NULL); /// Internal function that'll start the non blocking connection (mainly for switch connections) AREXPORT NonBlockingConnectReturn internalNonBlockingConnectContinue(void); /// Internal function to get the tcp socket AREXPORT ArSocket *getTcpSocket(void) { return &myTcpSocket; } /// Internal function to get the udp socket AREXPORT ArSocket *getUdpSocket(void) { return &myUdpSocket; } /// Internal function get get the data map AREXPORT const std::map *getDataMap(void) { return &myIntDataMap; } /// Returns the command number from the name AREXPORT unsigned int findCommandFromName(const char *name); /// Request some data every mSec milliseconds by command not name AREXPORT bool requestByCommand(unsigned int command, long mSec, ArNetPacket *packet = NULL); /// Don't want this data anymore, by command not name AREXPORT bool requestStopByCommand(unsigned int command); /// Request some data (or send a command) just once by command not name AREXPORT bool requestOnceByCommand(unsigned int command, ArNetPacket *packet = NULL); /// Request some data (or send a command) just once by command not name AREXPORT bool requestOnceByCommandUdp(unsigned int command, ArNetPacket *packet = NULL); /// Gets if we received the list of commands bool getReceivedDataList(void) { return myReceivedDataList; } /// Gets if we received the list of arguments and bool getReceivedArgRetList(void) { return myReceivedArgRetList; } /// Gets if we received the list of commands bool getReceivedGroupAndFlagsList(void) { return myReceivedGroupAndFlagsList; } /// Tells us the reason the connection was rejected (see ArServerCommands for details) int getRejected(void) { return myRejected; } /// Tells us the reason the connection was rejected (see ArServerCommands for details) const char *getRejectedString(void) { return myRejectedString; } protected: /// Optional robot name for logging std::string myRobotName; /// Optional prefix to be inserted at the start of log messages std::string myLogPrefix; /// If we're enforcing the version of the protocol or not std::string myEnforceProtocolVersion; /// if we're a particular type ArServerCommands::Type myEnforceType; AREXPORT bool setupPacket(ArNetPacket *packet); ArTime myLastPacketReceived; std::list myServerShutdownCBList; std::list myDisconnectOnErrorCBList; std::list myCycleCallbacks; void clear(void); // does the first part of connection bool internalConnect(const char *host, int port, bool obsolete, const char *openOnIP); void internalStartUdp(void); void buildList(ArNetPacket *packet); void internalSwitchState(ClientState state); bool myReceivedDataList; bool myReceivedArgRetList; bool myReceivedGroupAndFlagsList; 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 myIsRunningAsync; bool myDisconnectSoon; bool myIsStartedDisconnect; bool myIsStopped; bool myQuiet; bool myDebugLogging; ArLog::LogLevel myVerboseLogLevel; std::string myHost; std::string myUser; std::string myPassword; // the time we allow for connections int myTimeoutTime; // the time we started our connection ArTime myStartedConnection; ArTime myStartedUdpConnection; ArTime myStartedShutdown; enum NonBlockingConnectState { NON_BLOCKING_STATE_NONE, NON_BLOCKING_STATE_TCP, NON_BLOCKING_STATE_UDP }; NonBlockingConnectState myNonBlockingConnectState; ArMutex myDataMutex; ArMutex myClientMutex; ArMutex myMapsMutex; ArMutex myStateMutex; ArMutex myCallbackMutex; ArMutex myCycleCallbackMutex; ArMutex myPacketTrackingMutex; // our map of names to ints std::map myNameIntMap; // our map of ints to functors std::map 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; double myBackupTimeout; // this is a key we have for identifying ourselves moderately uniquely unsigned char myClientKey[16]; ArNetPacketSenderTcp myTcpSender; ArNetPacketReceiverTcp myTcpReceiver; ArNetPacketReceiverUdp myUdpReceiver; ArSocket myTcpSocket; ArSocket myUdpSocket; ArFunctor2C myProcessPacketCB; ArFunctor2C myProcessPacketUdpCB; int myRejected; char myRejectedString[32000]; ArTime myTrackingStarted; class Tracker { public: Tracker() { reset(); } virtual ~Tracker() {} void reset(void) { myPacketsTcp = 0; myBytesTcp = 0; myPacketsUdp = 0; myBytesUdp = 0; } long myPacketsTcp; long myBytesTcp; long myPacketsUdp; long myBytesUdp; }; AREXPORT void trackPacketSent(ArNetPacket *packet, bool tcp); AREXPORT void trackPacketReceived(ArNetPacket *packet, bool tcp); std::map myTrackingSentMap; std::map myTrackingReceivedMap; }; #endif // NLCLIENTBASE_H