openclonk/src/network/C4Network2Client.cpp

625 lines
16 KiB
C++
Raw Normal View History

2009-05-08 13:28:41 +00:00
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2004-2006 Peter Wortmann
* Copyright (c) 2005 Sven Eberhardt
* Copyright (c) 2005-2006 Günther Brammer
2009-05-08 13:28:41 +00:00
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
*
* Portions might be copyrighted by other authors who have contributed
* to OpenClonk.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* See isc_license.txt for full license and disclaimer.
*
* "Clonk" is a registered trademark of Matthes Bender.
* See clonk_trademark_license.txt for full license.
*/
#include <C4Include.h>
#include <C4Network2Client.h>
#include <C4Log.h>
#include <C4Console.h>
#include <C4Network2.h>
#include <C4Network2IO.h>
#include <C4Network2Stats.h>
#include <C4GameLobby.h> // fullscreen network lobby
#include <C4Game.h>
2009-06-12 23:09:32 +00:00
#include <C4PlayerList.h>
2009-05-08 13:28:41 +00:00
#ifndef _WIN32
#include <arpa/inet.h>
#include <netdb.h>
#endif
// *** C4Network2Address
void C4Network2Address::CompileFunc(StdCompiler *pComp)
{
// Clear
if(pComp->isCompiler())
{
ZeroMem(&addr, sizeof(addr));
addr.sin_family = AF_INET;
}
// Write protocol
StdEnumEntry<C4Network2IOProtocol> Protocols[] =
{
{ "UDP", P_UDP },
{ "TCP", P_TCP },
{ NULL, P_NONE },
};
pComp->Value(mkEnumAdaptT<uint8_t>(eProtocol, Protocols));
pComp->Seperator(StdCompiler::SEP_PART2); // ':'
// Write IP (no IP = 0.0.0.0)
in_addr zero; zero.s_addr = INADDR_ANY;
pComp->Value(mkDefaultAdapt(addr.sin_addr, zero));
pComp->Seperator(StdCompiler::SEP_PART2); // ':'
// Write port
uint16_t iPort = htons(addr.sin_port);
pComp->Value(iPort);
addr.sin_port = htons(iPort);
}
StdStrBuf C4Network2Address::toString() const
{
switch(eProtocol)
{
case P_UDP: return FormatString("UDP:%s:%d", inet_ntoa(addr.sin_addr), htons(addr.sin_port));
case P_TCP: return FormatString("TCP:%s:%d", inet_ntoa(addr.sin_addr), htons(addr.sin_port));
2010-01-25 04:00:59 +00:00
default: return StdStrBuf("INVALID");
2009-05-08 13:28:41 +00:00
}
}
bool C4Network2Address::operator == (const C4Network2Address &addr2) const
{
return eProtocol == addr2.getProtocol() && AddrEqual(addr, addr2.getAddr());
}
// *** C4Network2Client
C4Network2Client::C4Network2Client(C4Client *pClient)
: pClient(pClient),
iAddrCnt(0),
eStatus(NCS_Ready),
iLastActivity(0),
pMsgConn(NULL), pDataConn(NULL),
iNextConnAttempt(0),
pNext(NULL), pParent(NULL), pstatPing(NULL)
{
}
C4Network2Client::~C4Network2Client()
{
ClearGraphs();
if(pMsgConn) { pMsgConn->Close(); pMsgConn->DelRef(); } pMsgConn = NULL;
if(pDataConn) { pDataConn->Close(); pDataConn->DelRef(); } pDataConn = NULL;
if(pClient) pClient->UnlinkNetClient();
}
bool C4Network2Client::hasConn(C4Network2IOConnection *pConn)
{
return pMsgConn == pConn || pDataConn == pConn;
}
void C4Network2Client::SetMsgConn(C4Network2IOConnection *pConn)
{
// security
if(pConn != pMsgConn)
{
if(pMsgConn) pMsgConn->DelRef();
pMsgConn = pConn;
pMsgConn->AddRef();
}
if(!pDataConn) SetDataConn(pConn);
}
void C4Network2Client::SetDataConn(C4Network2IOConnection *pConn)
{
// security
if(pConn != pDataConn)
{
if(pDataConn) pDataConn->DelRef();
pDataConn = pConn;
pDataConn->AddRef();
}
if(!pMsgConn) SetMsgConn(pConn);
}
void C4Network2Client::RemoveConn(C4Network2IOConnection *pConn)
{
if(pConn == pMsgConn)
{ pMsgConn->DelRef(); pMsgConn = NULL; }
if(pConn == pDataConn)
{ pDataConn->DelRef(); pDataConn = NULL; }
if(pMsgConn && !pDataConn) SetDataConn(pMsgConn);
if(!pMsgConn && pDataConn) SetMsgConn(pDataConn);
}
void C4Network2Client::CloseConns(const char *szMsg)
{
C4PacketConnRe Pkt(false, false, szMsg);
C4Network2IOConnection *pConn;
2010-01-25 04:00:59 +00:00
while((pConn = pMsgConn))
2009-05-08 13:28:41 +00:00
{
// send packet, close
if(pConn->isOpen())
{
pConn->Send(MkC4NetIOPacket(PID_ConnRe, Pkt));
pConn->Close();
}
// remove
RemoveConn(pConn);
}
}
bool C4Network2Client::SendMsg(C4NetIOPacket rPkt) const
{
return getMsgConn() && getMsgConn()->Send(rPkt);
}
bool C4Network2Client::SendData(C4NetIOPacket rPkt) const
{
return getDataConn() && getDataConn()->Send(rPkt);
}
bool C4Network2Client::DoConnectAttempt(C4Network2IO *pIO)
{
// local?
if(isLocal()) { iNextConnAttempt = 0; return true; }
// msg and data connected? Nothing to do
if(getMsgConn() != getDataConn()) { iNextConnAttempt = time(NULL) + 10; return true; }
// too early?
if(iNextConnAttempt && iNextConnAttempt > time(NULL)) return true;
// find address to try
int32_t iBestAddress = -1;
for(int32_t i = 0; i < iAddrCnt; i++)
// no connection for this protocol?
if((!pDataConn || Addr[i].getProtocol() != pDataConn->getProtocol()) &&
(!pMsgConn || Addr[i].getProtocol() != pMsgConn->getProtocol()))
// protocol available?
if(pIO->getNetIO(Addr[i].getProtocol()))
// new best address?
if(iBestAddress < 0 || AddrAttempts[i] < AddrAttempts[iBestAddress])
iBestAddress = i;
// too many attempts or nothing found?
if(iBestAddress < 0 || AddrAttempts[iBestAddress] > C4NetClientConnectAttempts)
{ iNextConnAttempt = time(NULL) + 10; return true; }
// save attempt
AddrAttempts[iBestAddress]++; iNextConnAttempt = time(NULL) + C4NetClientConnectInterval;
// log
LogSilentF("Network: connecting client %s on %s...", getName(), Addr[iBestAddress].toString().getData());
// connect
return pIO->Connect(Addr[iBestAddress].getAddr(), Addr[iBestAddress].getProtocol(), pClient->getCore());
}
bool C4Network2Client::hasAddr(const C4Network2Address &addr) const
{
for(int32_t i = 0; i < iAddrCnt; i++)
if(Addr[i] == addr)
return true;
return false;
}
void C4Network2Client::ClearAddr()
{
iAddrCnt = 0;
}
bool C4Network2Client::AddAddr(const C4Network2Address &addr, bool fAnnounce)
{
// checks
if(iAddrCnt + 1 >= C4ClientMaxAddr) return false;
if(hasAddr(addr)) return true;
// add
Addr[iAddrCnt] = addr; AddrAttempts[iAddrCnt] = 0;
iAddrCnt++;
// attempt to use this one
if(!iNextConnAttempt) iNextConnAttempt = time(NULL);
// announce
if(fAnnounce)
if(!pParent->BroadcastMsgToConnClients(MkC4NetIOPacket(PID_Addr, C4PacketAddr(getID(), addr))))
return false;
// done
return true;
}
void C4Network2Client::AddLocalAddrs(int16_t iPortTCP, int16_t iPortUDP)
{
// set up address struct
sockaddr_in addr; ZeroMem(&addr, sizeof addr);
addr.sin_family = AF_INET;
// get local address(es)
in_addr **ppAddr = NULL;
#ifdef HAVE_WINSOCK
bool fGotWinSock = AcquireWinSock();
if(fGotWinSock)
#endif
{
// get local host name
char szLocalHostName[128+1]; *szLocalHostName = '\0';
::gethostname(szLocalHostName, 128);
// get hostent-struct
hostent *ph = ::gethostbyname(szLocalHostName);
// check type, get addr list
if(ph)
2010-01-25 04:00:59 +00:00
{
2009-05-08 13:28:41 +00:00
if(ph->h_addrtype != AF_INET)
ph = NULL;
else
ppAddr = reinterpret_cast<in_addr **>(ph->h_addr_list);
2010-01-25 04:00:59 +00:00
}
2009-05-08 13:28:41 +00:00
}
// add address(es)
for(;;)
{
if(iPortTCP >= 0)
{
addr.sin_port = htons(iPortTCP);
AddAddr(C4Network2Address(addr, P_TCP), false);
}
if(iPortUDP >= 0)
{
addr.sin_port = htons(iPortUDP);
AddAddr(C4Network2Address(addr, P_UDP), false);
}
// get next
if(!ppAddr || !*ppAddr) break;
addr.sin_addr = **ppAddr++;
}
#ifdef HAVE_WINSOCK
if(fGotWinSock) ReleaseWinSock();
#endif
}
void C4Network2Client::SendAddresses(C4Network2IOConnection *pConn)
{
// send all addresses
for(int32_t i = 0; i < iAddrCnt; i++)
{
C4NetIOPacket Pkt = MkC4NetIOPacket(PID_Addr, C4PacketAddr(getID(), Addr[i]));
if(pConn)
pConn->Send(Pkt);
else
pParent->BroadcastMsgToConnClients(Pkt);
}
}
void C4Network2Client::CreateGraphs()
{
// del prev
ClearGraphs();
// get client color
static const DWORD ClientDefColors[] = {0xff0000, 0x00ff00, 0xffff00, 0x7f7fff, 0xffffff, 0x00ffff, 0xff00ff, 0x7f7f7f, 0xff7f7f, 0x7fff7f, 0x0000ff};
int32_t iClientColorNum = sizeof(ClientDefColors)/sizeof(DWORD);
DWORD dwClientClr = ClientDefColors[Max<int32_t>(getID(), 0) % iClientColorNum];
// create graphs
pstatPing = new C4TableGraph(C4TableGraph::DefaultBlockLength, Game.pNetworkStatistics ? Game.pNetworkStatistics->SecondCounter : 0);
pstatPing->SetColorDw(dwClientClr);
pstatPing->SetTitle(getName());
// register into stat module
if (Game.pNetworkStatistics) Game.pNetworkStatistics->statPings.AddGraph(pstatPing);
}
void C4Network2Client::ClearGraphs()
{
// del all assigned graphs
if (pstatPing)
{
if (Game.pNetworkStatistics) Game.pNetworkStatistics->statPings.RemoveGraph(pstatPing);
delete pstatPing;
pstatPing = NULL;
}
}
// *** C4Network2ClientList
C4Network2ClientList::C4Network2ClientList(C4Network2IO *pIO)
2010-01-25 04:00:59 +00:00
: pIO(pIO), pFirst(NULL), pLocal(NULL)
2009-05-08 13:28:41 +00:00
{
}
C4Network2ClientList::~C4Network2ClientList()
{
Clear();
}
C4Network2Client *C4Network2ClientList::GetClientByID(int32_t iID) const
{
for(C4Network2Client *pClient = pFirst; pClient; pClient = pClient->pNext)
if(pClient->getID() == iID)
return pClient;
return NULL;
}
C4Network2Client *C4Network2ClientList::GetNextClientAfterID(int32_t iSmallerClientID) const
{
// return client with smallest ID > iSmallerClientID
C4Network2Client *pBest = NULL;
for(C4Network2Client *pClient = pFirst; pClient; pClient = pClient->pNext)
if(pClient->getID() > iSmallerClientID)
if (!pBest || pBest->getID() > pClient->getID())
pBest = pClient;
return pBest;
}
C4Network2Client *C4Network2ClientList::GetClient(const char *szName) const
{
for(C4Network2Client *pClient = pFirst; pClient; pClient = pClient->pNext)
if(SEqual(pClient->getName(), szName))
return pClient;
return NULL;
}
C4Network2Client *C4Network2ClientList::GetClient(C4Network2IOConnection *pConn) const
{
for(C4Network2Client *pClient = pFirst; pClient; pClient = pClient->pNext)
if(pClient->hasConn(pConn))
return pClient;
return NULL;
}
C4Network2Client *C4Network2ClientList::GetClient(const C4ClientCore &CCore, int32_t iMaxDiffLevel)
{
for(C4Network2Client *pClient = pFirst; pClient; pClient = pClient->pNext)
if(pClient->getCore().getDiffLevel(CCore) <= iMaxDiffLevel)
return pClient;
return NULL;
}
C4Network2Client *C4Network2ClientList::GetHost()
{
return GetClientByID(C4ClientIDHost);
}
C4Network2Client *C4Network2ClientList::GetNextClient(C4Network2Client *pClient)
{
return pClient ? pClient->pNext : pFirst;
}
void C4Network2ClientList::Init(C4ClientList *pnClientList, bool fnHost)
{
// save flag
fHost = fnHost;
// initialize
pClientList = pnClientList;
pClientList->InitNetwork(this);
}
C4Network2Client *C4Network2ClientList::RegClient(C4Client *pClient)
{
// security
if(pClient->getNetClient())
return pClient->getNetClient();
// find insert position
C4Network2Client *pPos = pFirst, *pLast = NULL;
for(;pPos; pLast = pPos, pPos = pPos->getNext())
if(pPos->getID() > pClient->getID())
break;
assert(!pLast || pLast->getID() != pClient->getID());
// create new client
C4Network2Client *pNetClient = new C4Network2Client(pClient);
// add to list
pNetClient->pNext = pPos;
(pLast ? pLast->pNext : pFirst) = pNetClient;
pNetClient->pParent = this;
// local?
if(pClient->isLocal())
pLocal = pNetClient;
else
// set auto-accept
pIO->AddAutoAccept(pClient->getCore());
// add
return pNetClient;
}
void C4Network2ClientList::DeleteClient(C4Network2Client *pClient)
{
// close connections
pClient->CloseConns("removing client");
// remove from list
if(pClient == pFirst)
pFirst = pClient->getNext();
else
{
C4Network2Client *pPrev;
for(pPrev = pFirst; pPrev && pPrev->getNext(); pPrev = pPrev->getNext())
if(pPrev->getNext() == pClient)
break;
if(pPrev && pPrev->getNext() == pClient)
pPrev->pNext = pClient->getNext();
}
// remove auto-accept
pIO->RemoveAutoAccept(pClient->getCore());
// delete
delete pClient;
}
void C4Network2ClientList::Clear()
{
// remove link to main client list
if(pClientList)
{
C4ClientList *poClientList = pClientList;
pClientList = NULL;
poClientList->ClearNetwork();
}
// delete clients
while(pFirst)
{
DeleteClient(pFirst);
}
pLocal = NULL;
}
bool C4Network2ClientList::BroadcastMsgToConnClients(const C4NetIOPacket &rPkt)
{
// Send a msg to all clients that are currently directly reachable.
// lock
pIO->BeginBroadcast(false);
// select connections for broadcast
for(C4Network2Client *pClient = pFirst; pClient; pClient = pClient->getNext())
if(pClient->isConnected())
pClient->getMsgConn()->SetBroadcastTarget(true);
// broadcast
bool fSuccess = pIO->Broadcast(rPkt);
// unlock
pIO->EndBroadcast();
// finished
return fSuccess;
}
bool C4Network2ClientList::BroadcastMsgToClients(const C4NetIOPacket &rPkt)
{
// Send a msg to all clients, including clients that are not connected to
// this computer (will get forwarded by host).
C4PacketFwd Fwd; Fwd.SetListType(true);
// lock
pIO->BeginBroadcast(false);
// select connections for broadcast
for(C4Network2Client *pClient = pFirst; pClient; pClient = pClient->getNext())
if(!pClient->isHost())
if(pClient->isConnected())
{
pClient->getMsgConn()->SetBroadcastTarget(true);
Fwd.AddClient(pClient->getID());
}
// broadcast
bool fSuccess = pIO->Broadcast(rPkt);
// unlock
pIO->EndBroadcast();
// clients: send forward request to host
if(!fHost)
{
Fwd.SetData(rPkt);
fSuccess &= SendMsgToHost(MkC4NetIOPacket(PID_FwdReq, Fwd));
}
return fSuccess;
}
bool C4Network2ClientList::SendMsgToHost(C4NetIOPacket rPkt)
{
// find host
C4Network2Client *pHost = GetHost();
if(!pHost) return false;
// send message
if(!pHost->getMsgConn()) return false;
return pHost->SendMsg(rPkt);
}
bool C4Network2ClientList::SendMsgToClient(int32_t iClient, C4NetIOPacket RREF rPkt)
{
// find client
C4Network2Client *pClient = GetClientByID(iClient);
if(!pClient) return false;
// connected? send directly
if(pClient->isConnected())
return pClient->SendMsg(rPkt);
// forward
C4PacketFwd Fwd; Fwd.SetListType(false);
Fwd.AddClient(iClient);
Fwd.SetData(rPkt);
return SendMsgToHost(MkC4NetIOPacket(PID_FwdReq, Fwd));
}
void C4Network2ClientList::HandlePacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn)
{
// find associated client
C4Network2Client *pClient = GetClient(pConn);
if(!pClient) return;
#define GETPKT(type, name) \
assert(pBasePkt); const type &name = \
/*dynamic_cast*/ static_cast<const type &>(*pBasePkt);
switch(cStatus)
{
case PID_Addr: // address propagation
{
GETPKT(C4PacketAddr, rPkt)
// find client
pClient = GetClientByID(rPkt.getClientID());
if(pClient)
{
C4Network2Address addr = rPkt.getAddr();
// IP zero? Set to IP from where the packet came
if(addr.isIPNull())
addr.SetIP(pConn->getPeerAddr().sin_addr);
// add (no announce)
if(pClient->AddAddr(addr, true))
// new address? Try to connect
pClient->DoConnectAttempt(pIO);
}
}
break;
}
#undef GETPKT
}
void C4Network2ClientList::SendAddresses(C4Network2IOConnection *pConn)
{
// send all client addresses known
for(C4Network2Client *pClient = pFirst; pClient; pClient = pClient->getNext())
pClient->SendAddresses(pConn);
}
void C4Network2ClientList::DoConnectAttempts()
{
// check interval
time_t t; time(&t);
for(C4Network2Client *pClient = pFirst; pClient; pClient = pClient->getNext())
if(!pClient->isLocal() && !pClient->isRemoved() && pClient->getNextConnAttempt() && pClient->getNextConnAttempt() <= t)
// attempt connect
pClient->DoConnectAttempt(pIO);
}
void C4Network2ClientList::ResetReady()
{
for(C4Network2Client *pClient = pFirst; pClient; pClient = pClient->getNext())
if(pClient->isWaitedFor())
pClient->SetStatus(NCS_NotReady);
}
bool C4Network2ClientList::AllClientsReady() const
{
for(C4Network2Client *pClient = pFirst; pClient; pClient = pClient->getNext())
if(!pClient->isLocal() && pClient->isWaitedFor() && !pClient->isReady())
return false;
return true;
}
void C4Network2ClientList::UpdateClientActivity()
{
for(C4Network2Client *pClient = pFirst; pClient; pClient = pClient->getNext())
if(pClient->isActivated())
2009-06-12 23:09:32 +00:00
if(::Players.GetAtClient(pClient->getID()))
2009-05-08 13:28:41 +00:00
pClient->SetLastActivity(Game.FrameCounter);
}
// *** C4PacketAddr
void C4PacketAddr::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iClientID), "ClientID", C4ClientIDUnknown));
pComp->Value(mkNamingAdapt(addr, "Addr"));
}