488 lines
15 KiB
C++
488 lines
15 KiB
C++
/*=============================================================================|
|
|
| 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_s(FLocalAddress,sizeof(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_s(SockListener->LocalAddress,16, 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_s(FLocalAddress,16, 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();
|
|
}
|
|
//---------------------------------------------------------------------------
|
|
|
|
|