/*=============================================================================| | PROJECT SNAP7 1.3.0 | |==============================================================================| | Copyright (C) 2013, 2015 Davide Nardella | | All rights reserved. | |==============================================================================| | SNAP7 is free software: you can redistribute it and/or modify | | it under the terms of the Lesser GNU General Public License as published by | | the Free Software Foundation, either version 3 of the License, or | | (at your option) any later version. | | | | It means that you can distribute your commercial software linked with | | SNAP7 without the requirement to distribute the source code of your | | application and without the requirement that your application be itself | | distributed under LGPL. | | | | SNAP7 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 | | Lesser GNU General Public License for more details. | | | | You should have received a copy of the GNU General Public License and a | | copy of Lesser GNU General Public License along with Snap7. | | If not, see http://www.gnu.org/licenses/ | |=============================================================================*/ #include "snap_tcpsrvr.h" //--------------------------------------------------------------------------- // EVENTS QUEUE //--------------------------------------------------------------------------- TMsgEventQueue::TMsgEventQueue(const int Capacity, const int BlockSize) { FCapacity = Capacity; Max = FCapacity - 1; FBlockSize = BlockSize; Buffer = new byte[FCapacity * FBlockSize]; Flush(); } //--------------------------------------------------------------------------- TMsgEventQueue::~TMsgEventQueue() { delete[] Buffer; } //--------------------------------------------------------------------------- void TMsgEventQueue::Flush() { IndexIn = 0; IndexOut = 0; } //--------------------------------------------------------------------------- void TMsgEventQueue::Insert(void *lpdata) { pbyte PBlock; if (!Full()) { // Calc offset if (IndexIn < Max) IndexIn++; else IndexIn = 0; PBlock = Buffer + uintptr_t(IndexIn * FBlockSize); memcpy(PBlock, lpdata, FBlockSize); }; } //--------------------------------------------------------------------------- bool TMsgEventQueue::Extract(void *lpdata) { int IdxOut; pbyte PBlock; if (!Empty()) { // stores IndexOut IdxOut = IndexOut; if (IdxOut < Max) IdxOut++; else IdxOut = 0; PBlock = Buffer + uintptr_t(IdxOut * FBlockSize); // moves data memcpy(lpdata, PBlock, FBlockSize); // Updates IndexOut IndexOut = IdxOut; return true; } else return false; } //--------------------------------------------------------------------------- bool TMsgEventQueue::Empty() { return (IndexIn == IndexOut); } //--------------------------------------------------------------------------- bool TMsgEventQueue::Full() { int IdxOut = IndexOut; // To avoid troubles if IndexOut changes during next line return ( (IdxOut == IndexIn + 1) || ((IndexIn == Max) && (IdxOut == 0))); } //--------------------------------------------------------------------------- // WORKER THREAD //--------------------------------------------------------------------------- TMsgWorkerThread::TMsgWorkerThread(TMsgSocket *Socket, TCustomMsgServer *Server) { FreeOnTerminate = true; WorkerSocket = Socket; FServer = Server; } //--------------------------------------------------------------------------- void TMsgWorkerThread::Execute() { bool Exception = false; bool SelfClose = false; // Working loop while (!Terminated && !SelfClose && !Exception && !FServer->Destroying) { try { if (!WorkerSocket->Execute()) // False -> End of Activities SelfClose = true; } catch (...) { Exception = true; } }; if (!FServer->Destroying) { // Exception detected during Worker activity if (Exception) { WorkerSocket->ForceClose(); FServer->DoEvent(WorkerSocket->ClientHandle, evcClientException, 0, 0, 0, 0, 0); } else if (SelfClose) { FServer->DoEvent(WorkerSocket->ClientHandle, evcClientDisconnected, 0, 0, 0, 0, 0); } else FServer->DoEvent(WorkerSocket->ClientHandle, evcClientTerminated, 0, 0, 0, 0, 0); } delete WorkerSocket; // Delete reference from list FServer->Delete(Index); } //--------------------------------------------------------------------------- // LISTENER THREAD //--------------------------------------------------------------------------- TMsgListenerThread::TMsgListenerThread(TMsgSocket *Listener, TCustomMsgServer *Server) { FServer = Server; FListener = Listener; FreeOnTerminate = false; } //--------------------------------------------------------------------------- void TMsgListenerThread::Execute() { socket_t Sock; bool Valid; while (!Terminated) { if (FListener->CanRead(FListener->WorkInterval)) { Sock = FListener->SckAccept(); // in any case we must accept Valid = Sock != INVALID_SOCKET; // check if we are not destroying if ((!Terminated) && (!FServer->Destroying)) { if (Valid) FServer->Incoming(Sock); } else if (Valid) Msg_CloseSocket(Sock); }; } } //--------------------------------------------------------------------------- // TCP SERVER //--------------------------------------------------------------------------- TCustomMsgServer::TCustomMsgServer() { strcpy(FLocalAddress, "0.0.0.0"); CSList = new TSnapCriticalSection(); CSEvent = new TSnapCriticalSection(); FEventQueue = new TMsgEventQueue(MaxEvents, sizeof (TSrvEvent)); memset(Workers, 0, sizeof (Workers)); for (int i = 0; i < MaxWorkers; i++) Workers[i] = NULL; Status = SrvStopped; EventMask = 0xFFFFFFFF; LogMask = 0xFFFFFFFF; Destroying = false; FLastError = 0; ClientsCount = 0; LocalBind = 0; MaxClients = MaxWorkers; OnEvent = NULL; } //--------------------------------------------------------------------------- TCustomMsgServer::~TCustomMsgServer() { Destroying = true; Stop(); OnEvent = NULL; delete CSList; delete CSEvent; delete FEventQueue; } //--------------------------------------------------------------------------- void TCustomMsgServer::LockList() { CSList->Enter(); } //--------------------------------------------------------------------------- void TCustomMsgServer::UnlockList() { CSList->Leave(); } //--------------------------------------------------------------------------- int TCustomMsgServer::FirstFree() { int i; for (i = 0; i < MaxWorkers; i++) { if (Workers[i] == 0) return i; } return -1; } //--------------------------------------------------------------------------- int TCustomMsgServer::StartListener() { int Result; // Creates the listener SockListener = new TMsgSocket(); strncpy(SockListener->LocalAddress, FLocalAddress, 16); SockListener->LocalPort = LocalPort; // Binds Result = SockListener->SckBind(); if (Result == 0) { LocalBind = SockListener->LocalBind; // Listen Result = SockListener->SckListen(); if (Result == 0) { // Creates the Listener thread ServerThread = new TMsgListenerThread(SockListener, this); ServerThread->Start(); } else delete SockListener; } else delete SockListener; return Result; } //--------------------------------------------------------------------------- void TCustomMsgServer::TerminateAll() { int c; longword Elapsed; bool Timeout; if (ClientsCount > 0) { for (c = 0; c < MaxWorkers; c++) { if (Workers[c] != 0) PMsgWorkerThread(Workers[c])->Terminate(); } // Wait for closing Elapsed = SysGetTick(); Timeout = false; while (!Timeout && (ClientsCount > 0)) { Timeout = DeltaTime(Elapsed) > WkTimeout; if (!Timeout) SysSleep(100); }; if (ClientsCount > 0) KillAll(); // one o more threads are hanged ClientsCount = 0; } } //--------------------------------------------------------------------------- void TCustomMsgServer::KillAll() { int c, cnt = 0; LockList(); for (c = 0; c < MaxWorkers; c++) { if (Workers[c] != 0) try { PMsgWorkerThread(Workers[c])->Kill(); PMsgWorkerThread(Workers[c])->WorkerSocket->ForceClose(); delete PMsgWorkerThread(Workers[c]); Workers[c] = 0; cnt++; } catch (...) { }; } UnlockList(); DoEvent(0, evcClientsDropped, 0, cnt, 0, 0, 0); } //--------------------------------------------------------------------------- bool TCustomMsgServer::CanAccept(socket_t Socket) { return ((MaxClients == 0) || (ClientsCount < MaxClients)); } //--------------------------------------------------------------------------- PWorkerSocket TCustomMsgServer::CreateWorkerSocket(socket_t Sock) { PWorkerSocket Result; // Creates a funny default class : a tcp echo worker Result = new TEcoTcpWorker(); Result->SetSocket(Sock); return Result; } //--------------------------------------------------------------------------- void TCustomMsgServer::DoEvent(int Sender, longword Code, word RetCode, word Param1, word Param2, word Param3, word Param4) { TSrvEvent SrvEvent; bool GoLog = (Code & LogMask) != 0; bool GoEvent = (Code & EventMask) != 0; if (!Destroying && (GoLog || GoEvent)) { CSEvent->Enter(); time(&SrvEvent.EvtTime); SrvEvent.EvtSender = Sender; SrvEvent.EvtCode = Code; SrvEvent.EvtRetCode = RetCode; SrvEvent.EvtParam1 = Param1; SrvEvent.EvtParam2 = Param2; SrvEvent.EvtParam3 = Param3; SrvEvent.EvtParam4 = Param4; if (GoEvent && (OnEvent != NULL)) try { // callback is outside here, we have to shield it OnEvent(FUsrPtr, &SrvEvent, sizeof (TSrvEvent)); } catch (...) { }; if (GoLog) FEventQueue->Insert(&SrvEvent); CSEvent->Leave(); }; } //--------------------------------------------------------------------------- void TCustomMsgServer::Delete(int Index) { LockList(); Workers[Index] = 0; ClientsCount--; UnlockList(); } //--------------------------------------------------------------------------- void TCustomMsgServer::Incoming(socket_t Sock) { int idx; PWorkerSocket WorkerSocket; longword ClientHandle = Msg_GetSockAddr(Sock); if (CanAccept(Sock)) { LockList(); // First position available in the thread buffer idx = FirstFree(); if (idx >= 0) { // Creates the Worker and assigns it the connected socket WorkerSocket = CreateWorkerSocket(Sock); // Creates the Worker thread Workers[idx] = new TMsgWorkerThread(WorkerSocket, this); PMsgWorkerThread(Workers[idx])->Index = idx; // Update the number ClientsCount++; // And Starts the worker PMsgWorkerThread(Workers[idx])->Start(); DoEvent(WorkerSocket->ClientHandle, evcClientAdded, 0, 0, 0, 0, 0); } else { DoEvent(ClientHandle, evcClientNoRoom, 0, 0, 0, 0, 0); Msg_CloseSocket(Sock); } UnlockList(); } else { Msg_CloseSocket(Sock); DoEvent(ClientHandle, evcClientRejected, 0, 0, 0, 0, 0); }; } //--------------------------------------------------------------------------- int TCustomMsgServer::Start() { int Result = 0; if (Status != SrvRunning) { Result = StartListener(); if (Result != 0) { DoEvent(0, evcListenerCannotStart, Result, 0, 0, 0, 0); Status = SrvError; } else { DoEvent(0, evcServerStarted, SockListener->ClientHandle, LocalPort, 0, 0, 0); Status = SrvRunning; }; }; FLastError = Result; return Result; } //--------------------------------------------------------------------------- int TCustomMsgServer::StartTo(const char *Address, word Port) { strncpy(FLocalAddress, Address, 16); LocalPort = Port; return Start(); } //--------------------------------------------------------------------------- void TCustomMsgServer::Stop() { if (Status == SrvRunning) { // Kills the listener thread ServerThread->Terminate(); if (ServerThread->WaitFor(ThTimeout) != WAIT_OBJECT_0) ServerThread->Kill(); delete ServerThread; // Kills the listener delete SockListener; // Terminate all client threads TerminateAll(); Status = SrvStopped; LocalBind = 0; DoEvent(0, evcServerStopped, 0, 0, 0, 0, 0); }; FLastError = 0; } //--------------------------------------------------------------------------- int TCustomMsgServer::SetEventsCallBack(pfn_SrvCallBack PCallBack, void *UsrPtr) { OnEvent = PCallBack; FUsrPtr = UsrPtr; return 0; } //--------------------------------------------------------------------------- bool TCustomMsgServer::PickEvent(void *pEvent) { try { return FEventQueue->Extract(pEvent); } catch (...) { return false; }; } //--------------------------------------------------------------------------- bool TCustomMsgServer::EventEmpty() { return FEventQueue->Empty(); } //--------------------------------------------------------------------------- void TCustomMsgServer::EventsFlush() { CSEvent->Enter(); FEventQueue->Flush(); CSEvent->Leave(); } //---------------------------------------------------------------------------