// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/> // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. /* * Layer 4 and Service example, front-end server. * * This front-end server expects pings, and forward them to * the real ping server. When the ping server sends a pong back, * the front-end server forwards it to the client. * * Even if the connection to the ping server is broken, our * front-end server will keep storing the ping messages and * will forward them when the connection is restored. * * To run this program, ensure there is a file "frontend_service.cfg" * containing the location of the naming service (NSHost, NSPort) * in the working directory. The naming service must be running. * * DEPRECATED: You should use layer5 (take a look in the layer5 sample directory) * */ // We're using the NeL Service framework and layer 4. #include "nel/net/service.h" #include "nel/net/net_manager.h" using namespace NLNET; #include <deque> using namespace std; // Storage (a queue because the connection to the ping service is reliable, the order is preserved) deque< pair<TSockId,uint32> > ClientIds; /* * Callback function called when receiving a "PING" message * * Arguments: * - msgin: the incoming message (coming from a client) * - from: the "sockid" of the sender client * - frontendserver: the CCallbackNetBase object (which really is a CCallbackServer object, for a server) * * Input (expected message from a client): PING * - uint32: ping counter * * Output (sent message to the ping server): PONG * - uint32: ping counter */ void cbPing( CMessage& msgin, TSockId from, CCallbackNetBase& frontendserver ) { uint32 counter; // Input msgin.serial( counter ); ClientIds.push_back( make_pair( from, counter ) ); // store client sockid // Output CMessage msgout( "PING" ); msgout.serial( counter ); CNetManager::send( "PS", msgout ); // does not send if not connected nlinfo( "Received PING number %u from %s", counter, frontendserver.hostAddress(from).asString().c_str() ); } /* * Callback function called when receiving a "PONG" message * * Arguments: * - msgin: the incoming message (coming from the ping server) * - from: the "sockid" of the sender (usually useless for a CCallbackClient) * - clientofthepingserver: the CCallbackNetBase object (which really is a CCallbackClient object) * * Input (expected message from the ping server): PONG * - uint32: ping counter * - TSockId: "sock id" of the client who sent the ping * * Output (sent message to a client): PONG * - uint32: ping counter */ void cbPong( CMessage& msgin, TSockId from, CCallbackNetBase& clientofthepingserver ) { uint32 counter; TSockId clientfrom; // Input: process the reply of the ping service msgin.serial( counter ); // Do not send in case of double ping service reply (see onDisconnectPS()) // (does not work if two clients send the same counter at the same time) if ( ( !ClientIds.empty() ) && (ClientIds.front().second == counter) ) { clientfrom = ClientIds.front().first; // retrieve client sockid ClientIds.pop_front(); // Output: send the reply to the client CMessage msgout( "PONG" ); msgout.serial( counter ); CNetManager::send( "FS", msgout, clientfrom ); nlinfo( "Sent PONG number %u to %s", counter, clientfrom->asString().c_str() ); } } /* * Disonnection callback for the ping service */ void onDisconnectPS( const std::string &serviceName, TSockId from, void *arg ) { /* Note: the pings already forwarded should get no reply, but it may occur * (e.g. if the server reconnects before the forwarding of a PING and * the reconnection callbacks is called after that). Then onReconnectPS() * may send PINGs that have already been sent and the front-end may get * the same PONG twice. This is partially handled in cbPong. */ nlinfo( "Ping Service disconnecting: pongs will be delayed until reconnection" ); } /* * Connection callback for the ping service */ void onReconnectPS( const std::string &serviceName, TSockId from, void *arg ) { uint32 i; uint32 counter; // Output: forward all stored pings to the reconnected ping service for ( i=0; i!=ClientIds.size(); i++ ) { CMessage msgout( "PING" ); counter = ClientIds[i].second; msgout.serial( counter ); CNetManager::send( "PS", msgout ); } nlinfo( "Ping Service reconnected: %d pings forwarded", ClientIds.size() ); } /* * Disonnection callback for a client */ void onDisconnectClient( const std::string &serviceName, TSockId from, void *arg ) { // Erase all associated elements in the queue deque< pair<TSockId,uint32> >::iterator iq = ClientIds.begin(); while ( iq!=ClientIds.end() ) { if ( (*iq).first == from ) { iq = ClientIds.erase( iq ); } else { iq++; } } nlinfo( "A client has disconnected" ); } /* * Callback array for messages received from a client */ TCallbackItem CallbackArray[] = { { "PING", cbPing } // when receiving a "PING" message, call cbPing() }; /* * Callback array for message received from the ping service */ TCallbackItem PingServiceCallbackArray[] = { { "PONG", cbPong } // when receiving a "PONG" message, call cbPong() }; /* * CFrontEndService, based on IService */ class CFrontEndService : public IService { public: /* * Initialization */ void init() { // Connect to the ping service CNetManager::addClient( "PS" ); CNetManager::addCallbackArray( "PS", PingServiceCallbackArray, sizeof(PingServiceCallbackArray)/sizeof(PingServiceCallbackArray[0]) ); CNetManager::setConnectionCallback( "PS", onReconnectPS, NULL ); CNetManager::setDisconnectionCallback( "PS", onDisconnectPS, NULL ); CNetManager::setDisconnectionCallback( "FS", onDisconnectClient, NULL ); } }; /* * Declare a service with the class CFrontEndService, the names "FS" (short) and "frontend_service" (long). * The port is set to 37000 and the main callback array is CallbackArray. */ NLNET_OLD_SERVICE_MAIN( CFrontEndService, "FS", "frontend_service", 37000, CallbackArray, "", "" )