#pragma warning (disable : 4786) #include #include #include #include "CommonClient.h" #include "CommonMessages.h" #include "Base/Platform.h" #include using namespace std; using namespace Base; const int CONNECTION_ATTEMPT_TIMEOUT = 4; // connection timeout in seconds const int CONNECTION_DELAY = 5; // pause between connection attempts in seconds const int KEEP_ALIVE_DELAY = 15; // connection keep-alive timeout in seconds const int PORT_ALIVE_DELAY = 0; // port-alive delay in milliseconds const int NO_DATA_TIMEOUT = 0; // no-data timeout in seconds const int UNACKNOWLEDGED_DATA_TIMEOUT = 15; // unacknowledged-data timeout in seconds struct RandFunction : std::unary_function { int operator()(int n) { return rand()%n; } }; //////////////////////////////////////////////////////////////////////////////// CConnectionHandler::CConnectionHandler(CConnectionManager & parent, UdpConnection * connection, unsigned index) : mParent(parent), mConnection(connection), mIndex(index) { if (mConnection) mConnection->SetHandler(this); } CConnectionHandler::~CConnectionHandler() { if (mConnection) { mConnection->SetHandler(NULL); mConnection->Disconnect(); mConnection->Release(); } } void CConnectionHandler::Send(const Base::ByteStream & stream) { if (mConnection && mConnection->GetStatus() == UdpConnection::cStatusConnected) { mConnection->Send(cUdpChannelReliable1, stream.getBuffer(), stream.getSize()); } } void CConnectionHandler::OnConnectComplete(UdpConnection *) { if (mConnection->GetStatus() == UdpConnection::cStatusConnected) { Message::Connect message; message.SetVersion(mParent.GetCore().GetVersion()); message.SetDescription(mParent.GetCore().GetDescription()); Base::ByteStream stream; message.pack(stream); Send(stream); } else { OnTerminated(mConnection); } } void CConnectionHandler::OnTerminated(UdpConnection *) { mParent.OnConnectionClosed(mIndex); if (mConnection) { mConnection->SetHandler(NULL); mConnection->Release(); mConnection = 0; } } void CConnectionHandler::OnRoutePacket(UdpConnection *, const uchar *data, int dataLen) { unsigned short itemCount; unsigned short messageId = 0; unsigned trackingNumber = 0; if (dataLen < 8) return; Base::ByteStream stream(data,dataLen); Base::ByteStream::ReadIterator streamIterator = stream.begin(); Base::get(streamIterator, itemCount); Base::get(streamIterator, messageId); Base::get(streamIterator, trackingNumber); if (messageId == Message::MESSAGE_CONNECT_REPLY) { streamIterator = stream.begin(); Message::ConnectReply message(streamIterator); if (message.GetResult() == RESULT_SUCCESS) mParent.OnConnectionOpened(mIndex); else OnTerminated(mConnection); } mParent.GetCore().QueueTrackedCallback(stream); } //////////////////////////////////////////////////////////////////////////////// CConnectionManager::CConnectionManager(apiCore & parent, std::vector & serverList, unsigned maxConnections) : mParent(parent), mServerArray(), mServerCount(0), mMaxConnections(maxConnections), mParams(), mUdpManager(0), mConnectionArray(), mConnectedSet(), mPendingSet(), mDisconnectedQueue(), mSendQueue(), mConnectionCount(0), mActiveCount(0), mConnectionDelay(0) { for (unsigned serverIndex = 0; serverIndex < serverList.size(); serverIndex++) { std::string address = serverList[serverIndex]; unsigned portIndex = address.find(':'); if (portIndex != string::npos) { std::string host = address.substr(0, portIndex); std::string port = address.substr(portIndex+1); if (!host.length() || !port.length()) continue; portIndex = port.find('-'); if (portIndex == string::npos) { if (atoi(port.c_str()) == 0) continue; mServerArray.push_back(address); mServerCount++; } else { int first = atoi(port.substr(0,portIndex).c_str()); int last = atoi(port.substr(portIndex+1).c_str()); if (!first || !last) continue; char buffer[64]; for (int rangeIndex=first; rangeIndex<=last; rangeIndex++) { sprintf(buffer,"%s:%d",host.c_str(),rangeIndex); mServerArray.push_back(std::string(buffer)); mServerCount++; } } } } if (mMaxConnections > mServerCount) mMaxConnections = mServerCount; if (mMaxConnections == 0) mMaxConnections = 1; //mParams.handler = this; mParams.keepAliveDelay = parent.GetKeepAliveDelay() * 1000; mParams.portAliveDelay = parent.GetPortAliveDelay(); mParams.incomingBufferSize = 256 * 1024; mParams.outgoingBufferSize = 256 * 1024; mParams.maxConnections = mMaxConnections; mParams.hashTableSize = mMaxConnections*10; mParams.port = 0; mUdpManager = new UdpManager(&mParams); srand((int)Base::getTimer()+(int)time(0)); RandFunction myRand; random_shuffle(mServerArray.begin(), mServerArray.end(), myRand); mConnectionArray.resize(mServerCount,0); for (unsigned i=0; iRelease(); } void CConnectionManager::Process() { mUdpManager->GiveTime(); //////////////////////////////////////// // try a new connection if: // 1. active connection count (pending + connected) < max connections // 2. (and) the set of disconnect servers is not empty // 3. (and) connection delay has expired or there are no active connections if (mActiveCount < mMaxConnections && !mDisconnectedQueue.empty()) { if (!mConnectionCount || mConnectionDelay < (unsigned)time(0)) { Connect(); } } for (unsigned i=0; imConnection) { std::set::iterator iterator; // remove from connected set if ((iterator = mConnectedSet.find(i)) != mConnectedSet.end()) { mConnectedSet.erase(iterator); mActiveCount--; mConnectionCount--; } // remove from pending set if ((iterator = mPendingSet.find(i)) != mPendingSet.end()) { mPendingSet.erase(iterator); mActiveCount--; } // add to disconnected set mDisconnectedQueue.push_back(i); delete handler; mConnectionArray[i] = 0; } else { UdpConnection::ChannelStatus cs; handler->mConnection->GetChannelStatus(cUdpChannelReliable1,&cs); if (handler->mConnection->GetStatus() == UdpConnection::cStatusDisconnected || cs.oldestUnacknowledgedAge > mParent.GetUnacknowledgedDataTimeout()*1000) // 30 seconds, fairly liberal, odds are the connection is dead after 10 seconds { handler->OnTerminated(handler->mConnection); } } } } void CConnectionManager::Connect() { unsigned index = mDisconnectedQueue.front(); mDisconnectedQueue.pop_front(); std::string address; int port; if (!ParseAddress(mServerArray[index],address,port)) { mDisconnectedQueue.push_back(index); return; } UdpConnection * connection = mUdpManager->EstablishConnection(address.c_str(), port, CONNECTION_ATTEMPT_TIMEOUT*1000); if (connection) { mConnectionArray[index] = new CConnectionHandler(*this, connection, index); mPendingSet.insert(index); mActiveCount++; } else { mDisconnectedQueue.push_back(index); mParent.OnConnectionFailed(mServerArray[index],mConnectionCount); } } void CConnectionManager::Send(const Base::ByteStream & stream) { if (CanSend()) { unsigned index; CConnectionHandler * handler = 0; while (!handler && !mSendQueue.empty()) { index = mSendQueue.front(); mSendQueue.pop_front(); handler = mConnectionArray[index]; } if (handler) { mSendQueue.push_back(index); handler->Send(stream); } } } bool CConnectionManager::CanSend() { return (mConnectionCount > 0); } void CConnectionManager::SetConnectionDelay() { mConnectionDelay = (int)time(0) + CONNECTION_DELAY; } void CConnectionManager::OnConnectionOpened(unsigned index) { mPendingSet.erase(index); mConnectedSet.insert(index); mSendQueue.push_back(index); mConnectionCount++; mParent.OnConnectionOpened(mServerArray[index],mConnectionCount); } void CConnectionManager::OnConnectionClosed(unsigned index) { if (mPendingSet.find(index) != mPendingSet.end()) { mParent.OnConnectionFailed(mServerArray[index],mConnectionCount ? mConnectionCount-1 : mConnectionCount); } else { mSendQueue.remove(index); mParent.OnConnectionClosed(mServerArray[index],mConnectionCount); } } bool CConnectionManager::ParseAddress(const std::string & full, std::string & address, int & port) { unsigned index = full.find(':'); if (full.length() == 0 || index == string::npos) return false; address = full.substr(0,index); port = atoi(full.substr(index+1).c_str()); return true; } //////////////////////////////////////////////////////////////////////////////// apiClient::apiClient() : mClientCore(0) { } apiClient::~apiClient() { } void apiClient::Process() { if (mClientCore) static_cast(mClientCore)->Process(); } void apiClient::OnConnectionOpened(const char *, unsigned) { } void apiClient::OnConnectionClosed(const char *, unsigned) { } void apiClient::OnConnectionFailed(const char *, unsigned) { } //////////////////////////////////////////////////////////////////////////////// apiCore::apiCore(apiClient * parent, const char * version, const char * serverList, const char * description, unsigned maxConnections) : mParent(parent), mVersion(version), mDescription(description), mServerArray(), mMaxConnections(maxConnections), mConnectionManager(0), mTrackingIndex(1), mTrackedMessages(), mRequestMap(), mCallbackQueue(), mTrackedCallbackQueue(), mTimeoutQueue(), mRequestQueueSize(0), mExpirationTimeout(0), mInCallback(false) { std::string list = serverList; unsigned offset=0; unsigned index=0; while ((index = list.find(' ',offset)) != std::string::npos) { mServerArray.push_back(list.substr(offset, index-offset)); offset = index + 1; } mServerArray.push_back(list.substr(offset)); if (mDescription.length() > 64) mDescription = mDescription.substr(0,64); } apiCore::apiCore(apiClient * parent, const char * version, const char ** serverList, unsigned serverCount, const char * description, unsigned maxConnections) : mParent(parent), mVersion(version), mDescription(description), mServerArray(), mMaxConnections(maxConnections), mConnectionManager(0), mTrackingIndex(1), mTrackedMessages(), mRequestMap(), mCallbackQueue(), mTrackedCallbackQueue(), mTimeoutQueue(), mRequestQueueSize(0), mExpirationTimeout(0), mInCallback(false) { for (unsigned i=0; i 64) mDescription = mDescription.substr(0,64); } apiCore::~apiCore() { delete mConnectionManager; } int apiCore::GetKeepAliveDelay() { return KEEP_ALIVE_DELAY; } int apiCore::GetPortAliveDelay() { return PORT_ALIVE_DELAY; } int apiCore::GetNoDataTimeout() { return NO_DATA_TIMEOUT; } int apiCore::GetUnacknowledgedDataTimeout() { return UNACKNOWLEDGED_DATA_TIMEOUT; } void apiCore::NotifyConnectionOpened(const char *address, unsigned connectionCount) { } void apiCore::NotifyConnectionClosed(const char *address, unsigned connectionCount) { } void apiCore::NotifyConnectionFailed(const char *address, unsigned connectionCount) { } void apiCore::OnConnectionOpened(const std::string & address, unsigned connectionCount) { NotifyConnectionOpened(address.c_str(),connectionCount); mParent->OnConnectionOpened(address.c_str(),connectionCount); } void apiCore::OnConnectionClosed(const std::string & address, unsigned connectionCount) { NotifyConnectionClosed(address.c_str(),connectionCount); mParent->OnConnectionClosed(address.c_str(),connectionCount); } void apiCore::OnConnectionFailed(const std::string & address, unsigned connectionCount) { NotifyConnectionFailed(address.c_str(),connectionCount); mParent->OnConnectionFailed(address.c_str(),connectionCount); } bool apiCore::IsTrackedMessage(const unsigned short messageId) { return mTrackedMessages.find(messageId) != mTrackedMessages.end(); } void apiCore::RegisterTrackedMessage(const unsigned short messageId) { mTrackedMessages.insert(messageId); } //////////////////////////////////////// // This function submits a request that expects no reply void apiCore::SubmitRequest(Message::Basic & input) { Base::ByteStream stream; input.pack(stream); if (mConnectionManager && mConnectionManager->CanSend()) mConnectionManager->Send(stream); } //////////////////////////////////////// // This function submits a request that expects a reply apiTrackingNumber apiCore::SubmitRequest(Message::Tracked & input, Message::TrackedReply & output, const void * userData, unsigned timeout) { //////////////////////////////////////// // Set tracking number apiTrackingNumber trackingNumber = mTrackingIndex++; input.SetTrackingNumber(trackingNumber); output.SetTrackingNumber(trackingNumber); mRequestMap.insert(pair(trackingNumber, apiTrackedRequest(trackingNumber, output.GetMessageID(), userData, timeout))); //////////////////////////////////////// // Verify that the request needs to be sent. // If the default result is not RESULT_TIMEOUT, // trigger callback using the output if (!mConnectionManager || !mConnectionManager->CanSend() || output.GetResult() != RESULT_TIMEOUT) { QueueTrackedCallback(output); } else { Base::ByteStream stream; input.pack(stream); mConnectionManager->Send(stream); } return input.GetTrackingNumber(); } void apiCore::QueueCallback(const Base::ByteStream & stream) { mCallbackQueue.push_back(stream); } void apiCore::QueueCallback(const Message::Basic & message) { Base::ByteStream stream; message.pack(stream); mCallbackQueue.push_back(stream); } void apiCore::QueueTrackedCallback(const Base::ByteStream & stream) { mTrackedCallbackQueue.push_back(stream); } void apiCore::QueueTrackedCallback(const Message::Basic & message) { Base::ByteStream stream; message.pack(stream); mTrackedCallbackQueue.push_back(stream); } std::string & apiCore::GetVersion() { return mVersion; } std::string & apiCore::GetDescription() { return mDescription; } void apiCore::Process() { if (mInCallback) return; mInCallback = true; unsigned currentTime = (unsigned)time(0); //////////////////////////////////////// // Process connection manager if (mConnectionManager) mConnectionManager->Process(); else { mConnectionManager = new CConnectionManager(*this, mServerArray, mMaxConnections); } //////////////////////////////////////// // Check timeout in Request Map if (mExpirationTimeout <= currentTime) { map::iterator mapIterator = mRequestMap.begin(); while (mapIterator != mRequestMap.end()) { apiTrackedRequest & request = (*mapIterator).second; ++mapIterator; if (request.Expired()) { mTimeoutQueue.push_back(request.GetTrackingNumber()); } } mExpirationTimeout = currentTime + 3; } //////////////////////////////////////// // Process Timeout Queue while (!mTimeoutQueue.empty()) { apiTrackingNumber trackingNumber = mTimeoutQueue.front(); map::iterator mapIterator; mapIterator = mRequestMap.find(trackingNumber); if (mapIterator != mRequestMap.end()) { apiTrackedRequest & request = mapIterator->second; Timeout(request.GetMessageId(), trackingNumber, (void *)request.GetUserData()); mRequestMap.erase(mapIterator); } mTimeoutQueue.pop_front(); } /* //////////////////////////////////////// // Process Callback Queue while (!mCallbackQueue.empty()) { Base::ByteStream & stream = mCallbackQueue.front(); Callback(stream,0); mCallbackQueue.pop_front(); } */ //////////////////////////////////////// // Process Tracked Callback Queue while (!mTrackedCallbackQueue.empty()) { unsigned short itemCount; unsigned short messageId = 0; Base::ByteStream & stream = mTrackedCallbackQueue.front(); Base::ByteStream::ReadIterator streamIterator = stream.begin(); Base::get(streamIterator, itemCount); Base::get(streamIterator, messageId); if (!IsTrackedMessage(messageId)) { Callback(stream,0); } else { streamIterator = stream.begin(); Message::Tracked message(streamIterator); map::iterator mapIterator = mRequestMap.find(message.GetTrackingNumber()); if (mapIterator != mRequestMap.end()) { apiTrackedRequest & request = (*mapIterator).second; Callback(stream,(void *)request.GetUserData()); mRequestMap.erase(mapIterator); } } mTrackedCallbackQueue.pop_front(); } mInCallback = false; } //////////////////////////////////////////////////////////////////////////////// const int DEFAULT_TIMEOUT = 20; apiTrackedRequest::apiTrackedRequest(apiTrackingNumber trackingNumber, const unsigned short messageId, const void * userData, unsigned duration) : mTrackingNumber(trackingNumber), mMessageId(messageId), mUserData(userData), mTimeout(0), mDuration(duration) { if (mDuration == 0) mDuration = DEFAULT_TIMEOUT; mTimeout = (int)time(0) + mDuration; } apiTrackedRequest::apiTrackedRequest(const apiTrackedRequest & copy) : mTrackingNumber(copy.mTrackingNumber), mMessageId(copy.mMessageId), mUserData(copy.mUserData), mTimeout(copy.mTimeout), mDuration(copy.mDuration) { } apiTrackedRequest::~apiTrackedRequest() { } apiTrackingNumber apiTrackedRequest::GetTrackingNumber() { return mTrackingNumber; } unsigned short apiTrackedRequest::GetMessageId() { return mMessageId; } const void * apiTrackedRequest::GetUserData() { return mUserData; } bool apiTrackedRequest::Expired() { return mTimeout < (unsigned)time(0); } ////////////////////////////////////////////////////////////////////////////////