Files
src-1.2/external/3rd/library/soePlatform/CTServiceGameAPI/TcpLibrary/TcpManager.cpp
T

735 lines
22 KiB
C++

#include "TcpManager.h"
#include <assert.h>
#include "IPAddress.h"
#include "TcpConnection.h"
#ifndef WIN32
#include <sys/poll.h>
#endif
#ifdef EXTERNAL_DISTRO
namespace NAMESPACE
{
#endif
TcpManager::TcpParams::TcpParams()
: port(0),
maxConnections(10),
incomingBufferSize(64*1024),
outgoingBufferSize(64*1024),
allocatorBlockSize(8*1024),
allocatorBlockCount(16),
maxRecvMessageSize(0),
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)
{
}
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()
{
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;
m_connectionList = m_connectionList->m_nextConnection;
con->Release();
m_connectionListCount--;
}
}
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)
{
struct hostent * lphp;
lphp = gethostbyname(serverAddress);
if (lphp == NULL)
return(NULL);
address = ((struct in_addr *)(lphp->h_addr))->s_addr;
}
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