Added VChatAPI library

This commit is contained in:
Anonymous
2014-01-17 05:18:10 -07:00
parent 4fae2dfe0f
commit 4ba1363775
97 changed files with 29311 additions and 62 deletions
@@ -0,0 +1,119 @@
#include "Clock.h"
#include <time.h>
#ifdef WIN32
#include <winsock.h>
#else //WIN32
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#endif
#ifdef EXTERNAL_DISTRO
namespace NAMESPACE
{
#endif
Clock::Clock()
: m_lastStart(0),
m_totalRunTime(0)
{
}
ClockStamp Clock::getCurTime()
{
#if defined(WIN32)
static int sClockHigh = 0;
static ClockStamp sClockLast = 0;
int high = sClockHigh;
DWORD low = GetTickCount();
ClockStamp holdLast = sClockLast; // this should be interlocked too
ClockStamp ret = ((ClockStamp)high << 32) | low;
// crazy trick to allow threading to work, by putting in a 1000 second fudge factor, we effective say
// that it is ok to time-slice us at a bad point and we will still handle it, provided that our thread
// gets processing time again within 1000 seconds
if (ret < holdLast - 1000000)
{
sClockHigh = high + 1;
ret = ((ClockStamp)high << 32) | low;
}
sClockLast = ret; // this really should be interlocked to be totally safe since it is a 64 bit value, but I don't see a way to do that and am not sure it would mess up anything but the one call anyways
return ret;
#else
struct timeval tv;
int err;
err = gettimeofday(&tv, NULL);
return (static_cast<ClockStamp>(tv.tv_sec) * 1000 + static_cast<ClockStamp>(tv.tv_usec / 1000));
#endif
}
ClockStamp Clock::getElapsedSinceLastStart()
{
if (m_lastStart == 0)
{
//hasn't been started
return 0;
}
ClockStamp elapsed = getCurTime() - m_lastStart;
if (elapsed > 2000000000) // only time differences up to 23 days can be measured with this function
elapsed = 2000000000;
return elapsed;
}
void Clock::start()
{
if (m_lastStart != 0)
{
//already started
return;
}
//set last start to curtime
m_lastStart = getCurTime();
}
void Clock::stop()
{
if (m_lastStart == 0)
{
//need to start before stoping
return;
}
m_totalRunTime += (unsigned)getElapsedSinceLastStart(); //rlsmith - explicit cast to prevent compiler warning
m_lastStart = 0;
}
bool Clock::isDone(unsigned runTime)
{
if (m_lastStart == 0)
{
//never started, so say no
return false;
}
ClockStamp totalElapsed = getElapsedSinceLastStart() + m_totalRunTime;
return (totalElapsed >= runTime);
}
void Clock::reset()
{
m_lastStart = 0;
m_totalRunTime = 0;
}
#ifdef EXTERNAL_DISTRO
};
#endif
@@ -0,0 +1,71 @@
#ifndef CLOCK_H
#define CLOCK_H
#ifdef EXTERNAL_DISTRO
namespace NAMESPACE
{
#endif
#if defined(WIN32)
typedef __int64 ClockStamp;
#else
typedef long long ClockStamp;
#endif
/**
* @brief A Clock can be used as a millisecond timer.
*/
class Clock
{
public:
/**
* @brief Creates a clock, must still be started with Clock::start method.
*
* Once created, a clock can be started, and stoped as often as possible.
*/
Clock();
/**
* @brief Starts the timer running.
*/
void start();
/**
* @brief Stops the timer from running (note: can still be started again later).
*/
void stop();
/**
* @brief Tells you if the timer has been in the started state for longer than runTime.
*
* @param runTime The amount of time to test if this timer has ran longer than.
*
* @return 'true' if timer has ran for longer than or equal to runTime, false otherwise.
*/
bool isDone(unsigned runTime);
/**
* @brief Resets this clock (as if it were never started).
*/
void reset();
private:
ClockStamp m_lastStart;
unsigned m_totalRunTime;
ClockStamp getCurTime();
ClockStamp getElapsedSinceLastStart();
};
#ifdef EXTERNAL_DISTRO
};
#endif
#endif //CLOCK_H
@@ -0,0 +1,33 @@
#include "IPAddress.h"
#if defined(WIN32)
#include <winsock2.h>
typedef int socklen_t;
#else // for non-windows platforms (linux)
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#endif
#ifdef EXTERNAL_DISTRO
namespace NAMESPACE
{
#endif
IPAddress::IPAddress(unsigned int ip)
: m_IP(ip)
{
}
char *IPAddress::GetAddress(char *buffer) const
{
struct sockaddr_in addr;
addr.sin_addr.s_addr = m_IP;
strcpy(buffer, inet_ntoa(addr.sin_addr));
return(buffer);
}
#ifdef EXTERNAL_DISTRO
};
#endif
@@ -0,0 +1,51 @@
#ifndef TCPIPADDRESS_H
#define TCPIPADDRESS_H
#ifdef EXTERNAL_DISTRO
namespace NAMESPACE
{
#endif
/**
* @brief Container object for IP Address.
*/
class IPAddress
{
public:
/**
* @brief Constructor, sets the ip address if specified.
*/
IPAddress(unsigned int ip = 0);
/**
* @brief Sets the ip address.
*/
void SetAddress(unsigned int ip){ m_IP = ip; }
/**
* @brief Returns the unsigned int representation of this address.
*/
unsigned int GetAddress() const { return m_IP; }
/**
* @brief Used to retreive the the dot-notation represenatatiion of this address.
*
* @param buffer A pointer to the buffer to place the ip address into.
* Must be at least 17 characters long, will be null terminated.
*
* @return A pointer to the buffer the address was placed into.
*/
char *GetAddress(char *buffer) const;
private:
unsigned int m_IP;
};
#ifdef EXTERNAL_DISTRO
};
#endif
#endif //TCPIPADDRESS_H
@@ -0,0 +1,94 @@
#include "TcpBlockAllocator.h"
#ifdef EXTERNAL_DISTRO
namespace NAMESPACE
{
#endif
TcpBlockAllocator::TcpBlockAllocator(const unsigned initSize, const unsigned initCount)
: m_freeHead(NULL), m_blockCount(initCount), m_blockSize(initSize), m_numAvailBlocks(0)
{
realloc();
}
TcpBlockAllocator::~TcpBlockAllocator()
{
while(m_freeHead)
{
data_block *tmp = m_freeHead;
m_freeHead = m_freeHead->m_next;
delete[] tmp->m_data;
delete tmp;m_numAvailBlocks--;
}
}
data_block *TcpBlockAllocator::getBlock()
{
data_block *tmp;
if(!m_freeHead)
{
realloc();
}
tmp = m_freeHead;
m_freeHead = m_freeHead->m_next;
tmp->m_next = NULL;
m_numAvailBlocks--;
return(tmp);
}
void TcpBlockAllocator::returnBlock(data_block *b)
{
b->m_usedSize = 0;
b->m_sentSize = 0;
if (m_numAvailBlocks >= m_blockCount)
{
delete[] b->m_data;
delete b;
return;
}
b->m_next = m_freeHead;
m_freeHead = b; m_numAvailBlocks++;
}
void TcpBlockAllocator::realloc()
{
data_block *tmp = NULL, *cursor = NULL;
tmp = new data_block; m_numAvailBlocks++;
cursor = tmp;
memset(cursor, 0, sizeof(data_block));
cursor->m_data = new char[m_blockSize];
cursor->m_totalSize = m_blockSize;
for(unsigned i = 1; i < m_blockCount; i++)
{
cursor->m_next = new data_block; m_numAvailBlocks++;
cursor = cursor->m_next;
memset(cursor, 0, sizeof(data_block));
cursor->m_data = new char[m_blockSize];
cursor->m_totalSize = m_blockSize;
}
if(m_freeHead)
{
cursor->m_next = m_freeHead;
m_freeHead = tmp;
}
else
{
m_freeHead = tmp;
}
}
#ifdef EXTERNAL_DISTRO
};
#endif
@@ -0,0 +1,44 @@
#ifndef TCPBLOCKALLOCATOR_H
#define TCPBLOCKALLOCATOR_H
#include <string.h>
#ifdef EXTERNAL_DISTRO
namespace NAMESPACE
{
#endif
struct data_block
{
unsigned m_usedSize;
unsigned m_sentSize;
unsigned m_totalSize;
char *m_data;
data_block *m_next;
};
class TcpBlockAllocator
{
public:
TcpBlockAllocator(const unsigned initSize, const unsigned initCount);
~TcpBlockAllocator();
data_block *getBlock();
void returnBlock(data_block *);
private:
void realloc();
data_block *m_freeHead;
unsigned m_blockCount;
unsigned m_blockSize;
unsigned m_numAvailBlocks;
};
#ifdef EXTERNAL_DISTRO
};
#endif
#endif //TCPBLOCKALLOCATOR_H
@@ -0,0 +1,792 @@
#include "TcpConnection.h"
#include "TcpManager.h"
#include "Clock.h"
#include <errno.h>
#ifdef EXTERNAL_DISTRO
namespace NAMESPACE
{
#endif
//used when want to open new connection with this socket
TcpConnection::TcpConnection(TcpManager *tcpManager, TcpBlockAllocator *sendAlloc, TcpManager::TcpParams &params, const IPAddress &destIP, unsigned short destPort, unsigned timeout)
: m_nextConnection(NULL),
m_prevConnection(NULL),
m_socket(INVALID_SOCKET),
m_nextKeepAliveConnection(NULL),
m_prevKeepAliveConnection(NULL),
m_aliveListId(tcpManager->m_aliveList.m_listID),
m_nextRecvDataConnection(NULL),
m_prevRecvDataConnection(NULL),
m_recvDataListId(tcpManager->m_dataList.m_listID),
m_manager(tcpManager),
m_status(StatusNegotiating),
m_handler(NULL),
m_destIP(destIP),
m_destPort(destPort),
m_refCount(0),
m_sendAllocator(sendAlloc),
m_head(NULL),
m_tail(NULL),
m_bytesRead(0),
m_bytesNeeded(0),
m_params(params),
m_recvBuff(NULL),
m_connectTimeout(timeout),
m_connectTimer(),
m_wasConRemovedFromMgr(false),
m_connectionRefused(false)
{
//start connection timer
m_connectTimer.start();
memset(&m_addr, 0, sizeof(m_addr));
if (m_params.maxRecvMessageSize != 0)
{
m_recvBuff = new char[m_params.maxRecvMessageSize];
}
m_socket = socket(AF_INET, SOCK_STREAM, 0);
setOptions();
m_addr.sin_family = AF_INET;
m_addr.sin_port = htons(m_destPort);
m_addr.sin_addr.s_addr = m_destIP.GetAddress();
int err = connect(m_socket, (sockaddr *)&m_addr, sizeof(m_addr));
if(err == SOCKET_ERROR)
{
#ifdef WIN32
int sockerr = WSAGetLastError();
if(sockerr != WSAEWOULDBLOCK)
{
//a real error
m_status = StatusDisconnected;
}
else
{
m_status = StatusNegotiating;
}
#else // UNIX
if (errno != EINPROGRESS)
{
m_status = StatusDisconnected;
}
else
{
m_status = StatusNegotiating;
}
#endif
}
else
{
//we are connected, wow
m_status = StatusConnected;
}
}
//used when server mode creates new connection object representing a connect request
TcpConnection::TcpConnection(TcpManager *tcpManager, TcpBlockAllocator *sendAlloc, TcpManager::TcpParams &params, SOCKET socket, const IPAddress &destIP, unsigned short destPort)
: m_nextConnection(NULL),
m_prevConnection(NULL),
m_socket(socket),
m_nextKeepAliveConnection(NULL),
m_prevKeepAliveConnection(NULL),
m_aliveListId(tcpManager->m_aliveList.m_listID),
m_nextRecvDataConnection(NULL),
m_prevRecvDataConnection(NULL),
m_recvDataListId(tcpManager->m_dataList.m_listID),
m_manager(tcpManager),
m_status(StatusConnected),
m_handler(NULL),
m_destIP(destIP),
m_destPort(destPort),
m_refCount(0),
m_sendAllocator(sendAlloc),
m_head(NULL),
m_tail(NULL),
m_bytesRead(0),
m_bytesNeeded(0),
m_params(params),
m_recvBuff(NULL),
m_connectTimeout(0),
m_connectTimer(),
m_wasConRemovedFromMgr(false)
{
memset(&m_addr, 0, sizeof(m_addr));
if (m_params.maxRecvMessageSize != 0)
{
m_recvBuff = new char[m_params.maxRecvMessageSize];
}
setOptions();
}
void TcpConnection::setOptions()
{
if (m_socket != INVALID_SOCKET)
{
#if defined(WIN32)
unsigned long isNonBlocking = 1;
int outBufSize = m_params.outgoingBufferSize;
int inBufSize = m_params.incomingBufferSize;
int keepAlive = 1;
int reuseAddr = 1;
struct linger ld;
ld.l_onoff = 0;
ld.l_linger = 0;
if (ioctlsocket(m_socket, FIONBIO, &isNonBlocking) != 0
|| setsockopt(m_socket, SOL_SOCKET, SO_SNDBUF, (char *)&outBufSize, sizeof(outBufSize)) != 0
|| setsockopt(m_socket, SOL_SOCKET, SO_RCVBUF, (char *)&inBufSize, sizeof(inBufSize)) != 0
|| setsockopt(m_socket, SOL_SOCKET, SO_KEEPALIVE, (char *)&keepAlive, sizeof(keepAlive)) != 0
|| setsockopt(m_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&reuseAddr, sizeof(reuseAddr)) != 0
|| setsockopt(m_socket, SOL_SOCKET, SO_LINGER, (char *)&ld, sizeof(ld)) != 0 )
{
//bummer, but no need to crash now.... ?
}
#else // linux is to remain the default compile mode
unsigned long isNonBlocking = 1;
unsigned long keepAlive = 1;
unsigned long outBufSize = m_params.outgoingBufferSize;
unsigned long inBufSize = m_params.incomingBufferSize;
unsigned long reuseAddr = 1;
struct linger ld;
ld.l_onoff = 0;
ld.l_linger = 0;
if (ioctl(m_socket, FIONBIO, &isNonBlocking) != 0
|| setsockopt(m_socket, SOL_SOCKET, SO_SNDBUF, &outBufSize, sizeof(outBufSize)) != 0
|| setsockopt(m_socket, SOL_SOCKET, SO_RCVBUF, &inBufSize, sizeof(inBufSize)) != 0
|| setsockopt(m_socket, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(keepAlive)) != 0
|| setsockopt(m_socket, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr)) != 0
|| setsockopt(m_socket, SOL_SOCKET, SO_LINGER, &ld, sizeof(ld)) != 0)
{
//bummer, but no need to crash now.... ?
}
#endif
}
}
int TcpConnection::finishConnect()
{
AddRef();
int returnVal = 0;
m_connectionRefused = false;
/**< returns < 0 if fatal error and connect will not work, =0 if need more time, >0 if connect completed */
switch (m_status)
{
case StatusDisconnected:
{
//something went wrong
Disconnect(false);
returnVal = -1;
}
break;
case StatusNegotiating:
{
#ifdef WIN32
//try to finish connection
fd_set wrSet;
FD_ZERO(&wrSet);
FD_SET(m_socket, &wrSet);
timeval t;
t.tv_sec = 0;
t.tv_usec = 0;
int err = select(m_socket + 1, NULL, &wrSet, NULL, &t);
if (err == 0)
{
//needs more time
returnVal = 0;
}
else if (err == SOCKET_ERROR)
{
//huhoh, let's hope it needs more time
int sockerr = WSAGetLastError();
if (sockerr == WSAEINPROGRESS
|| sockerr == WSAEWOULDBLOCK
|| sockerr == WSAEALREADY
|| sockerr == WSAEINVAL)
{
//yep
returnVal = 0;
}
else
{
Disconnect(false);
returnVal = -1;
}
}
else
{
//check if write bit set for socket
if (FD_ISSET(m_socket, &wrSet))
{
//connection complete
m_status = StatusConnected;
returnVal = 1;
}
else
{
//give it more time??
returnVal = 0;
}
}
#else // not WIN32
int err = connect(m_socket, (sockaddr *)&m_addr, sizeof(m_addr));
if(err == SOCKET_ERROR)
{
m_connectionRefused = (errno == ECONNREFUSED);
if (errno != EINPROGRESS && errno != EALREADY)
{
/* if (errno == ECONNREFUSED) */ Disconnect(true);
/* else Disconnect(false); -- Don't do this here; bad! */
returnVal = -1;//failure
}
else
{
returnVal = 0;//need to wait
}
}
else
{
m_status = StatusConnected;
returnVal = 1;//connect success
}
#endif
}
break;
case StatusConnected:
{
//wierd, shouldn't be trying to do this here
Disconnect(true);
returnVal = -1;
}
break;
}
if (returnVal == 0 && m_connectTimeout != 0 && m_connectTimer.isDone(m_connectTimeout))
{
Disconnect(true);
returnVal = -1;
}
else if (returnVal ==1/* && m_connectTimeout != 0*/)
{
//need to give, onConnect callback
if (m_handler)
m_handler->OnConnectRequest(this);
}
Release();
return returnVal;
}
TcpConnection::~TcpConnection()
{
if (m_recvBuff != NULL)
{
delete [] m_recvBuff;
}
while(m_head != NULL)
{
data_block *tmp = m_head;
m_head = m_head->m_next;
m_sendAllocator->returnBlock(tmp);
}
//TODO: need to notify app if are currently connected
}
void TcpConnection::Send(const char *data, unsigned int dataLen)
{
//add msg to buf
int totalLen = dataLen + sizeof(int);
if(m_status == StatusDisconnected)
{
return;
}
if (m_params.keepAliveDelay > 0 && m_aliveListId == m_manager->m_aliveList.m_listID)
{
m_aliveListId = m_manager->m_keepAliveList.m_listID;
if (m_prevKeepAliveConnection != NULL)
m_prevKeepAliveConnection->m_nextKeepAliveConnection = m_nextKeepAliveConnection;
if (m_nextKeepAliveConnection != NULL)
m_nextKeepAliveConnection->m_prevKeepAliveConnection = m_prevKeepAliveConnection;
if (m_manager->m_keepAliveList.m_beginList == this)
m_manager->m_keepAliveList.m_beginList = m_nextKeepAliveConnection;
m_nextKeepAliveConnection = m_manager->m_aliveList.m_beginList;
m_prevKeepAliveConnection = NULL;
if (m_manager->m_aliveList.m_beginList != NULL)
m_manager->m_aliveList.m_beginList->m_prevKeepAliveConnection = this;
m_manager->m_aliveList.m_beginList = this;
}
data_block *work = NULL;
// this connection has no send buffer. Get a block
if(!m_tail)
{
m_head = m_sendAllocator->getBlock();
m_tail = m_head;
}
work = m_tail;
//send message len first
unsigned nLen = htonl(totalLen);
unsigned lenLength = sizeof(int);
unsigned lenIndex = 0;
while(lenIndex < lenLength)
{
if ((lenLength - lenIndex) <= (work->m_totalSize - work->m_usedSize))
{
//size will fit in this block
memcpy(work->m_data + work->m_usedSize, (char *)(&nLen) + lenIndex, lenLength - lenIndex);
work->m_usedSize += (lenLength - lenIndex);
lenIndex += (lenLength - lenIndex);
}
else
{
//size will not fit in this block
memcpy(work->m_data + work->m_usedSize, (char *)(&nLen) + lenIndex, work->m_totalSize - work->m_usedSize);
lenIndex += work->m_totalSize - work->m_usedSize;
work->m_usedSize += work->m_totalSize - work->m_usedSize;
work->m_next = m_sendAllocator->getBlock();
work = work->m_next;
m_tail = work;
}
}
//now send message payload
unsigned messageIndex = 0;
while(messageIndex < dataLen)
{
if((dataLen - messageIndex) <= (work->m_totalSize - work->m_usedSize))
{
// data will fit in this block
memcpy(work->m_data + work->m_usedSize, data + messageIndex, (dataLen - messageIndex));
work->m_usedSize += (dataLen - messageIndex);
messageIndex += (dataLen - messageIndex);
}
else
{
// data will not fit in this block. Fill this block and get another block
memcpy(work->m_data + work->m_usedSize, data + messageIndex, work->m_totalSize - work->m_usedSize);
messageIndex += work->m_totalSize - work->m_usedSize;
work->m_usedSize += work->m_totalSize - work->m_usedSize;
work->m_next = m_sendAllocator->getBlock();
work = work->m_next;
m_tail = work;
}
}
return;
}
void TcpConnection::Disconnect(bool notifyApplication)
{
AddRef();
m_status = StatusDisconnected;
if (!m_wasConRemovedFromMgr)
{
m_manager->removeConnection(this);
m_wasConRemovedFromMgr = true;
}
if(m_socket != INVALID_SOCKET)
{
#if defined(WIN32)
closesocket(m_socket);
#else
close(m_socket);
#endif
m_socket = INVALID_SOCKET;
}
if (notifyApplication && m_handler)
m_handler->OnTerminated(this);
Release();
}
void TcpConnection::AddRef()
{
m_refCount++;
}
void TcpConnection::Release()
{
if (--m_refCount == 0)
{
//make sure manager knows I'm gone
if (m_status != StatusDisconnected)
{
m_refCount = 1;
Disconnect(false);
m_refCount = 0;
}
delete this;
}
}
int TcpConnection::processIncoming()
{
/**< returns < 0 if fatal error and socket has been closed,
=0 if read anything (full or partial message),
>0 if nothing to read now, or would block so shouldn't try again immediately. */
if (m_status != StatusConnected)
{
//wait until connect succeeds
return 1;
}
if (m_params.noDataTimeout > 0 && m_recvDataListId == m_manager->m_dataList.m_listID)
{
m_recvDataListId = m_manager->m_noDataList.m_listID;
if (m_prevRecvDataConnection != NULL)
m_prevRecvDataConnection->m_nextRecvDataConnection = m_nextRecvDataConnection;
if (m_nextRecvDataConnection != NULL)
m_nextRecvDataConnection->m_prevRecvDataConnection = m_prevRecvDataConnection;
if (m_manager->m_noDataList.m_beginList == this)
m_manager->m_noDataList.m_beginList = m_nextRecvDataConnection;
m_nextRecvDataConnection = m_manager->m_dataList.m_beginList;
m_prevRecvDataConnection = NULL;
if (m_manager->m_dataList.m_beginList != NULL)
m_manager->m_dataList.m_beginList->m_prevRecvDataConnection = this;
m_manager->m_dataList.m_beginList = this;
}
int newMsg = 0;
if (m_bytesRead < sizeof(int))
{
//new msg
newMsg = 1;
//printf("socket: %d\n", m_socket);
int ret = recv(m_socket, ((char *)(&m_bytesNeeded) + m_bytesRead),
4 - m_bytesRead, 0);
//fprintf(stderr, "READ: %d\n", ret);
if (ret == 0)
{
//We did a select, so there should be data. Socket was closed.
Disconnect();
return -1;
}
else if (ret == -1)
{
if (translateRecvSocketEror())
{
//fatal error
return -1;
}
else
{
//need to wait
return 1;
}
}
else
{
m_bytesRead += ret;
if (m_bytesRead < 4)
{
return 1;//need to wait
}
else
{
m_bytesNeeded = ntohl(m_bytesNeeded);
//printf("m_bytesNeeded = %i\n", m_bytesNeeded);
if (m_bytesNeeded == sizeof(int))
{
//keepalive, ignore
m_bytesRead = 0;
m_bytesNeeded = 0;
return 0;
}
else if (m_bytesNeeded < sizeof(int))
{
//major protocol violation
Disconnect();
return -1;
}
else if (m_params.maxRecvMessageSize == 0)
{
if (m_recvBuff!=NULL)
delete [] m_recvBuff;
m_recvBuff = new char[m_bytesNeeded-4];
}
else if (m_params.maxRecvMessageSize != 0 && (m_bytesNeeded-4) > m_params.maxRecvMessageSize)
{
//error, maxRecvMeessageSize exceeded, Disconnect
Disconnect();
return -1;
}
}
}
}
int msgBytesRead = m_bytesRead - 4;
int msgBytesNeeded = m_bytesNeeded - 4;
int ret = recv(m_socket, (char *)(m_recvBuff + msgBytesRead),
msgBytesNeeded - msgBytesRead, 0);
if (ret == 0 && !newMsg)
{
//We did a select, so there should be data. Socket was closed.
Disconnect();
return -1;
}
if (ret == -1)
{
if (translateRecvSocketEror())
{
//fatal error
return -1;
}
else
{
//need to wait
return 1;
}
} else
{
m_bytesRead += ret;
}
if (m_bytesRead == m_bytesNeeded)
{
m_bytesRead = 0;
m_bytesNeeded = 0;
if (m_handler)
{
AddRef();//could get deleted during this callback
m_handler->OnRoutePacket(this, (unsigned char *)m_recvBuff, msgBytesNeeded);
if (m_status == StatusDisconnected)
{
Release();
return -1;
}
Release();
}
//entire message received
return 0;
}
else
{
return 1;//couldn't get entire msg
}
}
int TcpConnection::processOutgoing()
{
/**< returns < 0 if fatal error and socket has been closed,
=0 if sent data, call again immediately if want to,
>0 may have sent data, but calling again would do no good because there is either no more data to send, or would block. */
if (m_status != StatusConnected)
{
//wait until connect succeeds
return 0;
}
int sendError = 1;
// If m_head is not null, then this connection has something to send
if(m_head)
{
int amt = ::send(m_socket, m_head->m_data + m_head->m_sentSize, m_head->m_usedSize - m_head->m_sentSize, 0);
if(amt < 0)
{
#ifdef WIN32
switch(WSAGetLastError())
{
case WSAEWOULDBLOCK:
case WSAEINTR:
case WSAEINPROGRESS:
case WSAEALREADY:
case WSA_IO_PENDING:
case WSA_NOT_ENOUGH_MEMORY:
case WSATRY_AGAIN:
//try again
sendError = 1;
break;
default:
//assume broken, Disconnect
Disconnect();
sendError = -1;
break;
}
#else //not WIN32
// error condition, EAGAIN is recoverable, otherwise raise an error condition. Break from loop
switch(errno)
{
case EAGAIN:
//try again
sendError = 1;
break;
default:
//assume broken, Disconnect
Disconnect();
sendError = -1;
break;
}
#endif
}
else if(static_cast<unsigned>(amt) < (m_head->m_usedSize - m_head->m_sentSize))
{
// partial send: trying to do anything more now would be a waste of time. Break from loop
m_head->m_sentSize += amt;
sendError = 1;
}
else if(amt == 0)
{
Disconnect();
sendError = -1;
// client closed connection
}
else
{
// everything was sent from this block. Return it to the pool, advance m_head. Attempt to continue
// sending
data_block *tmp = m_head;
if(m_tail == m_head)
{
m_tail = m_tail->m_next;
m_head = m_head->m_next;
}
else
{
m_head = m_head->m_next;
}
m_sendAllocator->returnBlock(tmp);
sendError = 0;
}
}
return sendError;
}
bool TcpConnection::translateRecvSocketEror()
{
/**< returns false if fatal error and socket has been closed,
true if should try again. */
bool fatalError=false;
#ifdef WIN32
int lastErr = WSAGetLastError();
switch(lastErr)
{
case WSAENOBUFS:
case WSAEINPROGRESS:
case WSAEINTR:
case WSAEWOULDBLOCK:
case WSABASEERR:
fatalError=false;
break;
case WSANOTINITIALISED:
case WSAENETDOWN:
case WSAEFAULT:
case WSAENOTCONN:
case WSAENETRESET:
case WSAENOTSOCK:
case WSAEOPNOTSUPP:
case WSAESHUTDOWN:
case WSAEMSGSIZE:
case WSAEINVAL:
case WSAECONNABORTED:
case WSAETIMEDOUT:
case WSAECONNRESET:
default:
//fatal
fatalError=true;
Disconnect();
break;
}
#else //not WIN32
switch(errno)
{
case EWOULDBLOCK:
case EINTR:
case ETIMEDOUT:
case ENOBUFS:
//try later
fatalError=false;
break;
case EBADF:
case ECONNRESET:
case EFAULT:
case EINVAL:
case ENOTCONN:
case ENOTSOCK:
case EOPNOTSUPP:
case EPIPE:
case EIO:
case ENOMEM:
case ENOSR:
default:
//fatal
fatalError=true;
Disconnect();
break;
}
#endif
return fatalError;
}
#ifdef EXTERNAL_DISTRO
};
#endif
@@ -0,0 +1,160 @@
#ifndef TCPCONNECTION_H
#define TCPCONNECTION_H
#include "TcpHandlers.h"
#include "TcpManager.h"
#include "IPAddress.h"
#include "TcpBlockAllocator.h"
#include "Clock.h"
#if defined(WIN32)
#include <winsock2.h>
typedef int socklen_t;
#else // for non-windows platforms (linux)
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#ifdef EXTERNAL_DISTRO
namespace NAMESPACE
{
#endif
/**
* @brief Manages a single connection.
*/
class TcpConnection
{
public:
/**
* @brief The connection status.
*/
enum Status {
StatusNegotiating, /**< Currently attempting to connect. */
StatusConnected, /**< Currently connected. */
StatusDisconnected /**< Currently disconnected. */
};
/**
* @brief Sets the handler object which will receive callback methods.
*
* To have the TcpConnection call your object directly when packets are received, and when the
* connection is disconnected, you simply need to derive your class
* (multiply if necessary) from TcpConnectionHandler, then you can use
* this method to set the object the TcpConnection will call as appropriate.
* default = NULL (no callbacks made)
*
* @param handler The object which will be called for notifications.
*/
void SetHandler(TcpConnectionHandler *handler){ m_handler = handler; }
/**
* @brief Returns the handler associated with this object.
*/
TcpConnectionHandler *GetHandler(){ return m_handler; }
/**
* @brief Returns the current status of this connection.
*/
Status GetStatus(){ return m_status; }
/**
* @brief Queues a message to be sent on this connection.
*/
void Send(const char *data, unsigned dataLen);
/**
* @brief Disconnects and recycles the socket.
*
* @param notifyApplication primarily used internally, but when set to 'true', it will cause the application
* to be called back via the onTerminated handler due to this call (the callback will not occur if the connection was
* already disconnected)
*/
void Disconnect(bool notifyApplication=true);
/**
* @brief Returns the ip on the other side of this connection.
*/
IPAddress GetDestinationIp(){ return m_destIP; }
/**
* @brief Returns the port on the other side of this conection.
*/
unsigned short GetDestinationPort(){ return m_destPort; }
/**
* @brief Standard AddRef/Release scheme
*/
void AddRef();
/**
* @brief Standard AddRef/Release scheme
*/
void Release();
bool wasRemovedFromMgr() { return m_wasConRemovedFromMgr; }
void setRemovedFromMgr() { m_wasConRemovedFromMgr = true; }
bool isConnectionRefused() const { return m_connectionRefused; }
protected:
friend class TcpManager;
TcpConnection(TcpManager *tcpManager, TcpBlockAllocator *sendAlloc, TcpManager::TcpParams &params, const IPAddress &destIP, unsigned short destPort, unsigned timeout);
int finishConnect();/**< returns < 0 if fatal error and connect will not work, =0 if need more time, >0 if connect completed */
TcpConnection(TcpManager *tcpManager, TcpBlockAllocator *sendAlloc, TcpManager::TcpParams &params, SOCKET socket, const IPAddress &destIP, unsigned short destPort);
TcpConnection *m_nextConnection; /**< Double linked list imp. */
TcpConnection *m_prevConnection; /**< Double linked list imp. */
SOCKET m_socket;
int processOutgoing();/**< returns < 0 if fatal error and socket has been closed, =0 if sent data, call again immediately if want to, >0 may have sent data, but calling again would do no good because there is either no more data to send, or would block. */
int processIncoming();/**< returns < 0 if fatal error and socket has been closed, =0 if read anything (full or partial message), >0 if nothing to read now, or would block so shouldn't try again immediately. */
TcpConnection *m_nextKeepAliveConnection; /**< Double linked list imp. */
TcpConnection *m_prevKeepAliveConnection; /**< Double linked list imp. */
int m_aliveListId;
TcpConnection *m_nextRecvDataConnection;
TcpConnection *m_prevRecvDataConnection;
int m_recvDataListId;
private:
~TcpConnection();
void setOptions();
TcpManager *m_manager;
bool translateRecvSocketEror();
Status m_status;
TcpConnectionHandler *m_handler;
IPAddress m_destIP;
unsigned short m_destPort;
unsigned m_refCount;
TcpBlockAllocator *m_sendAllocator;
data_block *m_head;
data_block *m_tail;
unsigned m_bytesRead;
unsigned m_bytesNeeded;
TcpManager::TcpParams m_params;
char *m_recvBuff;
sockaddr_in m_addr;
unsigned m_connectTimeout;
Clock m_connectTimer;
bool m_connectionRefused;
bool m_wasConRemovedFromMgr;
};
#ifdef EXTERNAL_DISTRO
};
#endif
#endif //TCPCONNECTION_H
@@ -0,0 +1,52 @@
#ifndef TCPHANDLERS_H
#define TCPHANDLERS_H
#ifdef EXTERNAL_DISTRO
namespace NAMESPACE
{
#endif
class TcpConnection;
/**
* @brief Interface used by TcpManager class for notification to application of connection state/etc.
*
* Note: these callbacks will only be made when during a call to TcpManager::giveTime.
*/
class TcpManagerHandler
{
public:
/**
* @brief Callback made when a new connection has been established by the manager.
*/
virtual void OnConnectRequest(TcpConnection *con)=0;
};
/**
* @brief Interface used by TcpConnection class for notification to application of connection state/etc.
*
* Note: these callbacks will only be made when during a call to TcpManager::giveTime.
*/
class TcpConnectionHandler
{
public:
/**
* @brief Callback made when a new message has been received on the specified connection.
*/
virtual void OnRoutePacket(TcpConnection *con, const unsigned char *data, int dataLen)=0;
/**
* @brief Callback made when the specified connection has closed, or been closed.
*/
virtual void OnTerminated(TcpConnection *con)=0;
virtual void OnConnectRequest(TcpConnection *con)=0;
};
#ifdef EXTERNAL_DISTRO
};
#endif
#endif //TCPHANDLERS_H
@@ -0,0 +1,435 @@
//This code is not used by anything intentionally
//The Connection class was getting mistakenly linked in
//instead of the SWG Connection class that is used for
//all of our connections. Removing the whole thing
//to prevent the issue from coming up again.
#if 0
#ifdef WIN32
#pragma warning (disable: 4786)
#endif
#include <stdio.h>
#include "TcpConnection.h"
#include "TcpListener.h"
#ifdef WIN32
#define snprintf _snprintf
#endif
#ifdef EXTERNAL_DISTRO
namespace NAMESPACE
{
#endif
////////////////////////////////////////////////////////////////////////////////
Listener::QueueNode::QueueNode() :
connection(0),
request(0)
{
}
Listener::QueueNode::QueueNode(Connection * con, RequestBase * req) :
connection(con),
request(req)
{
}
Listener::Listener() :
mParams(),
mTcpManager(0),
mConnections(),
mConnectionCount(0),
mClosedConnections(),
mQueuedRequests(),
mActiveRequests(),
mActiveCount(0),
mActiveMax(0),
mAcceptingNewConnections(true)
{
//printf("ctor 0x%x Listener\n",this);
}
Listener::~Listener()
{
std::set<Connection *>::iterator iterator;
for (iterator = mConnections.begin(); iterator != mConnections.end(); iterator++)
{
Connection * connection = *iterator;
delete connection;
}
mConnections.clear();
if (mTcpManager != 0)
{
mTcpManager->Release();
mTcpManager = 0;
}
//printf("dtor 0x%x Listener\n",this);
}
void Listener::RequestSleep(RequestBase * request)
{
mSleepingRequests.insert(request);
}
void Listener::RequestWake(RequestBase * request)
{
if (mSleepingRequests.erase(request))
{
QueueNode node(request->mConnection, request);
mActiveRequests.push_front(node);
}
}
void Listener::OnConnectRequest(TcpConnection * connection)
{
if (IsAcceptingNewConnections())
{
Connection * connectionObject = new Connection(*this, connection);
mConnections.insert(connectionObject);
mConnectionCount++;
OnConnectionOpened(connectionObject);
}
}
void Listener::QueueRequest(Connection * connection, RequestBase * request)
{
if (connection)
{
// normal request, internal requests have no connection
connection->NotifyQueuedRequest(request);
}
mQueuedRequests.push_back(QueueNode(connection,request));
}
bool Listener::IsIdle() const
{
return (!IsActive() && !mTcpManager && mQueuedRequests.empty() && !mActiveCount);
}
bool Listener::IsAcceptingNewConnections() const
{
return (IsActive() && mAcceptingNewConnections);
}
unsigned Listener::Process()
{
//Profile profile("Listener::Process");
////////////////////////////////////////
// handle inactive state (with UdpManager)
if (!IsActive() && mTcpManager)
{
// check all connections to see if they are idle
std::set<Connection *>::iterator iterator;
for (iterator = mConnections.begin(); iterator != mConnections.end(); iterator++)
{
Connection * connection = *iterator;
if (connection->IsConnected())
connection->Disconnect();
}
// close the UdpManager if all the connections are closed
if (!mConnectionCount)
{
mTcpManager->Release();
mTcpManager = 0;
OnShutdown();
}
}
////////////////////////////////////////
// handle active state (without UdpManager)
else if (IsActive() && !mTcpManager)
{
mParams = GetConnectionParams();
mActiveMax = GetActiveRequestMax();
mTcpManager = new TcpManager(mParams);
mTcpManager->SetHandler(this);
if (mTcpManager->BindAsServer()){
OnStartup();
}else{
OnFailedStartup();
return 0;
}
}
////////////////////////////////////////
// process the TcpManager
if (mTcpManager)
{
//Profile subProfile("TcpManager::GiveTime()");
mTcpManager->GiveTime();
}
// check all closed connections to see if they are idle
std::list<Connection *>::iterator closedIterator = mClosedConnections.begin();
while (closedIterator != mClosedConnections.end())
{
//Profile profile("Listener::Process (cleanup connection)");
std::list<Connection *>::iterator current = closedIterator++;
Connection * connection = *current;
if (!connection->GetActiveRequests() &&
!connection->GetQueuedRequests())
{
mClosedConnections.erase(current);
mConnections.erase(connection);
mConnectionCount--;
OnConnectionDestroyed(connection);
delete connection;
}
}
////////////////////////////////////////
// process request queue
while (!mQueuedRequests.empty() && (!mActiveMax || mActiveCount < mActiveMax))
{
//Profile profile("Listener::Process (activate queued request)");
QueueNode & node = mQueuedRequests.front();
if (!IsActive())
{
// If not active, discard queued request
if (node.connection)
{
// normal request, internal requests have no connection
node.connection->NotifyDiscardRequest(node.request);
}
DestroyRequest(node.request);
}
else
{
// Move request to active list
if (node.connection)
{
// normal request, internal requests have no connection
node.connection->NotifyBeginRequest(node.request);
}
mActiveRequests.push_back(node);
mActiveCount++;
}
mQueuedRequests.pop_front();
}
////////////////////////////////////////
// Process active requests
unsigned requestsProcessed = 0;
std::list<QueueNode>::iterator iterator = mActiveRequests.begin();
while (iterator != mActiveRequests.end())
{
//Profile profile("Listener::Process (process request)");
std::list<QueueNode>::iterator current = iterator++;
RequestBase * request = current->request;
Connection * connection = current->connection;
if (request->Process())
{
if (connection)
{
// normal request, internal requests have no connection
connection->NotifyEndRequest(request);
}
DestroyRequest(request);
mActiveRequests.erase(current);
mActiveCount--;
}
else if (mSleepingRequests.find(request) != mSleepingRequests.end())
{
mActiveRequests.erase(current);
}
requestsProcessed++;
}
return requestsProcessed;
}
/*
void Listener::GetStats(UdpManagerStatistics & statsStruct)
{
if (mTcpManager)
mTcpManager->GetStats(&statsStruct);
}
void Listener::ResetStats()
{
if (mTcpManager)
mTcpManager->ResetStats();
}
*/
void Listener::OnStartup()
{
}
void Listener::OnShutdown()
{
}
void Listener::OnFailedStartup()
{
}
void Listener::OnConnectionOpened(Connection * connection)
{
}
void Listener::OnConnectionClosed(Connection * connection, const char * reason)
{
}
void Listener::OnConnectionDestroyed(Connection * connection)
{
}
void Listener::OnCrcReject(Connection *connection, const unsigned char * buffer, unsigned size)
{
}
/*
void Listener::OnPacketCorrupt(Connection *con, const unsigned char *data, int dataLen, UdpCorruptionReason reason)
{
}
*/
////////////////////////////////////////////////////////////////////////////////
Connection::Connection(Listener & listener, TcpConnection * connection) :
mListener(listener),
mConnection(connection),
mHost(),
mHostIp(0),
mDisconnectReason(0),
mQueuedRequests(0),
mActiveRequests(0)
{
mConnection->AddRef();
mConnection->SetHandler(this);
char buffer[256];
char addr[32];
mHostIp = mConnection->GetDestinationIp().GetAddress();
mConnection->GetDestinationIp().GetAddress(addr);
snprintf(buffer, sizeof(buffer), "%s:%u", addr, mConnection->GetDestinationPort());
mHost = buffer;
//printf("ctor 0x%x connection\n",this);
}
Connection::~Connection()
{
Disconnect();
//printf("dtor 0x%x connection\n",this);
}
unsigned Connection::Send(const unsigned char * data, unsigned dataLen)
{
if (mConnection)
{
mConnection->Send((const char*)data, dataLen);
return dataLen;
}
return 0;
}
void Connection::Disconnect()
{
if (mConnection)
OnTerminated(mConnection);
}
bool Connection::IsConnected() const
{
return mConnection != 0;
}
const std::string & Connection::GetHost() const
{
return mHost;
}
const unsigned Connection::GetHostIP() const
{
return mHostIp;
}
unsigned Connection::GetQueuedRequests() const
{
return mQueuedRequests;
}
unsigned Connection::GetActiveRequests() const
{
return mActiveRequests;
}
void Connection::OnTerminated(TcpConnection *con)
{
mConnection->SetHandler(0);
mConnection->Disconnect();
//TcpConnection::DisconnectReason disconnectReason = mConnection->GetDisconnectReason();
mConnection->Release();
mConnection = 0;
mListener.mClosedConnections.push_back(this);
mListener.OnConnectionClosed(this, "Test"/*TcpConnection::DisconnectReasonText(disconnectReason)*/);
}
void Connection::OnRoutePacket(TcpConnection *, const unsigned char * data, int dataLen)
{
mListener.OnReceive(this, data, dataLen);
}
void Connection::OnCrcReject(TcpConnection *, const unsigned char * data, int dataLen)
{
mListener.OnCrcReject(this, data, dataLen);
}
/*
void Connection::OnPacketCorrupt(TcpConnection *, const uchar *data, int dataLen, UdpCorruptionReason reason)
{
mListener.OnPacketCorrupt(this, data, dataLen, reason);
}
*/
void Connection::NotifyQueuedRequest(RequestBase * request)
{
mQueuedRequests++;
}
void Connection::NotifyDiscardRequest(RequestBase * request)
{
mQueuedRequests--;
}
void Connection::NotifyBeginRequest(RequestBase * request)
{
mQueuedRequests--;
mActiveRequests++;
}
void Connection::NotifyEndRequest(RequestBase * request)
{
mActiveRequests--;
}
////////////////////////////////////////////////////////////////////////////////
RequestBase::RequestBase(Connection * connection, bool isInternal) :
mConnection(connection),
mProcessState(0),
mIsInternal(isInternal)
{
}
RequestBase::~RequestBase()
{
}
#ifdef EXTERNAL_DISTRO
}
#endif
#endif
@@ -0,0 +1,160 @@
//This code is not used by anything intentionally
//The Connection class was getting mistakenly linked in
//instead of the SWG Connection class that is used for
//all of our connections. Removing the whole thing
//to prevent the issue from coming up again.
#if 0
#ifndef TCP_LISTENER_H
#define TCP_LISTENER_H
#include <string>
#include <list>
#include <set>
#include "TcpManager.h"
#ifdef EXTERNAL_DISTRO
namespace NAMESPACE
{
#endif
////////////////////////////////////////////////////////////////////////////////
class RequestBase;
class Connection;
class Listener : public TcpManagerHandler
{
friend class Connection;
struct QueueNode
{
QueueNode();
QueueNode(Connection * connection, RequestBase * request);
bool operator<(const QueueNode & rhs) { return request < rhs.request; }
Connection * connection;
RequestBase * request;
};
public:
Listener();
virtual ~Listener();
void QueueRequest(Connection * connection, RequestBase * request);
bool IsIdle() const;
bool IsAcceptingNewConnections() const;
unsigned Process();
void SetAcceptingNewConnections(bool value) { mAcceptingNewConnections = value; }
//void GetStats(TcpManagerStatistics & statsStruct);
//void ResetStats();
unsigned GetNumberQueuedRequests() { return (unsigned)mQueuedRequests.size(); }
void RequestSleep(RequestBase * request);
void RequestWake(RequestBase * request);
virtual bool IsActive() const = 0;
virtual unsigned GetActiveRequestMax() = 0;
virtual TcpManager::TcpParams GetConnectionParams() = 0;
virtual void OnStartup();
virtual void OnShutdown();
virtual void OnFailedStartup();
virtual void OnConnectionOpened(Connection * connection);
virtual void OnConnectionClosed(Connection * connection, const char * reason);
virtual void OnConnectionDestroyed(Connection * connection);
virtual void OnReceive(Connection * connection, const unsigned char * data, unsigned dataLen) = 0;
virtual void OnCrcReject(Connection *connection, const unsigned char * data, unsigned dataLen);
//virtual void OnPacketCorrupt(Connection *con, const unsigned char *data, int dataLen, TcpCorruptionReason reason);
virtual void DestroyRequest(RequestBase * request) = 0;
virtual void OnConnectRequest(TcpConnection *con);
protected:
TcpManager::TcpParams mParams;
TcpManager * mTcpManager;
std::set<Connection *> mConnections;
unsigned mConnectionCount;
std::list<Connection *> mClosedConnections;
std::list<QueueNode> mQueuedRequests;
std::list<QueueNode> mActiveRequests;
std::set<RequestBase *> mSleepingRequests;
unsigned mActiveCount;
unsigned mActiveMax;
bool mAcceptingNewConnections;
};
////////////////////////////////////////////////////////////////////////////////
class Connection : public TcpConnectionHandler
{
friend class Listener;
public:
Connection(Listener & listener, TcpConnection * connection);
virtual ~Connection();
unsigned Send(const unsigned char * data, unsigned dataLen);
void Disconnect();
bool IsConnected() const;
const std::string & GetHost() const;
const unsigned GetHostIP() const;
unsigned GetQueuedRequests() const;
unsigned GetActiveRequests() const;
virtual void OnTerminated(TcpConnection *con);
virtual void OnConnectRequest(TcpConnection *con) {};
virtual void OnRoutePacket(TcpConnection *con, const unsigned char *data, int dataLen);
virtual void OnCrcReject(TcpConnection *con, const unsigned char *data, int dataLen);
//virtual void OnPacketCorrupt(TcpConnection *con, const unsigned char *data, int dataLen, TcpCorruptionReason reason);
protected:
void NotifyQueuedRequest(RequestBase * request);
void NotifyDiscardRequest(RequestBase * request);
void NotifyBeginRequest(RequestBase * request);
void NotifyEndRequest(RequestBase * request);
protected:
Listener & mListener;
TcpConnection * mConnection;
std::string mHost;
unsigned mHostIp;
const char * mDisconnectReason;
unsigned mQueuedRequests;
unsigned mActiveRequests;
};
////////////////////////////////////////////////////////////////////////////////
class RequestBase
{
friend class Listener;
public:
RequestBase(Connection * connection, bool isInternal=false);
virtual ~RequestBase();
virtual bool Process() = 0;
Connection * GetConnection() const { return mConnection; }
unsigned GetState() const { return mProcessState; }
protected:
Connection * mConnection;
unsigned mProcessState;
bool mIsInternal;
};
////////////////////////////////////////////////////////////////////////////////
#ifdef EXTERNAL_DISTRO
}
#endif
#endif
#endif
@@ -0,0 +1,761 @@
#include "TcpManager.h"
#include <assert.h>
#include "IPAddress.h"
#include "TcpConnection.h"
#include <time.h>
#ifndef WIN32
#include <sys/poll.h>
#endif
#ifdef EXTERNAL_DISTRO
namespace NAMESPACE
{
#endif
const time_t DNS_TIMEOUT = 60 * 5;
TcpManager::TcpParams::TcpParams()
: port(0),
/*
maxConnections(1000),
incomingBufferSize(6*1024),
outgoingBufferSize(64*1024),
allocatorBlockSize(8*1024),
allocatorBlockCount(16),
maxRecvMessageSize(0),
keepAliveDelay(0),
noDataTimeout(0)
*/
maxConnections(1000),
incomingBufferSize(512*1024),
outgoingBufferSize(512*1024),
allocatorBlockSize(8*1024),
allocatorBlockCount(1024),
maxRecvMessageSize(2048*1024),
keepAliveDelay(0),
noDataTimeout(0)
{
memset(bindAddress, 0, sizeof(bindAddress));
}
TcpManager::TcpParams::TcpParams(const TcpParams &cpy)
: port(cpy.port),
maxConnections(cpy.maxConnections),
incomingBufferSize(cpy.incomingBufferSize),
outgoingBufferSize(cpy.outgoingBufferSize),
allocatorBlockSize(cpy.allocatorBlockSize),
allocatorBlockCount(cpy.allocatorBlockCount),
maxRecvMessageSize(cpy.maxRecvMessageSize),
keepAliveDelay(cpy.keepAliveDelay),
noDataTimeout(cpy.noDataTimeout)
{
memset(bindAddress, 0, sizeof(bindAddress));
strncpy(bindAddress, cpy.bindAddress, sizeof(cpy.bindAddress));
}
TcpManager::TcpManager(const TcpParams &params)
: m_handler(NULL),
m_keepAliveList(NULL, 1),
m_aliveList(NULL, 2),
m_noDataList(NULL, 1),
m_dataList(NULL, 2),
m_params(params),
m_refCount(1),
m_connectionList(NULL),
m_connectionListCount(0),
m_socket(INVALID_SOCKET),
m_boundAsServer(false),
m_allocator(params.allocatorBlockSize, params.allocatorBlockCount),
m_keepAliveTimer(),
m_noDataTimer(),
m_dnsMap()
{
if (params.keepAliveDelay > 0)
m_keepAliveTimer.start();
if (params.noDataTimeout > 0)
m_noDataTimer.start();
#if defined(WIN32)
WSADATA wsaData;
WSAStartup(MAKEWORD(1,1), &wsaData);
FD_ZERO(&m_permfds);//select only used on win32
#endif
}
TcpManager::~TcpManager()
{
#if defined(WIN32)
WSACleanup();
#endif
if (m_boundAsServer)
{
#if defined(WIN32)
closesocket(m_socket);
#else
close(m_socket);
#endif
}
while (m_connectionList != NULL)
{
TcpConnection *con = m_connectionList;
con->AddRef();
removeConnection(con);
con->Release();
}
}
bool TcpManager::BindAsServer()
{
m_socket = socket(AF_INET, SOCK_STREAM, 0);
if (m_socket != INVALID_SOCKET)
{
#if defined(WIN32)
FD_SET(m_socket, &m_permfds);//the socket this server is listening on
unsigned long isNonBlocking = 1;
int outBufSize = m_params.outgoingBufferSize;
int inBufSize = m_params.incomingBufferSize;
int keepAlive = 1;
int reuseAddr = 1;
struct linger ld;
ld.l_onoff = 0;
ld.l_linger = 0;
if (ioctlsocket(m_socket, FIONBIO, &isNonBlocking) != 0
|| setsockopt(m_socket, SOL_SOCKET, SO_SNDBUF, (char *)&outBufSize, sizeof(outBufSize)) != 0
|| setsockopt(m_socket, SOL_SOCKET, SO_RCVBUF, (char *)&inBufSize, sizeof(inBufSize)) != 0
|| setsockopt(m_socket, SOL_SOCKET, SO_KEEPALIVE, (char *)&keepAlive, sizeof(keepAlive)) != 0
|| setsockopt(m_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&reuseAddr, sizeof(reuseAddr)) != 0
|| setsockopt(m_socket, SOL_SOCKET, SO_LINGER, (char *)&ld, sizeof(ld)) != 0 )
{
return false;
}
#else // linux is to remain the default compile mode
unsigned long isNonBlocking = 1;
unsigned long keepAlive = 1;
unsigned long outBufSize = m_params.outgoingBufferSize;
unsigned long inBufSize = m_params.incomingBufferSize;
unsigned long reuseAddr = 1;
struct linger ld;
ld.l_onoff = 0;
ld.l_linger = 0;
if (ioctl(m_socket, FIONBIO, &isNonBlocking) != 0
|| setsockopt(m_socket, SOL_SOCKET, SO_SNDBUF, &outBufSize, sizeof(outBufSize)) != 0
|| setsockopt(m_socket, SOL_SOCKET, SO_RCVBUF, &inBufSize, sizeof(inBufSize)) != 0
|| setsockopt(m_socket, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(keepAlive)) != 0
|| setsockopt(m_socket, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr)) != 0
|| setsockopt(m_socket, SOL_SOCKET, SO_LINGER, &ld, sizeof(ld)) != 0)
{
return false;
}
#endif
}
else
{
return false;
}
struct sockaddr_in addr_loc;
addr_loc.sin_family = AF_INET;
addr_loc.sin_port = htons(m_params.port);
addr_loc.sin_addr.s_addr = htonl(INADDR_ANY);
if (m_params.bindAddress[0] != 0)
{
unsigned long address = inet_addr(m_params.bindAddress);
if (address == INADDR_NONE)
{
struct hostent * lphp;
lphp = gethostbyname(m_params.bindAddress);
if (lphp != NULL)
addr_loc.sin_addr.s_addr = ((struct in_addr *)(lphp->h_addr))->s_addr;
}
else
{
addr_loc.sin_addr.s_addr = address;
}
}
if (bind(m_socket, (struct sockaddr *)&addr_loc, sizeof(addr_loc)) != 0)
{
return false;
}
if (listen(m_socket, 1000) != 0)
{
return false;
}
m_boundAsServer = true;
return true;
}
TcpConnection *TcpManager::acceptClient()
{
TcpConnection *newConn = NULL;
if (m_boundAsServer && m_connectionListCount < m_params.maxConnections)
{
sockaddr_in addr;
int addrLength = sizeof(addr);
SOCKET sock = ::accept(m_socket, (sockaddr *) &addr, (socklen_t *) &addrLength);
if (sock != INVALID_SOCKET)
{
newConn = new TcpConnection(this, &m_allocator, m_params, sock, IPAddress(addr.sin_addr.s_addr), ntohs(addr.sin_port));
addNewConnection(newConn);
if (m_handler != NULL)
{
m_handler->OnConnectRequest(newConn);
}
}
}
return newConn;
}
void TcpManager::SetHandler(TcpManagerHandler *handler)
{
m_handler = handler;
}
SOCKET TcpManager::getMaxFD()
{
#ifdef WIN32
return 0;//this param is not used on win32 for select, only on unix
#else
SOCKET maxfd = 0;
if (m_boundAsServer)
maxfd = m_socket+1;
TcpConnection *next = NULL;
for (TcpConnection *con = m_connectionList ; con != NULL ; con = next)
{
next = con->m_nextConnection;
if (con->GetStatus() != TcpConnection::StatusDisconnected && con->m_socket > maxfd)
{
maxfd = con->m_socket + 1;
}
}
return maxfd;
#endif
}
TcpConnection *TcpManager::getConnection(SOCKET fd)
{
TcpConnection *next = NULL;
for (TcpConnection *con = m_connectionList ; con != NULL ; con = next)
{
next = con->m_nextConnection;
if (con->m_socket == fd)
{
return con;
}
}
//if get here ,couldn't find it
return NULL;
}
bool TcpManager::GiveTime(unsigned maxTimeAcceptingConnections,unsigned maxSendTimePerConnection, unsigned maxRecvTimePerConnection)
{
bool processedIncoming = false;
if (maxTimeAcceptingConnections == 0 && maxSendTimePerConnection==0 && maxRecvTimePerConnection==0)
{
//they don't want to do anything now
return processedIncoming;
}
AddRef(); //keep a reference to ourself in case we callback to the application and the application releases us.
//first process outgoing on each connection, and finish establishing connections, if params say to
if (m_connectionListCount != 0 && maxSendTimePerConnection != 0)
{
// Send output from last heartbeat
TcpConnection *next = NULL;
for (TcpConnection *con = m_connectionList ; con != NULL ; con = next)
{
con->AddRef();
if (next) next->Release();
next = con->m_nextConnection;
if (next) next->AddRef();
if(con->GetStatus() == TcpConnection::StatusConnected)
{
Clock timer;
timer.start();
while(!timer.isDone(maxSendTimePerConnection))
{
int err = con->processOutgoing();
if (err > 0)
{
//couldn't finish processing last request, don't try more
break;
}
else if (err < 0)
{
break;
}
}
con->Release();
}
else if (con->GetStatus() == TcpConnection::StatusNegotiating)
{
if (con->finishConnect() < 0)
{
con->Release();
continue;
}
con->Release();
}
else //inactive client in client list????
{
removeConnection(con);
con->Release();
continue;
}
}
}
//process incoming messages (including connect requests)
if ((
m_boundAsServer //if in server mode and want to spend time accepting clients
&& maxTimeAcceptingConnections != 0
)
||
(
m_connectionListCount != 0 //if there are connections and want to spend time receiving on them
&& maxRecvTimePerConnection != 0
)
)
{
#ifdef WIN32
SOCKET maxfd = getMaxFD(); //re-calc maxfd every time select on WIN32
//select on all fd's
struct timeval timeout;
fd_set tmpfds;
tmpfds = m_permfds;
timeout.tv_sec = 0;
timeout.tv_usec = 0;
int cnt = select(maxfd, &tmpfds, NULL, NULL, &timeout); // blocks for timeout
if (cnt > 0)
{
if (m_boundAsServer && maxTimeAcceptingConnections != 0)
{//activity on our socket means connect requests
//see if are new incoming clients
if (FD_ISSET(m_socket, &tmpfds))
{
//yep
Clock timer;
timer.start();
while (acceptClient() && !timer.isDone(maxTimeAcceptingConnections))
{
//loop
}
}
}
//process incoming client messages
if (maxRecvTimePerConnection != 0)
{
TcpConnection *next = NULL;
for (TcpConnection *con = m_connectionList ; con != NULL ; con = next)
{
con->AddRef();
if (next) next->Release();
next = con->m_nextConnection;
if (next) next->AddRef();
SOCKET fd = con->m_socket;
if (fd == INVALID_SOCKET)
{
//invalid socket in list?, check if is connecting, otherwise, Disconnect and discard
if (con->GetStatus() != TcpConnection::StatusNegotiating)
{
removeConnection(con);
con->Release();
continue;
}
}
if (FD_ISSET(fd, &tmpfds))
{
Clock timer;
timer.start();
while(!timer.isDone(maxRecvTimePerConnection) && con->GetStatus() == TcpConnection::StatusConnected)
{
int err = con->processIncoming();
if (err >= 0)
{
processedIncoming = true;
}
if (err > 0)
{
//couldn't finish processing last request, don't try more
break;
}
else if (err < 0)
{
break;
}
}//while(!timer...)
}//if (FD_ISSET...)
con->Release();
}//for (...)
} //maxRecvTimePerConnection != 0
}//cnt > 0
#else //on UNIX use poll
int numfds = m_connectionListCount;
int idx = 0;
if (m_boundAsServer)
{
numfds++;
idx++;
}
struct pollfd pollfds[numfds];
if (m_boundAsServer)
{
pollfds[0].fd = m_socket;
pollfds[0].events |= POLLIN;
}
TcpConnection *next = NULL;
for (TcpConnection *con = m_connectionList ; con != NULL ; con = next, idx++)
{
next = con->m_nextConnection;
pollfds[idx].fd = con->m_socket;
pollfds[idx].events |= POLLIN;
pollfds[idx].events |= POLLHUP;
}
int cnt = poll(pollfds, numfds, 1);
if(cnt == SOCKET_ERROR)
{
//poll not working?
//TODO: need to notify client somehow, don't think we can assume a fatal error here
}
else if (cnt > 0)
{
for (idx = 0; idx < numfds; idx++)
{
//find corresponding TcpConnection
//TODO: optimize, seriously, this is takes linear time, every time
TcpConnection *con = getConnection(pollfds[idx].fd);
if (pollfds[idx].revents & POLLIN)
{
if (m_boundAsServer && maxTimeAcceptingConnections != 0 && pollfds[idx].fd == m_socket)
{
//new incoming clients
Clock timer;
timer.start();
while (acceptClient() && !timer.isDone(maxTimeAcceptingConnections))
{
//loop
}
continue;//don't try to readmsgs from listening fd
}
//process regular msg(s)
if (con == NULL)
{
close(pollfds[idx].fd);
continue;
}
Clock timer;
timer.start();
con->AddRef();//so it can't get deleted while we are checking it's status
while(!timer.isDone(maxRecvTimePerConnection) && con->GetStatus() == TcpConnection::StatusConnected)
{
int err = con->processIncoming();
if (err >= 0)
{
processedIncoming = true;
}
if (err > 0)
{
//couldn't finish processing last request, don't try more
break;
}
else if (err < 0)
{
break;
}
}//while(!timer....)
con->Release();
}//if(pollfds[...
else if (pollfds[idx].revents & POLLHUP)
{
if (con == NULL)
{
close(pollfds[idx].fd);
continue;
}
//Disconnect client
con->Disconnect();
}
}//for (idx=0....
}//else if (cnt > 0)
#endif
}//wanted to process incoming messages or connect requests
//now process any keepalives, if time to do that
if (m_params.keepAliveDelay > 0 && m_keepAliveTimer.isDone(m_params.keepAliveDelay))
{
TcpConnection *next = NULL;
for (TcpConnection *con = m_keepAliveList.m_beginList ; con != NULL ; con = next)
{
con->AddRef();
if (next) next->Release();
next = con->m_nextKeepAliveConnection;
if (next) next->AddRef();
con->Send(NULL, 0); //note: this request will move the connection from the keepAliveList to the aliveList
con->Release();
}
//now move the complete alive list over to the keepalive list to reset those timers
m_keepAliveList.m_beginList = m_aliveList.m_beginList;
m_aliveList.m_beginList = NULL;
//switch id's for those connections that were in the alive list last go - around
int tmpID = m_aliveList.m_listID;
m_aliveList.m_listID = m_keepAliveList.m_listID;
m_keepAliveList.m_listID = tmpID;
m_keepAliveTimer.reset();
m_keepAliveTimer.start();
}
//now process any noDataCons, if time to do that
if (m_params.noDataTimeout > 0 && m_noDataTimer.isDone(m_params.noDataTimeout))
{
TcpConnection *next = NULL;
for (TcpConnection *con = m_noDataList.m_beginList ; con != NULL ; con = next)
{
con->AddRef();
if (next) next->Release();
next = con->m_nextRecvDataConnection;
if (next) next->AddRef();
//time to disconnect this guy
con->Disconnect();
con->Release();
}
//now move the complete data list over to the nodata list to reset those timers
m_noDataList.m_beginList = m_dataList.m_beginList;
m_dataList.m_beginList = NULL;
//switch id's for those connections that were in the data list last go - around
int tmpID = m_dataList.m_listID;
m_dataList.m_listID = m_noDataList.m_listID;
m_noDataList.m_listID = tmpID;
m_noDataTimer.reset();
m_noDataTimer.start();
}
Release();
return processedIncoming;
}
TcpConnection *TcpManager::EstablishConnection(const char *serverAddress, unsigned short serverPort, unsigned timeout)
{
if (m_boundAsServer)
{
//can't open outgoing connections when in server mode
// use a different TcpManager to do that
return NULL;
}
if (m_connectionListCount >= m_params.maxConnections)
return(NULL);
// get server address
unsigned long address = inet_addr(serverAddress);
if (address == INADDR_NONE)
{
if (m_dnsMap[serverAddress].timeout >= time(NULL))
{
address = m_dnsMap[serverAddress].addr;
}
else
{
struct hostent * lphp;
lphp = gethostbyname(serverAddress);
if (lphp == NULL)
return(NULL);
address = ((struct in_addr *)(lphp->h_addr))->s_addr;
m_dnsMap[serverAddress].addr = address;
m_dnsMap[serverAddress].timeout = time(NULL)+DNS_TIMEOUT;
}
}
IPAddress destIP(address);
TcpConnection *con = new TcpConnection(this, &m_allocator, m_params, destIP, serverPort, timeout);
con->AddRef();//for the client - to conform to UdpLibrary method
addNewConnection(con);
return con;
}
void TcpManager::addNewConnection(TcpConnection *con)
{
con->AddRef();
#ifdef WIN32 //uses select
if (con->m_socket != INVALID_SOCKET)
FD_SET(con->m_socket, &m_permfds);
#endif
con->m_nextConnection = m_connectionList;
con->m_prevConnection = NULL;
if (m_connectionList != NULL)
m_connectionList->m_prevConnection = con;
m_connectionList = con;
m_connectionListCount++;
con->m_nextKeepAliveConnection = m_aliveList.m_beginList;
con->m_prevKeepAliveConnection = NULL;
if (m_aliveList.m_beginList != NULL)
m_aliveList.m_beginList->m_prevKeepAliveConnection = con;
m_aliveList.m_beginList = con;
con->m_aliveListId = m_keepAliveList.m_listID;//start it out thinking it's already in the alive list, since it is
con->m_nextRecvDataConnection = m_dataList.m_beginList;
con->m_prevRecvDataConnection = NULL;
if (m_dataList.m_beginList != NULL)
m_dataList.m_beginList->m_prevRecvDataConnection = con;
m_dataList.m_beginList = con;
con->m_recvDataListId = m_noDataList.m_listID;//start it out thinking it's already in the data list, since it is
}
void TcpManager::removeConnection(TcpConnection *con)
{
if (!con->wasRemovedFromMgr())
{
con->setRemovedFromMgr();
m_connectionListCount--;
#ifdef WIN32 //select only used on win32
if (con->m_socket != INVALID_SOCKET)
{
FD_CLR(con->m_socket, &m_permfds);
}
#endif
if (con->m_prevConnection != NULL)
con->m_prevConnection->m_nextConnection = con->m_nextConnection;
if (con->m_nextConnection != NULL)
con->m_nextConnection->m_prevConnection = con->m_prevConnection;
if (m_connectionList == con)
m_connectionList = con->m_nextConnection;
con->m_nextConnection = NULL;
con->m_prevConnection = NULL;
if (con->m_prevKeepAliveConnection != NULL)
con->m_prevKeepAliveConnection->m_nextKeepAliveConnection = con->m_nextKeepAliveConnection;
if (con->m_nextKeepAliveConnection != NULL)
con->m_nextKeepAliveConnection->m_prevKeepAliveConnection = con->m_prevKeepAliveConnection;
if (m_aliveList.m_beginList == con)
m_aliveList.m_beginList = con->m_nextKeepAliveConnection;
else if (m_keepAliveList.m_beginList == con)
m_keepAliveList.m_beginList = con->m_nextKeepAliveConnection;
con->m_nextKeepAliveConnection = NULL;
con->m_prevKeepAliveConnection = NULL;
if (con->m_prevRecvDataConnection != NULL)
con->m_prevRecvDataConnection->m_nextRecvDataConnection = con->m_nextRecvDataConnection;
if (con->m_nextRecvDataConnection != NULL)
con->m_nextRecvDataConnection->m_prevRecvDataConnection = con->m_prevRecvDataConnection;
if (m_dataList.m_beginList == con)
m_dataList.m_beginList = con->m_nextRecvDataConnection;
else if (m_noDataList.m_beginList == con)
m_noDataList.m_beginList = con->m_nextRecvDataConnection;
con->m_nextRecvDataConnection = NULL;
con->m_prevRecvDataConnection = NULL;
con->Release();
}
}
void TcpManager::AddRef()
{
m_refCount++;
}
void TcpManager::Release()
{
if (--m_refCount == 0)
delete this;
}
IPAddress TcpManager::GetLocalIp() const
{
struct sockaddr_in addr_self;
memset(&addr_self, 0, sizeof(addr_self));
socklen_t len = sizeof(addr_self);
getsockname(m_socket, (struct sockaddr *)&addr_self, &len);
return(IPAddress(addr_self.sin_addr.s_addr));
}
unsigned int TcpManager::GetLocalPort() const
{
struct sockaddr_in addr_self;
memset(&addr_self, 0, sizeof(addr_self));
socklen_t len = sizeof(addr_self);
getsockname(m_socket, (struct sockaddr *)&addr_self, &len);
return(ntohs(addr_self.sin_port));
}
#ifdef EXTERNAL_DISTRO
};
#endif
@@ -0,0 +1,300 @@
#ifndef TCPMANAGER_H
#define TCPMANAGER_H
#ifdef WIN32
#pragma warning (disable: 4786)
#endif
#include "TcpHandlers.h"
#include "TcpBlockAllocator.h"
#include "IPAddress.h"
#include "Clock.h"
#include <map>
#include <string>
#if defined(WIN32)
#include <winsock2.h>
typedef int socklen_t;
#else // for non-windows platforms (linux)
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
const int INVALID_SOCKET = 0xFFFFFFFF;
const int SOCKET_ERROR = 0xFFFFFFFF;
typedef int SOCKET;
#endif
#ifdef EXTERNAL_DISTRO
namespace NAMESPACE
{
#endif
class TcpConnection;
struct ConnectionList
{
ConnectionList(TcpConnection *con, int id) : m_beginList(con), m_listID(id) {}
TcpConnection *m_beginList;
int m_listID;
};
/**
* @brief The purpose of the TcpManager is to manage a set of connections that are coming in on a particular port.
*
*/
class TcpManager
{
public:
struct AddrTimeout
{
AddrTimeout() : addr(0), timeout(0) {}
long addr;
time_t timeout;
};
/** @brief Parameters for the TcpManager. */
struct TcpParams
{
/** @brief Simple constructor sets default values for members. */
TcpParams();
/** @brief Simple copy constructor. */
TcpParams(const TcpParams &cpy);
/**
* @brief Connection port number.
*
* this is the port number that this manager will use for all incoming and outgoing data. On the client side
* this is typically set to 0, which causes the manager object to randomly pick an available port. On the server
* side, this port should be set to a specific value as it will represent the port number that clients will use
* to connect to the server (ie. the listening port). It's generally a good idea to give the user on the client
* side the option of fixing this port number at a specific value as well as it is often necessary for them to
* do so in order to navigate company firewalls which may have specific port numbers open to them for this purpose.
* default = 0
*/
unsigned short port;
/**
* @ brief Server bind ip.
*
*/
char bindAddress[64];
/**
* @brief Maximum number of connections that can be established by this manager.
*
* this is the maximum number of connections that can be established by this manager, any incoming/outgoing connections
* over this limit will be refused. On the client side, this typically only needs to be set to 1, though there
* is little harm in setting this number larger.
* default = 10
*/
unsigned maxConnections;
/**
* @brief The size of the incoming socket buffer.
*
* The client will want to set this fairly small (32k or so), but the server
* will want to set this fairly large (512k)
* default = 64k
*/
unsigned incomingBufferSize;
/**
* @brief The size of the outgoing socket buffer.
*
* The client will want to set this fairly small (32k or so), but the server
* will want to set this fairly large (512k)
* default = 64k
*/
unsigned outgoingBufferSize;
/**
* @brief The block size of a single outgoing buffer memory allocator block.
*
* This param should allways be set at least as high as the maximum message size you
* expect to send (performance will suffer otherwise).
* default = 8K
*/
unsigned allocatorBlockSize;
/**
* @brief The number of block memory allocator 'blocks' created at a time.
*
* This is the number of blocks created for the buffer allocator for each
* TcpConnection opened by this manager. Since the block size should be
* the max size of an outgoing message, the recommended setting is: greater
* than the number of concurrent connections you expect to normally have open.
* default = 1024
*/
unsigned allocatorBlockCount;
/**
* @brief The maximum size that a recvd message is allowed to be.
*
* Really only here for protection, not required. If you set this, you can safeguard
* your client/server from receiving stray oversized messages. If a message on the socket
* specifies it's length at larger than this value, then the message is not read, and the connection
* is terminated. If the value is set to 0, then there is no max message size checking
* on incoming messages (this will also cary a performance hit, since every new message
* recieved will have to have a new buffer created if you don't specify a value here). Be careful
* not to set this too small, if you have messages that could exceed the value you set here
* they will be discarded, and the connection will be terminated without warning.
* default = 0
*/
unsigned maxRecvMessageSize;
unsigned keepAliveDelay;
unsigned noDataTimeout;
};
/**
* @brief
*/
TcpManager(const TcpParams &params);
/**
* @brief Use to specify a handler object to receive callbacks.
*
* To have the TcpManager call your object directly when connection requests come in, you
* simply need to derive your class (multiply if necessary) from TcpManagerHandler, then you can use
* this method to set the object the TcpManager will call as appropriate. The TcpConnection object
* also has a handler mechanism that replaces the other callback functions below, see TcpConnection::SetHandler
* default = NULL (no callbacks made)
*
* @param handler The object which will be called for manager related notifications.
*/
void SetHandler(TcpManagerHandler *handler);
/**
* @brief This function MUST be called on a regular basis in order to give the manager object time to service the socket and give time to various connection objects that may need processing time, etc.
*
* @param maxTimeAcceptingConnections The max amount of time in milliseconds to spend accepting new client connections.
* This parameter is only used if this manager has been bound as a server (bindAsServer).
* If you set this param to 0, it will not attempt to accept any new connections.
*
* @param giveConnectionsTime
* True if every connection opened on this manager is given time in this call, false if
* no connections are given time.
*
* @param maxSendTimePerConnection Max amount of time in milliseconds to spend on each client processing outgoing messages.
* A max of the specified amount of time will be spent on each and every individual connection. If you set
* this parametrer to 0, it will not process any outgoing messages on any clients. Note also that when attempting
* to establish new connections (via the EstablishConnection method), this parameter must be > 0 in order to
* complete the connection process for any connections that were still negotiating.
*
* @param maxRecvTimePerConnection Max amount of time in milliseconds to spend on each client processing incoming messages.
* A max of the specified amount of time will be spent on each and every individual connection. If you set
* this param to 0, it will not process any incoming messages on any clients.
* This is a good way to give the manager processing time for outgoing packets in situations
* where the application does not want to have to worry about processing incoming packets.
*
* @return true if any incoming packets were processed during this time slice, otherwise returns false
*/
bool GiveTime(unsigned maxTimeAcceptingConnections = 5, unsigned maxSendTimePerConnection = 5, unsigned maxRecvTimePerConnection = 5);
/**
* @brief Used to establish a connection to a server that is listening at the specified address and port.
*
* The serverAddress will do a DNS lookup as appropriate. This call will block long enough to resolve
* the DNS lookup, but then will return a TcpConnection object that will be in a StatusNegotiating
* state until the connection is actually established. The application must give the manager
* object time after calling EstablishConnection or else the negotiation process to establish the
* connection will never have time to actually occur. Typically the client establishing the connection
* will call EstablishConnection, then sit in a loop calling TcpManager::GiveTime and checking to see
* if the status of the returned TcpConnection object is changed from StatusNegotiating. This allows
* the application to look for the ESC key or timeout an attempted connection.
*
* @param serverAddress The address of the server to open a connection to.
*
* @param serverPort The port of the server to open a connection to.
*
* @param timeout How long to attempt connecting to the server (in milliseconds).
* Setting the timeout value to something greater than 0 will cause the TcpConnection object to change
* from a StatusNegotiating state to a StatusDisconnected state after the timeout has expired. It will also cause
* the connect-complete callback to be called if the connection is succesfull.
*
* @return A pointer to a TcpConnection object.
* NULL if the manager object has exceeded its maximum number of connections
* or if the serverAddress cannot be resolved to an IP address.
*/
TcpConnection *EstablishConnection(const char *serverAddress, unsigned short serverPort, unsigned timeout = 0);
/**
* @brief Binds this manager as a server which will listen for and accept incoming connections.
*
* @return 'true' if manager is able to bind succesfully, false otherwise.
*/
bool BindAsServer();
/**
* @brief Standard AddRef/Release scheme
*/
void AddRef();
/**
* @brief Standard AddRef/Release scheme
*/
void Release();
/**
* @brief Returns the ip address of this machine. If the machine is multi-homed, this value may be blank.
*/
IPAddress GetLocalIp() const;
/**
* @brief Returns the port the manager is actually using. This value will be the same as is specified in
* Params::port (or if Params::port was set to 0, this will be the dynamically assigned port number)
*/
unsigned int GetLocalPort() const;
protected:
friend class TcpConnection;
void removeConnection(TcpConnection *con);
TcpManagerHandler *m_handler;
ConnectionList m_keepAliveList;
ConnectionList m_aliveList;
ConnectionList m_noDataList;
ConnectionList m_dataList;
private:
~TcpManager();
TcpParams m_params;
int m_refCount;
TcpConnection *m_connectionList;
unsigned m_connectionListCount;
#ifdef WIN32
fd_set m_permfds; /**< Used for select on WIN32 if we are in server mode. Keeps track of all clients connected to us. */
#endif //WIN32
SOCKET m_socket;
bool m_boundAsServer;
TcpBlockAllocator m_allocator;
Clock m_keepAliveTimer;
Clock m_noDataTimer;
void addNewConnection(TcpConnection *con);
SOCKET getMaxFD();
TcpConnection *getConnection(SOCKET fd);
TcpConnection *acceptClient();
std::map<std::string, AddrTimeout> m_dnsMap;
};
#ifdef EXTERNAL_DISTRO
};
#endif
#endif //TCPMANAGER_H