Serious-Engine/Sources/Engine/Network/CommunicationInterface.cpp
2016-03-11 15:57:17 +02:00

1271 lines
36 KiB
C++

/* Copyright (c) 2002-2012 Croteam Ltd. All rights reserved. */
#include "stdh.h"
#include <Engine/Base/Console.h>
#include <Engine/Base/CTString.h>
#include <Engine/Base/ErrorReporting.h>
#include <Engine/Base/ErrorTable.h>
#include <Engine/Base/ProgressHook.h>
#include <Engine/Base/Synchronization.h>
#include <Engine/Base/Translation.h>
#include <Engine/Network/ClientInterface.h>
#include <Engine/Network/CommunicationInterface.h>
#include <Engine/Network/CPacket.h>
#include <Engine/Network/Network.h>
#include <Engine/Network/Server.h>
#include <Engine/GameAgent/GameAgent.h>
#pragma comment(lib, "wsock32.lib")
#define SERVER_LOCAL_CLIENT 0
extern INDEX net_iPort;
extern CTString net_strLocalHost;
extern INDEX net_bLookupHostNames;
extern INDEX net_bReportICMPErrors;
extern FLOAT net_fDropPackets;
extern FLOAT net_tmConnectionTimeout;
extern INDEX net_bReportPackets;
static struct ErrorCode ErrorCodes[] = {
ERRORCODE(WSAEINTR , "WSAEINTR"),
ERRORCODE(WSAEBADF , "WSAEBADF"),
ERRORCODE(WSAEACCES , "WSAEACCES"),
ERRORCODE(WSAEFAULT , "WSAEFAULT"),
ERRORCODE(WSAEINVAL , "WSAEINVAL"),
ERRORCODE(WSAEMFILE , "WSAEMFILE"),
ERRORCODE(WSAEWOULDBLOCK , "WSAEWOULDBLOCK"),
ERRORCODE(WSAEINPROGRESS , "WSAEINPROGRESS"),
ERRORCODE(WSAEALREADY , "WSAEALREADY"),
ERRORCODE(WSAENOTSOCK , "WSAENOTSOCK"),
ERRORCODE(WSAEDESTADDRREQ , "WSAEDESTADDRREQ"),
ERRORCODE(WSAEMSGSIZE , "WSAEMSGSIZE"),
ERRORCODE(WSAEPROTOTYPE , "WSAEPROTOTYPE"),
ERRORCODE(WSAENOPROTOOPT , "WSAENOPROTOOPT"),
ERRORCODE(WSAEPROTONOSUPPORT, "WSAEPROTONOSUPPORT"),
ERRORCODE(WSAESOCKTNOSUPPORT, "WSAESOCKTNOSUPPORT"),
ERRORCODE(WSAEOPNOTSUPP , "WSAEOPNOTSUPP"),
ERRORCODE(WSAEPFNOSUPPORT , "WSAEPFNOSUPPORT"),
ERRORCODE(WSAEAFNOSUPPORT , "WSAEAFNOSUPPORT"),
ERRORCODE(WSAEADDRINUSE , "WSAEADDRINUSE"),
ERRORCODE(WSAEADDRNOTAVAIL , "WSAEADDRNOTAVAIL"),
ERRORCODE(WSAENETDOWN , "WSAENETDOWN"),
ERRORCODE(WSAENETUNREACH , "WSAENETUNREACH"),
ERRORCODE(WSAENETRESET , "WSAENETRESET"),
ERRORCODE(WSAECONNABORTED , "WSAECONNABORTED"),
ERRORCODE(WSAECONNRESET , "WSAECONNRESET"),
ERRORCODE(WSAENOBUFS , "WSAENOBUFS"),
ERRORCODE(WSAEISCONN , "WSAEISCONN"),
ERRORCODE(WSAENOTCONN , "WSAENOTCONN"),
ERRORCODE(WSAESHUTDOWN , "WSAESHUTDOWN"),
ERRORCODE(WSAETOOMANYREFS , "WSAETOOMANYREFS"),
ERRORCODE(WSAETIMEDOUT , "WSAETIMEDOUT"),
ERRORCODE(WSAECONNREFUSED , "WSAECONNREFUSED"),
ERRORCODE(WSAELOOP , "WSAELOOP"),
ERRORCODE(WSAENAMETOOLONG , "WSAENAMETOOLONG"),
ERRORCODE(WSAEHOSTDOWN , "WSAEHOSTDOWN"),
ERRORCODE(WSAEHOSTUNREACH , "WSAEHOSTUNREACH"),
ERRORCODE(WSASYSNOTREADY , "WSASYSNOTREADY"),
ERRORCODE(WSAVERNOTSUPPORTED, "WSAVERNOTSUPPORTED"),
ERRORCODE(WSANOTINITIALISED , "WSANOTINITIALISED"),
ERRORCODE(WSAEDISCON , "WSAEDISCON"),
ERRORCODE(WSAHOST_NOT_FOUND , "WSAHOST_NOT_FOUND"),
ERRORCODE(WSATRY_AGAIN , "WSATRY_AGAIN"),
ERRORCODE(WSANO_RECOVERY , "WSANO_RECOVERY"),
ERRORCODE(WSANO_DATA , "WSANO_DATA"),
};
static struct ErrorTable SocketErrors = ERRORTABLE(ErrorCodes);
//structures used to emulate bandwidth and latency parameters - shared by all client interfaces
CPacketBufferStats _pbsSend;
CPacketBufferStats _pbsRecv;
ULONG cm_ulLocalHost; // configured local host address
CTString cm_strAddress; // local address
CTString cm_strName; // local address
CTCriticalSection cm_csComm; // critical section for access to communication data
BOOL cm_bNetworkInitialized;
// index 0 is the server's local client, this is an array used by server only
CClientInterface cm_aciClients[SERVER_CLIENTS];
// Broadcast interface - i.e. interface for 'nonconnected' communication
CClientInterface cm_ciBroadcast;
// this is used by client only
CClientInterface cm_ciLocalClient;
// global communication interface object (there is only one for the entire engine)
CCommunicationInterface _cmiComm;
/*
*
* Two helper functions - conversion from IP to words
*
*/
// convert address to a printable string
CTString AddressToString(ULONG ulHost)
{
ULONG ulHostNet = htonl(ulHost);
// initially not converted
struct hostent *hostentry = NULL;
// if DNS lookup is allowed
if (net_bLookupHostNames) {
// lookup the host
hostentry = gethostbyaddr ((char *)&ulHostNet, sizeof(ulHostNet), AF_INET);
}
// if DNS lookup succeeded
if (hostentry!=NULL) {
// return its ascii name
return (char *)hostentry->h_name;
// if DNS lookup failed
} else {
// just convert to dotted number format
return inet_ntoa((const in_addr &)ulHostNet);
}
};
// convert string address to a number
ULONG StringToAddress(const CTString &strAddress)
{
// first try to convert numeric address
ULONG ulAddress = ntohl(inet_addr(strAddress));
// if not a valid numeric address
if (ulAddress==INADDR_NONE) {
// lookup the host
HOSTENT *phe = gethostbyname(strAddress);
// if succeeded
if (phe!=NULL) {
// get that address
ulAddress = ntohl(*(ULONG*)phe->h_addr_list[0]);
}
}
// return what we got
return ulAddress;
};
CCommunicationInterface::CCommunicationInterface(void)
{
cm_csComm.cs_iIndex = -1;
CTSingleLock slComm(&cm_csComm, TRUE);
cci_bInitialized = FALSE;
cci_bWinSockOpen = FALSE;
cci_bServerInitialized = FALSE;
cci_bClientInitialized = FALSE;
cm_ciLocalClient.ci_bClientLocal = FALSE;
cci_hSocket=INVALID_SOCKET;
};
// initialize
void CCommunicationInterface::Init(void)
{
CTSingleLock slComm(&cm_csComm, TRUE);
cci_bWinSockOpen = FALSE;
cci_bInitialized = TRUE;
// mark as initialized
cm_bNetworkInitialized = FALSE;
cci_pbMasterInput.Clear();
cci_pbMasterOutput.Clear();
};
// close
void CCommunicationInterface::Close(void)
{
CTSingleLock slComm(&cm_csComm, TRUE);
ASSERT(cci_bInitialized);
ASSERT(!cci_bServerInitialized);
ASSERT(!cci_bClientInitialized);
// mark as closed
cm_bNetworkInitialized = FALSE;
cci_bInitialized = FALSE;
cm_ciLocalClient.ci_bClientLocal = FALSE;
cci_pbMasterInput.Clear();
cci_pbMasterOutput.Clear();
};
void CCommunicationInterface::InitWinsock(void)
{
if (cci_bWinSockOpen) {
return;
}
// start winsock
WSADATA winsockdata;
WORD wVersionRequested;
wVersionRequested = MAKEWORD(1, 1);
int iResult = WSAStartup(wVersionRequested, &winsockdata);
// if winsock open ok
if (iResult==0) {
// remember that
cci_bWinSockOpen = TRUE;
CPrintF(TRANS(" winsock opened ok\n"));
}
};
void CCommunicationInterface::EndWinsock(void)
{
if (!cci_bWinSockOpen) {
return;
}
int iResult = WSACleanup();
ASSERT(iResult==0);
cci_bWinSockOpen = FALSE;
};
// prepares the comm interface for use - MUST be invoked before any data can be sent/received
void CCommunicationInterface::PrepareForUse(BOOL bUseNetwork, BOOL bClient)
{
// clear the network conditions emulation data
_pbsSend.Clear();
_pbsRecv.Clear();
// if the network is already initialized, shut it down before proceeding
if (cm_bNetworkInitialized) {
Unprepare();
}
// make sure winsock is off (could be on if enumeration was triggered)
GameAgent_EnumCancel();
EndWinsock();
if (bUseNetwork) {
CPrintF(TRANS("Initializing TCP/IP...\n"));
if (bClient) {
CPrintF(TRANS(" opening as client\n"));
} else {
CPrintF(TRANS(" opening as server\n"));
}
// make sure winsock is on
InitWinsock();
// no address by default
cm_ulLocalHost = 0;
// if there is a desired local address
if (net_strLocalHost!="") {
CPrintF(TRANS(" user forced local address: %s\n"), (const char*)net_strLocalHost);
// use that address
cm_strName = net_strLocalHost;
cm_ulLocalHost = StringToAddress(cm_strName);
// if invalid
if (cm_ulLocalHost==0 || cm_ulLocalHost==-1) {
cm_ulLocalHost=0;
// report it
CPrintF(TRANS(" requested local address is invalid\n"));
}
}
// if no valid desired local address
CPrintF(TRANS(" getting local addresses\n"));
// get default
char hostname[256];
gethostname(hostname, sizeof(hostname)-1);
cm_strName = hostname;
// lookup the host
HOSTENT *phe = gethostbyname(cm_strName);
// if succeeded
if (phe!=NULL) {
// get the addresses
cm_strAddress = "";
for(INDEX i=0; phe->h_addr_list[i]!=NULL; i++) {
if (i>0) {
cm_strAddress += ", ";
}
cm_strAddress += inet_ntoa(*(const in_addr *)phe->h_addr_list[i]);
}
}
CPrintF(TRANS(" local addresses: %s (%s)\n"), cm_strName, cm_strAddress);
CPrintF(TRANS(" port: %d\n"), net_iPort);
// try to open master UDP socket
try {
OpenSocket_t(cm_ulLocalHost, bClient?0:net_iPort);
cci_pbMasterInput.pb_ppbsStats = NULL;
cci_pbMasterOutput.pb_ppbsStats = NULL;
cm_ciBroadcast.SetLocal(NULL);
CPrintF(TRANS(" opened socket: \n"));
} catch (char *strError) {
CPrintF(TRANS(" cannot open UDP socket: %s\n"), strError);
}
}
cm_bNetworkInitialized = cci_bWinSockOpen;
};
// shut down the communication interface
void CCommunicationInterface::Unprepare(void)
{
// close winsock
if (cci_bWinSockOpen) {
// if socket is open
if (cci_hSocket != INVALID_SOCKET) {
// close it
closesocket(cci_hSocket);
cci_hSocket = INVALID_SOCKET;
}
cm_ciBroadcast.Clear();
EndWinsock();
cci_bBound=FALSE;
}
cci_pbMasterInput.Clear();
cci_pbMasterOutput.Clear();
cm_bNetworkInitialized = cci_bWinSockOpen;
};
BOOL CCommunicationInterface::IsNetworkEnabled(void)
{
return cm_bNetworkInitialized;
};
// get address of local machine
void CCommunicationInterface::GetHostName(CTString &strName, CTString &strAddress)
{
strName = cm_strName;
strAddress = cm_strAddress;
};
/*
*
*
* Socket functions - creating, binding...
*
*
*/
// create an inet-family socket
void CCommunicationInterface::CreateSocket_t()
{
ASSERT(cci_hSocket==INVALID_SOCKET);
// open the socket
cci_hSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
cci_bBound = FALSE;
if (cci_hSocket == INVALID_SOCKET) {
ThrowF_t(TRANS("Cannot open socket. %s"), (const char*)GetSocketError(WSAGetLastError()));
}
};
// bind socket to the given address
void CCommunicationInterface::Bind_t(ULONG ulLocalHost, ULONG ulLocalPort)
{
if (cci_hSocket==INVALID_SOCKET) {
ASSERT(FALSE);
return;
}
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(ulLocalPort);
sin.sin_addr.s_addr = htonl(ulLocalHost);
// bind socket to server address/port
if (bind(cci_hSocket, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR) {
ThrowF_t(TRANS("Cannot bind socket. %s"), (const char*)GetSocketError(WSAGetLastError()));
}
cci_bBound = TRUE;
};
// set socket to non-blocking mode
void CCommunicationInterface::SetNonBlocking_t(void)
{
if (cci_hSocket==INVALID_SOCKET) {
ASSERT(FALSE);
return;
}
ULONG ulArgNonBlocking = 1;
if (ioctlsocket(cci_hSocket, FIONBIO, &ulArgNonBlocking) == SOCKET_ERROR) {
ThrowF_t(TRANS("Cannot set socket to non-blocking mode. %s"),
(const char*)GetSocketError(WSAGetLastError()));
}
};
// get generic socket error info string about last error
CTString CCommunicationInterface::GetSocketError(INDEX iError)
{
CTString strError;
strError.PrintF(TRANS("Socket %d, Error %d (%s)"),
cci_hSocket, iError, ErrorDescription(&SocketErrors, iError));
return strError;
};
// open an UDP socket at given port
void CCommunicationInterface::OpenSocket_t(ULONG ulLocalHost, ULONG ulLocalPort)
{
// create the socket as UDP
CreateSocket_t();
// bind it to that address/port
if (ulLocalPort!=0) {
Bind_t(ulLocalHost, ulLocalPort);
}
// go non-blocking
SetNonBlocking_t();
// mark as open
cci_bSocketOpen = TRUE;
};
// get address of this host
void CCommunicationInterface::GetLocalAddress_t(ULONG &ulHost, ULONG &ulPort)
{
ulHost = 0;
ulPort = 0;
if (cci_hSocket==INVALID_SOCKET) {
ASSERT(FALSE);
return;
}
// get socket local port and address
sockaddr_in sin;
int iSize = sizeof(sin);
if (getsockname(cci_hSocket, (sockaddr*)&sin, &iSize) == SOCKET_ERROR) {
ThrowF_t(TRANS("Cannot get local address on socket. %s"),
(const char*)GetSocketError(WSAGetLastError()));
}
ulHost = ntohl(sin.sin_addr.S_un.S_addr);
ulPort = ntohs(sin.sin_port);
}
// get address of the peer host connected to this socket
void CCommunicationInterface::GetRemoteAddress_t(ULONG &ulHost, ULONG &ulPort)
{
ulHost = 0;
ulPort = 0;
if (cci_hSocket==INVALID_SOCKET) {
ASSERT(FALSE);
return;
}
// get socket local port
sockaddr_in sin;
int iSize = sizeof(sin);
if (getpeername(cci_hSocket, (sockaddr*)&sin, &iSize) == SOCKET_ERROR) {
ThrowF_t(TRANS("Cannot get remote address on socket. %s"),
(const char*)GetSocketError(WSAGetLastError()));
}
ulHost = ntohl(sin.sin_addr.S_un.S_addr);
ulPort = ntohs(sin.sin_port);
}
/*
* ---->>>> BROADCAST INTERFACE <<<<----
*/
// broadcast communication
void CCommunicationInterface::Broadcast_Send(const void *pvSend, SLONG slSendSize,CAddress &adrDestination)
{
CTSingleLock slComm(&cm_csComm, TRUE);
cm_ciBroadcast.ci_adrAddress.adr_ulAddress = adrDestination.adr_ulAddress;
cm_ciBroadcast.ci_adrAddress.adr_uwPort = adrDestination.adr_uwPort;
cm_ciBroadcast.ci_adrAddress.adr_uwID = adrDestination.adr_uwID;
cm_ciBroadcast.Send(pvSend, slSendSize,FALSE);
}
BOOL CCommunicationInterface::Broadcast_Receive(void *pvReceive, SLONG &slReceiveSize,CAddress &adrAddress)
{
CTSingleLock slComm(&cm_csComm, TRUE);
return cm_ciBroadcast.ReceiveFrom(pvReceive, slReceiveSize,&adrAddress,FALSE);
}
// update the broadcast input buffer - handle any incoming connection requests
void CCommunicationInterface::Broadcast_Update_t() {
CPacket* ppaConnectionRequest;
BOOL bIsAlready;
BOOL bFoundEmpty;
ULONG iClient;
UBYTE ubDummy=65;
// while there is a connection request packet in the input buffer
while ((ppaConnectionRequest = cm_ciBroadcast.ci_pbReliableInputBuffer.GetConnectRequestPacket()) != NULL) {
// see if there is a client already connected at that address and port
bIsAlready = FALSE;
for (iClient=1;iClient<SERVER_CLIENTS;iClient++) {
if (cm_aciClients[iClient].ci_adrAddress.adr_ulAddress == ppaConnectionRequest->pa_adrAddress.adr_ulAddress &&
cm_aciClients[iClient].ci_adrAddress.adr_uwPort == ppaConnectionRequest->pa_adrAddress.adr_uwPort) {
bIsAlready = TRUE;
break;
}
}
// if the client is already connected then just ignore the packet - else, connect it
if (!bIsAlready) {
// find an empty client structure
bFoundEmpty = FALSE;
for (iClient=1;iClient<SERVER_CLIENTS;iClient++) {
if (cm_aciClients[iClient].ci_bUsed == FALSE) {
bFoundEmpty = TRUE;
// we have an empty slot, so fill it for the client
cm_aciClients[iClient].ci_adrAddress.adr_ulAddress = ppaConnectionRequest->pa_adrAddress.adr_ulAddress;
cm_aciClients[iClient].ci_adrAddress.adr_uwPort = ppaConnectionRequest->pa_adrAddress.adr_uwPort;
// generate the ID
UWORD uwID = _pTimer->GetHighPrecisionTimer().tv_llValue&0x0FFF;
if (uwID==0 || uwID=='//') {
uwID+=1;
}
cm_aciClients[iClient].ci_adrAddress.adr_uwID = (uwID<<4)+iClient;
// form the connection response packet
ppaConnectionRequest->pa_adrAddress.adr_uwID = '//';
ppaConnectionRequest->pa_ubReliable = UDP_PACKET_RELIABLE | UDP_PACKET_RELIABLE_HEAD | UDP_PACKET_RELIABLE_TAIL | UDP_PACKET_CONNECT_RESPONSE;
// return it to the client
ppaConnectionRequest->WriteToPacket(&(cm_aciClients[iClient].ci_adrAddress.adr_uwID),sizeof(cm_aciClients[iClient].ci_adrAddress.adr_uwID),ppaConnectionRequest->pa_ubReliable,cm_ciBroadcast.ci_ulSequence++,ppaConnectionRequest->pa_adrAddress.adr_uwID,sizeof(cm_aciClients[iClient].ci_adrAddress.adr_uwID));
cm_ciBroadcast.ci_pbOutputBuffer.AppendPacket(*ppaConnectionRequest,TRUE);
cm_aciClients[iClient].ci_bUsed = TRUE;
return;
}
}
// if none found
if (!bFoundEmpty) {
// error
ThrowF_t(TRANS("Server: Cannot accept new clients, all slots used!\n"));
}
}
}
};
/*
* ---->>>> SERVER <<<<----
*/
/*
* Initialize server
*/
void CCommunicationInterface::Server_Init_t(void)
{
CTSingleLock slComm(&cm_csComm, TRUE);
ASSERT(cci_bInitialized);
ASSERT(!cci_bServerInitialized);
// for each client
for(INDEX iClient=0; iClient<SERVER_CLIENTS; iClient++) {
// clear its status
cm_aciClients[iClient].Clear();
cm_aciClients[iClient].ci_pbOutputBuffer.pb_ppbsStats = &_pbsSend;
cm_aciClients[iClient].ci_pbInputBuffer.pb_ppbsStats = &_pbsRecv;
}
// mark the server's instance of the local client as such
cm_aciClients[SERVER_LOCAL_CLIENT].ci_bClientLocal = TRUE;
cm_aciClients[SERVER_LOCAL_CLIENT].ci_bUsed = TRUE;
// prepare the client part of the local client
cm_ciLocalClient.Clear();
cm_ciLocalClient.ci_bUsed = TRUE;
cm_ciLocalClient.ci_bClientLocal = TRUE;
cm_ciLocalClient.ci_pbOutputBuffer.pb_ppbsStats = &_pbsSend;
cm_ciLocalClient.ci_pbInputBuffer.pb_ppbsStats = &_pbsRecv;
// mark that the server was initialized
cci_bServerInitialized = TRUE;
};
/*
* Close server
*/
void CCommunicationInterface::Server_Close(void)
{
CTSingleLock slComm(&cm_csComm, TRUE);
// close all clients
for (INDEX iClient=0; iClient<SERVER_CLIENTS; iClient++) {
cm_aciClients[iClient].Clear();
}
// mark that the server is uninitialized
cci_bServerInitialized = FALSE;
};
/*
* Server clear client and prepare for new connection
*/
void CCommunicationInterface::Server_ClearClient(INDEX iClient)
{
// synchronize access to communication data
CTSingleLock slComm(&cm_csComm, TRUE);
ASSERT(iClient>=0 && iClient<SERVER_CLIENTS);
cm_aciClients[iClient].Clear();
};
BOOL CCommunicationInterface::Server_IsClientLocal(INDEX iClient)
{
CTSingleLock slComm(&cm_csComm, TRUE);
ASSERT(iClient>=0 && iClient<SERVER_CLIENTS);
return iClient==SERVER_LOCAL_CLIENT;
};
BOOL CCommunicationInterface::Server_IsClientUsed(INDEX iClient)
{
CTSingleLock slComm(&cm_csComm, TRUE);
ASSERT(iClient>=0 && iClient<SERVER_CLIENTS);
return cm_aciClients[iClient].ci_bUsed;
};
CTString CCommunicationInterface::Server_GetClientName(INDEX iClient)
{
CTSingleLock slComm(&cm_csComm, TRUE);
ASSERT(iClient>=0 && iClient<SERVER_CLIENTS);
if (iClient==SERVER_LOCAL_CLIENT) {
return TRANS("Local machine");
}
cm_aciClients[iClient].ci_strAddress = AddressToString(cm_aciClients[iClient].ci_adrAddress.adr_ulAddress);
return cm_aciClients[iClient].ci_strAddress;
};
/*
* Server Send/Receive Reliable
*/
void CCommunicationInterface::Server_Send_Reliable(INDEX iClient, const void *pvSend, SLONG slSendSize)
{
CTSingleLock slComm(&cm_csComm, TRUE);
ASSERT(iClient>=0 && iClient<SERVER_CLIENTS);
cm_aciClients[iClient].Send(pvSend, slSendSize,TRUE);
};
BOOL CCommunicationInterface::Server_Receive_Reliable(INDEX iClient, void *pvReceive, SLONG &slReceiveSize)
{
CTSingleLock slComm(&cm_csComm, TRUE);
ASSERT(iClient>=0 && iClient<SERVER_CLIENTS);
return cm_aciClients[iClient].Receive(pvReceive, slReceiveSize,TRUE);
};
/*
* Server Send/Receive Unreliable
*/
void CCommunicationInterface::Server_Send_Unreliable(INDEX iClient, const void *pvSend, SLONG slSendSize)
{
CTSingleLock slComm(&cm_csComm, TRUE);
ASSERT(iClient>=0 && iClient<SERVER_CLIENTS);
cm_aciClients[iClient].Send(pvSend, slSendSize,FALSE);
};
BOOL CCommunicationInterface::Server_Receive_Unreliable(INDEX iClient, void *pvReceive, SLONG &slReceiveSize)
{
CTSingleLock slComm(&cm_csComm, TRUE);
ASSERT(iClient>=0 && iClient<SERVER_CLIENTS);
return cm_aciClients[iClient].Receive(pvReceive, slReceiveSize,FALSE);
};
BOOL CCommunicationInterface::Server_Update()
{
CTSingleLock slComm(&cm_csComm, TRUE);
CPacket *ppaPacket;
CPacket *ppaPacketCopy;
CTimerValue tvNow = _pTimer->GetHighPrecisionTimer();
INDEX iClient;
// transfer packets for the local client
if (cm_ciLocalClient.ci_bUsed && cm_ciLocalClient.ci_pciOther != NULL) {
cm_ciLocalClient.ExchangeBuffers();
};
cm_aciClients[0].UpdateOutputBuffers();
// if not just playing single player
if (cci_bServerInitialized) {
Broadcast_Update_t();
// for each client transfer packets from the output buffer to the master output buffer
for (iClient=1; iClient<SERVER_CLIENTS; iClient++) {
CClientInterface &ci = cm_aciClients[iClient];
// if not connected
if (!ci.ci_bUsed) {
// skip it
continue;
}
// update its buffers, if a reliable packet is overdue (has not been delivered too long)
// disconnect the client
if (ci.UpdateOutputBuffers() != FALSE) {
// transfer packets ready to be sent out to the master output buffer
while (ci.ci_pbOutputBuffer.pb_ulNumOfPackets > 0) {
ppaPacket = ci.ci_pbOutputBuffer.PeekFirstPacket();
if (ppaPacket->pa_tvSendWhen < tvNow) {
ci.ci_pbOutputBuffer.RemoveFirstPacket(FALSE);
if (ppaPacket->pa_ubReliable & UDP_PACKET_RELIABLE) {
ppaPacketCopy = new CPacket;
*ppaPacketCopy = *ppaPacket;
ci.ci_pbWaitAckBuffer.AppendPacket(*ppaPacketCopy,FALSE);
}
cci_pbMasterOutput.AppendPacket(*ppaPacket,FALSE);
} else {
break;
}
}
} else {
CPrintF(TRANS("Unable to deliver data to client '%s', disconnecting.\n"),AddressToString(cm_aciClients[iClient].ci_adrAddress.adr_ulAddress));
Server_ClearClient(iClient);
_pNetwork->ga_srvServer.HandleClientDisconected(iClient);
}
}
// update broadcast output buffers
// update its buffers
cm_ciBroadcast.UpdateOutputBuffers();
// transfer packets ready to be sent out to the master output buffer
while (cm_ciBroadcast.ci_pbOutputBuffer.pb_ulNumOfPackets > 0) {
ppaPacket = cm_ciBroadcast.ci_pbOutputBuffer.PeekFirstPacket();
if (ppaPacket->pa_tvSendWhen < tvNow) {
cm_ciBroadcast.ci_pbOutputBuffer.RemoveFirstPacket(FALSE);
cci_pbMasterOutput.AppendPacket(*ppaPacket,FALSE);
} else {
break;
}
}
// send/receive packets over the TCP/IP stack
UpdateMasterBuffers();
// dispatch all packets from the master input buffer to the clients' input buffers
while (cci_pbMasterInput.pb_ulNumOfPackets > 0) {
BOOL bClientFound;
ppaPacket = cci_pbMasterInput.GetFirstPacket();
bClientFound = FALSE;
if (ppaPacket->pa_adrAddress.adr_uwID=='//' || ppaPacket->pa_adrAddress.adr_uwID==0) {
cm_ciBroadcast.ci_pbInputBuffer.AppendPacket(*ppaPacket,FALSE);
bClientFound = TRUE;
} else {
for (iClient=0; iClient<SERVER_CLIENTS; iClient++) {
if (ppaPacket->pa_adrAddress.adr_uwID == cm_aciClients[iClient].ci_adrAddress.adr_uwID) {
cm_aciClients[iClient].ci_pbInputBuffer.AppendPacket(*ppaPacket,FALSE);
bClientFound = TRUE;
break;
}
}
}
if (!bClientFound) {
// warn about possible attack
extern INDEX net_bReportMiscErrors;
if (net_bReportMiscErrors) {
CPrintF(TRANS("WARNING: Invalid message from: %s\n"), AddressToString(ppaPacket->pa_adrAddress.adr_ulAddress));
}
}
}
for (iClient=1; iClient<SERVER_CLIENTS; iClient++) {
cm_aciClients[iClient].UpdateInputBuffers();
}
}
cm_aciClients[0].UpdateInputBuffers();
cm_ciLocalClient.UpdateInputBuffers();
cm_ciBroadcast.UpdateInputBuffersBroadcast();
Broadcast_Update_t();
return TRUE;
};
/*
* ---->>>> CLIENT <<<<----
*/
/*
* Initialize client
*/
void CCommunicationInterface::Client_Init_t(char* strServerName)
{
CTSingleLock slComm(&cm_csComm, TRUE);
ASSERT(cci_bInitialized);
ASSERT(!cci_bClientInitialized);
// retrieve server address from server name
ULONG ulServerAddress = StringToAddress(strServerName);
// if lookup failed
if (ulServerAddress==INADDR_NONE) {
ThrowF_t(TRANS("Host '%s' not found!\n"), strServerName);
}
// call client init with server address
Client_Init_t(ulServerAddress);
};
void CCommunicationInterface::Client_Init_t(ULONG ulServerAddress)
{
CTSingleLock slComm(&cm_csComm, TRUE);
ASSERT(cci_bInitialized);
ASSERT(!cci_bClientInitialized);
cm_ciLocalClient.Clear();
cm_ciLocalClient.ci_pbOutputBuffer.pb_ppbsStats = &_pbsSend;
cm_ciLocalClient.ci_pbInputBuffer.pb_ppbsStats = &_pbsRecv;
// if this computer is not the server
if (!cci_bServerInitialized) {
// open with connecting to remote server
cm_ciLocalClient.ci_bClientLocal= FALSE;
Client_OpenNet_t(ulServerAddress);
// if this computer is server
} else {
// open local client
cm_ciLocalClient.ci_bClientLocal = TRUE;
Client_OpenLocal();
}
cci_bClientInitialized = TRUE;
};
/*
* Close client
*/
void CCommunicationInterface::Client_Close(void)
{
CTSingleLock slComm(&cm_csComm, TRUE);
ASSERT(cci_bInitialized);
// dispatch remaining packets (keep trying for half a second - 10 attempts)
for(TIME tmWait=0; tmWait<500;
Sleep(NET_WAITMESSAGE_DELAY), tmWait+=NET_WAITMESSAGE_DELAY) {
// if all packets are successfully sent, exit loop
if ((cm_ciLocalClient.ci_pbOutputBuffer.pb_ulNumOfPackets == 0)
&& (cm_ciLocalClient.ci_pbWaitAckBuffer.pb_ulNumOfPackets == 0)) {
break;
}
if (Client_Update() == FALSE) {
break;
}
}
cm_ciLocalClient.Clear();
cm_ciLocalClient.ci_bClientLocal= FALSE;
cci_bClientInitialized = FALSE;
};
/*
* Open client local
*/
void CCommunicationInterface::Client_OpenLocal(void)
{
CTSingleLock slComm(&cm_csComm, TRUE);
CClientInterface &ci0 = cm_ciLocalClient;
CClientInterface &ci1 = cm_aciClients[SERVER_LOCAL_CLIENT];
ci0.ci_bUsed = TRUE;
ci0.SetLocal(&ci1);
ci1.ci_bUsed = TRUE;
ci1.SetLocal(&ci0);
};
/*
* Open client remote
*/
void CCommunicationInterface::Client_OpenNet_t(ULONG ulServerAddress)
{
CTSingleLock slComm(&cm_csComm, TRUE);
CPacket* ppaInfoPacket;
CPacket* ppaReadPacket;
UBYTE ubDummy=65;
UBYTE ubReliable;
// check for reconnection
static ULONG ulLastServerAddress = -1;
BOOL bReconnecting = ulServerAddress == ulLastServerAddress;
ulLastServerAddress = ulServerAddress;
const INDEX iRefresh = 100; // (in miliseconds)
// determine connection timeout
INDEX ctRetries = bReconnecting?(180*1000/iRefresh):3;
// start waiting for server's response
if (ctRetries>1) {
SetProgressDescription(TRANS("waiting for server"));
CallProgressHook_t(0.0f);
}
// form the connection request packet
ppaInfoPacket = new CPacket;
ubReliable = UDP_PACKET_RELIABLE | UDP_PACKET_RELIABLE_HEAD | UDP_PACKET_RELIABLE_TAIL | UDP_PACKET_CONNECT_REQUEST;
ppaInfoPacket->pa_adrAddress.adr_ulAddress = ulServerAddress;
ppaInfoPacket->pa_adrAddress.adr_uwPort = net_iPort;
ppaInfoPacket->pa_ubRetryNumber = 0;
ppaInfoPacket->WriteToPacket(&ubDummy,1,ubReliable,cm_ciLocalClient.ci_ulSequence++,'//',1);
cm_ciLocalClient.ci_pbOutputBuffer.AppendPacket(*ppaInfoPacket,TRUE);
// set client destination address to server address
cm_ciLocalClient.ci_adrAddress.adr_ulAddress = ulServerAddress;
cm_ciLocalClient.ci_adrAddress.adr_uwPort = net_iPort;
// for each retry
for(INDEX iRetry=0; iRetry<ctRetries; iRetry++) {
// send/receive and juggle the buffers
if (Client_Update() == FALSE) {
break;
}
// if there is something in the input buffer
if (cm_ciLocalClient.ci_pbReliableInputBuffer.pb_ulNumOfPackets > 0) {
ppaReadPacket = cm_ciLocalClient.ci_pbReliableInputBuffer.GetFirstPacket();
// and it is a connection confirmation
if (ppaReadPacket->pa_ubReliable && UDP_PACKET_CONNECT_RESPONSE) {
// the client has succedeed to connect, so read the uwID from the packet
cm_ciLocalClient.ci_adrAddress.adr_ulAddress = ulServerAddress;
cm_ciLocalClient.ci_adrAddress.adr_uwPort = net_iPort;
cm_ciLocalClient.ci_adrAddress.adr_uwID = *((UWORD*) (ppaReadPacket->pa_pubPacketData + MAX_HEADER_SIZE));
cm_ciLocalClient.ci_bUsed = TRUE;
cm_ciLocalClient.ci_bClientLocal = FALSE;
cm_ciLocalClient.ci_pciOther = NULL;
cm_ciLocalClient.ci_pbReliableInputBuffer.RemoveConnectResponsePackets();
delete ppaReadPacket;
// finish waiting
CallProgressHook_t(1.0f);
return;
}
}
Sleep(iRefresh);
CallProgressHook_t(FLOAT(iRetry%10)/10);
}
cci_bBound = FALSE;
ThrowF_t(TRANS("Client: Timeout receiving UDP port"));
};
/*
* Clear local client
*/
void CCommunicationInterface::Client_Clear(void)
{
// synchronize access to communication data
CTSingleLock slComm(&cm_csComm, TRUE);
cm_ciLocalClient.Clear();
};
/*
* Client get status
*/
BOOL CCommunicationInterface::Client_IsConnected(void)
{
// synchronize access to communication data
CTSingleLock slComm(&cm_csComm, TRUE);
return cm_ciLocalClient.ci_bUsed;
};
/*
* Client Send/Receive Reliable
*/
void CCommunicationInterface::Client_Send_Reliable(const void *pvSend, SLONG slSendSize)
{
CTSingleLock slComm(&cm_csComm, TRUE);
cm_ciLocalClient.Send(pvSend, slSendSize,TRUE);
};
BOOL CCommunicationInterface::Client_Receive_Reliable(void *pvReceive, SLONG &slReceiveSize)
{
CTSingleLock slComm(&cm_csComm, TRUE);
return cm_ciLocalClient.Receive(pvReceive, slReceiveSize,TRUE);
};
BOOL CCommunicationInterface::Client_Receive_Reliable(CTStream &strmReceive)
{
CTSingleLock slComm(&cm_csComm, TRUE);
return cm_ciLocalClient.Receive(strmReceive,TRUE);
};
void CCommunicationInterface::Client_PeekSize_Reliable(SLONG &slExpectedSize,SLONG &slReceivedSize)
{
slExpectedSize = cm_ciLocalClient.GetExpectedReliableSize();
slReceivedSize = cm_ciLocalClient.GetCurrentReliableSize();
}
/*
* Client Send/Receive Unreliable
*/
void CCommunicationInterface::Client_Send_Unreliable(const void *pvSend, SLONG slSendSize)
{
CTSingleLock slComm(&cm_csComm, TRUE);
cm_ciLocalClient.Send(pvSend, slSendSize,FALSE);
};
BOOL CCommunicationInterface::Client_Receive_Unreliable(void *pvReceive, SLONG &slReceiveSize)
{
CTSingleLock slComm(&cm_csComm, TRUE);
return cm_ciLocalClient.Receive(pvReceive, slReceiveSize,FALSE);
};
BOOL CCommunicationInterface::Client_Update(void)
{
CTSingleLock slComm(&cm_csComm, TRUE);
CPacket *ppaPacket;
CPacket *ppaPacketCopy;
CTimerValue tvNow = _pTimer->GetHighPrecisionTimer();
// update local client's output buffers
if (cm_ciLocalClient.UpdateOutputBuffers() == FALSE) {
return FALSE;
}
// if not playing on the server (i.e. connectet to a remote server)
if (!cci_bServerInitialized) {
// put all pending packets in the master output buffer
while (cm_ciLocalClient.ci_pbOutputBuffer.pb_ulNumOfPackets > 0) {
ppaPacket = cm_ciLocalClient.ci_pbOutputBuffer.PeekFirstPacket();
if (ppaPacket->pa_tvSendWhen < tvNow) {
cm_ciLocalClient.ci_pbOutputBuffer.RemoveFirstPacket(FALSE);
if (ppaPacket->pa_ubReliable & UDP_PACKET_RELIABLE) {
ppaPacketCopy = new CPacket;
*ppaPacketCopy = *ppaPacket;
cm_ciLocalClient.ci_pbWaitAckBuffer.AppendPacket(*ppaPacketCopy,FALSE);
}
cci_pbMasterOutput.AppendPacket(*ppaPacket,FALSE);
} else {
break;
}
}
// update broadcast output buffers
// update its buffers
cm_ciBroadcast.UpdateOutputBuffers();
// transfer packets ready to be sent out to the master output buffer
while (cm_ciBroadcast.ci_pbOutputBuffer.pb_ulNumOfPackets > 0) {
ppaPacket = cm_ciBroadcast.ci_pbOutputBuffer.PeekFirstPacket();
if (ppaPacket->pa_tvSendWhen < tvNow) {
cm_ciBroadcast.ci_pbOutputBuffer.RemoveFirstPacket(FALSE);
cci_pbMasterOutput.AppendPacket(*ppaPacket,FALSE);
} else {
break;
}
}
// send/receive packets over the TCP/IP stack
UpdateMasterBuffers();
// dispatch all packets from the master input buffer to the clients' input buffers
while (cci_pbMasterInput.pb_ulNumOfPackets > 0) {
BOOL bClientFound;
ppaPacket = cci_pbMasterInput.GetFirstPacket();
bClientFound = FALSE;
// if the packet address is broadcast and it's an unreliable transfer, put it in the broadcast buffer
if ((ppaPacket->pa_adrAddress.adr_uwID=='//' || ppaPacket->pa_adrAddress.adr_uwID==0) &&
ppaPacket->pa_ubReliable == UDP_PACKET_UNRELIABLE) {
cm_ciBroadcast.ci_pbInputBuffer.AppendPacket(*ppaPacket,FALSE);
bClientFound = TRUE;
// if the packet is for this client, accept it
} else if ((ppaPacket->pa_adrAddress.adr_uwID == cm_ciLocalClient.ci_adrAddress.adr_uwID) ||
ppaPacket->pa_adrAddress.adr_uwID=='//' || ppaPacket->pa_adrAddress.adr_uwID==0) {
cm_ciLocalClient.ci_pbInputBuffer.AppendPacket(*ppaPacket,FALSE);
bClientFound = TRUE;
}
if (!bClientFound) {
// warn about possible attack
extern INDEX net_bReportMiscErrors;
if (net_bReportMiscErrors) {
CPrintF(TRANS("WARNING: Invalid message from: %s\n"), AddressToString(ppaPacket->pa_adrAddress.adr_ulAddress));
}
}
}
}
cm_ciLocalClient.UpdateInputBuffers();
cm_ciBroadcast.UpdateInputBuffersBroadcast();
return TRUE;
};
// update master UDP socket and route its messages
void CCommunicationInterface::UpdateMasterBuffers()
{
UBYTE aub[MAX_PACKET_SIZE];
CAddress adrIncomingAddress;
SOCKADDR_IN sa;
int size = sizeof(sa);
SLONG slSizeReceived;
SLONG slSizeSent;
BOOL bSomethingDone;
CPacket* ppaNewPacket;
CTimerValue tvNow;
if (cci_bBound) {
// read from the socket while there is incoming data
do {
// initially, nothing is done
bSomethingDone = FALSE;
slSizeReceived = recvfrom(cci_hSocket,(char*)aub,MAX_PACKET_SIZE,0,(SOCKADDR *)&sa,&size);
tvNow = _pTimer->GetHighPrecisionTimer();
adrIncomingAddress.adr_ulAddress = ntohl(sa.sin_addr.s_addr);
adrIncomingAddress.adr_uwPort = ntohs(sa.sin_port);
//On error, report it to the console (if error is not a no data to read message)
if (slSizeReceived == SOCKET_ERROR) {
int iResult = WSAGetLastError();
if (iResult!=WSAEWOULDBLOCK) {
// report it
if (iResult!=WSAECONNRESET || net_bReportICMPErrors) {
CPrintF(TRANS("Socket error during UDP receive. %s\n"),
(const char*)GetSocketError(iResult));
return;
}
}
// if block received
} else {
// if there is not at least one byte more in the packet than the header size
if (slSizeReceived <= MAX_HEADER_SIZE) {
// the packet is in error
extern INDEX net_bReportMiscErrors;
if (net_bReportMiscErrors) {
CPrintF(TRANS("WARNING: Bad UDP packet from '%s'\n"), AddressToString(adrIncomingAddress.adr_ulAddress));
}
// there might be more to do
bSomethingDone = TRUE;
} else if (net_fDropPackets <= 0 || (FLOAT(rand())/RAND_MAX) > net_fDropPackets) {
// if no packet drop emulation (or the packet is not dropped), form the packet
// and add it to the end of the UDP Master's input buffer
ppaNewPacket = new CPacket;
ppaNewPacket->WriteToPacketRaw(aub,slSizeReceived);
ppaNewPacket->pa_adrAddress.adr_ulAddress = adrIncomingAddress.adr_ulAddress;
ppaNewPacket->pa_adrAddress.adr_uwPort = adrIncomingAddress.adr_uwPort;
if (net_bReportPackets == TRUE) {
CPrintF("%lu: Received sequence: %d from ID: %d, reliable flag: %d\n",(ULONG) tvNow.GetMilliseconds(),ppaNewPacket->pa_ulSequence,ppaNewPacket->pa_adrAddress.adr_uwID,ppaNewPacket->pa_ubReliable);
}
cci_pbMasterInput.AppendPacket(*ppaNewPacket,FALSE);
// there might be more to do
bSomethingDone = TRUE;
}
}
} while (bSomethingDone);
}
// write from the output buffer to the socket
while (cci_pbMasterOutput.pb_ulNumOfPackets > 0) {
ppaNewPacket = cci_pbMasterOutput.PeekFirstPacket();
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = htonl(ppaNewPacket->pa_adrAddress.adr_ulAddress);
sa.sin_port = htons(ppaNewPacket->pa_adrAddress.adr_uwPort);
slSizeSent = sendto(cci_hSocket, (char*) ppaNewPacket->pa_pubPacketData, (int) ppaNewPacket->pa_slSize, 0, (SOCKADDR *)&sa, sizeof(sa));
cci_bBound = TRUE; // UDP socket that did a send is considered bound
tvNow = _pTimer->GetHighPrecisionTimer();
// if some error
if (slSizeSent == SOCKET_ERROR) {
int iResult = WSAGetLastError();
// if output UDP buffer full, stop sending
if (iResult == WSAEWOULDBLOCK) {
return;
// report it
} else if (iResult!=WSAECONNRESET || net_bReportICMPErrors) {
CPrintF(TRANS("Socket error during UDP send. %s\n"),
(const char*)GetSocketError(iResult));
}
return;
// if all sent ok
} else {
if (net_bReportPackets == TRUE) {
CPrintF("%lu: Sent sequence: %d to ID: %d, reliable flag: %d\n",(ULONG)tvNow.GetMilliseconds(),ppaNewPacket->pa_ulSequence,ppaNewPacket->pa_adrAddress.adr_uwID,ppaNewPacket->pa_ubReliable);
}
cci_pbMasterOutput.RemoveFirstPacket(TRUE);
bSomethingDone=TRUE;
}
}
};