You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/CryEngine/CrySystem/ServiceNetwork.cpp

2036 lines
65 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
// Description : Service network implementation
#include "CrySystem_precompiled.h"
#include "ServiceNetwork.h"
#include "RemoteCommandHelpers.h"
#include <AzCore/Socket/AzSocket.h>
//-----------------------------------------------------------------------------
// network system internal logging
#ifdef RELEASE
#define LOG_VERBOSE(level, txt, ...)
#else
#define LOG_VERBOSE(level, txt, ...) if (GetManager()->CheckVerbose(level)) { GetManager()->Log(txt, __VA_ARGS__); }
#endif
//-----------------------------------------------------------------------------
namespace
{
union AddValueConv
{
struct
{
uint8 ip0, ip1, ip2, ip3;
} bytes;
uint32 u32;
};
inline void TranslateAddress(const ServiceNetworkAddress& addr, AZ::AzSock::AzSocketAddress& outAddr)
{
AddValueConv addr_value;
addr_value.bytes.ip0 = addr.GetAddress().m_ip0;
addr_value.bytes.ip1 = addr.GetAddress().m_ip1;
addr_value.bytes.ip2 = addr.GetAddress().m_ip2;
addr_value.bytes.ip3 = addr.GetAddress().m_ip3;
outAddr.SetAddress(addr_value.u32, addr.GetAddress().m_port);
}
inline void TranslateAddress(const AZ::AzSock::AzSocketAddress& addr, ServiceNetworkAddress& outAddr)
{
AddValueConv addr_value;
const AZSOCKADDR_IN* addrIn = reinterpret_cast<const AZSOCKADDR_IN*>(addr.GetTargetAddress());
addr_value.u32 = AZ::AzSock::NetToHostLong((*addrIn).sin_addr.s_addr);
outAddr = ServiceNetworkAddress(
addr_value.bytes.ip0,
addr_value.bytes.ip1,
addr_value.bytes.ip2,
addr_value.bytes.ip3,
addr.GetAddrPort());
}
inline bool SocketConnectionsFull(AZ::AzSock::AzSockError error)
{
return (error == AZ::AzSock::AzSockError::eASE_NO_ERROR || error == AZ::AzSock::AzSockError::eASE_EWOULDBLOCK || error == AZ::AzSock::AzSockError::eASE_EWOULDBLOCK_CONN);
}
}
//-----------------------------------------------------------------------------
CServiceNetworkMessage::CServiceNetworkMessage(const uint32 id, const uint32 size)
: m_refCount(1)
, m_size(size)
, m_id(id)
{
// Allocate buffer memory
m_pData = CryModuleMalloc(size);
}
CServiceNetworkMessage::~CServiceNetworkMessage()
{
// Release the memory buffer
CryModuleFree(m_pData);
m_pData = NULL;
}
uint32 CServiceNetworkMessage::GetSize() const
{
return m_size;
}
uint32 CServiceNetworkMessage::GetId() const
{
return m_id;
}
void* CServiceNetworkMessage::GetPointer()
{
return m_pData;
}
const void* CServiceNetworkMessage::GetPointer() const
{
return m_pData;
}
void CServiceNetworkMessage::AddRef()
{
CryInterlockedIncrement(&m_refCount);
}
void CServiceNetworkMessage::Release()
{
if (0 == CryInterlockedDecrement(&m_refCount))
{
delete this;
}
}
IDataReadStream* CServiceNetworkMessage::CreateReader() const
{
return new CDataReadStreamFormMessage(this);
}
//-----------------------------------------------------------------------------
void CServiceNetworkConnection::Header::Swap()
{
// if we are on big endian system swap data to LE
// NOTE: this is a little bit confusing so see how the eLittleEndian and eBigEndian is defined
SwapEndian(m_size, eLittleEndian);
}
void CServiceNetworkConnection::InitHeader::Swap()
{
// if we are on big endian system swap data to LE
// NOTE: this is a little bit confusing so see how the eLittleEndian and eBigEndian is defined
SwapEndian(m_tryCount, eLittleEndian);
SwapEndian(m_guid0, eLittleEndian);
SwapEndian(m_guid1, eLittleEndian);
}
//-----------------------------------------------------------------------------
CServiceNetworkConnection::CServiceNetworkConnection(
class CServiceNetwork* manager,
EEndpoint endpointType,
AZSOCKET socket,
const CryGUID& connectionID,
const ServiceNetworkAddress& localAddress,
const ServiceNetworkAddress& remoteAddress)
: m_pManager(manager)
, m_connectionID(connectionID)
, m_socket(socket)
, m_localAddress(localAddress)
, m_remoteAddress(remoteAddress)
, m_endpointType(endpointType)
, m_state(eState_Initializing)
, m_sendQueueDataSize(0)
, m_receiveQueueDataSize(0)
, m_messageDataSentSoFar(0)
, m_messageDataReceivedSoFar(0)
, m_bCloseRequested(false)
, m_pCurrentReceiveMessage(NULL)
, m_messageReceiveLength(0)
, m_messageDummyReadLength(0)
, m_reconnectTryCount(0)
, m_bDisableCommunication(false)
, m_pSendedMessages(NULL)
, m_refCount(1)
{
// put the socket back in non blocking mode
AZ::AzSock::SetSocketBlockingMode(m_socket, false);
// reset stats
m_statsNumDataSend = 0;
m_statsNumDataReceived = 0;
m_statsNumPacketsSend = 0;
m_statsNumPacketsReceived = 0;
// reset timers to values at the creation time
const uint64 currentNetworkTime = m_pManager->GetNetworkTime();
m_lastReconnectTime = currentNetworkTime;
m_lastMessageReceivedTime = currentNetworkTime;
m_lastInitializationSendTime = currentNetworkTime;
// make sure keep alive messages are sent as soon as possible
m_lastKeepAliveSendTime = currentNetworkTime - kKeepAlivePeriod;
LOG_VERBOSE(3, "Connection(): local='%s', remote='%s', this=%p",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this);
}
CServiceNetworkConnection::~CServiceNetworkConnection()
{
// in here we must be already closed!
CRY_ASSERT(m_state == eState_Closed);
LOG_VERBOSE(3, "~Connection(): local='%s', remote='%s', this=%p",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this);
// can happen
if (m_pCurrentReceiveMessage != NULL)
{
m_pCurrentReceiveMessage->Release();
m_pCurrentReceiveMessage = NULL;
}
}
void CServiceNetworkConnection::AddRef()
{
CryInterlockedIncrement(&m_refCount);
}
void CServiceNetworkConnection::Release()
{
if (0 == CryInterlockedDecrement(&m_refCount))
{
delete this;
}
}
void CServiceNetworkConnection::Close()
{
LOG_VERBOSE(2, "Connection local='%s', remote='%s', this=%p: close requested",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this);
m_bCloseRequested = true;
m_bDisableCommunication = true;
}
void CServiceNetworkConnection::FlushAndClose(const uint32 timeout)
{
if (!m_bDisableCommunication)
{
// We don't have any messages on the waiting list, we can close immediately
if (m_pSendQueue.empty())
{
// Normal close
Close();
}
else
{
LOG_VERBOSE(2, "Connection local='%s', remote='%s', this=%p: flush and close requested",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this);
// Disable communication layer so no more new messages can be transmitted
m_bDisableCommunication = true;
// Register in the manager list of connections to close after sending queue is empty
m_pManager->RegisterForDeferredClose(*this, timeout);
}
}
}
void CServiceNetworkConnection::FlushAndWait()
{
// Disable communication layer so no more new messages can be transmitted
m_bDisableCommunication = true;
// Wait for the connection to be empty
while (IsAlive() && !m_pSendQueue.empty())
{
Sleep(1);
}
// Resume communication layer
m_bDisableCommunication = false;
}
const CryGUID& CServiceNetworkConnection::GetGUID() const
{
return m_connectionID;
}
const ServiceNetworkAddress& CServiceNetworkConnection::GetRemoteAddress() const
{
return m_remoteAddress;
}
const ServiceNetworkAddress& CServiceNetworkConnection::GetLocalAddress() const
{
return m_localAddress;
}
void CServiceNetworkConnection::Reset()
{
if (m_state == eState_Initializing || m_state == eState_Valid)
{
// Close the socket (we wont be able to use it anyway)
if (AZ::AzSock::IsAzSocketValid(m_socket))
{
AZ::AzSock::Shutdown(m_socket, SD_BOTH);
AZ::AzSock::CloseSocket(m_socket);
m_socket = AZ_SOCKET_INVALID;
}
// Reset messages buffers pointers
m_messageDataSentSoFar = 0;
m_messageDataReceivedSoFar = 0;
// release current in-flight message
if (m_pCurrentReceiveMessage != NULL)
{
m_pCurrentReceiveMessage->Release();
m_pCurrentReceiveMessage = NULL;
}
// reset reconnection timer
m_lastMessageReceivedTime = m_pManager->GetNetworkTime();
m_lastReconnectTime = m_pManager->GetNetworkTime();
// we are in the lost state now, we can try to reconnect
m_state = eState_Lost;
LOG_VERBOSE(2, "Connection local='%s', remote='%s', this=%p: LOST!",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this);
}
}
void CServiceNetworkConnection::Shutdown()
{
// Close the socket
if (AZ::AzSock::IsAzSocketValid(m_socket))
{
AZ::AzSock::Shutdown(m_socket, SD_BOTH);
AZ::AzSock::CloseSocket(m_socket);
m_socket = AZ_SOCKET_INVALID;
}
// Release all pending messages (they wont be sent anyway)
while (!m_pSendQueue.empty())
{
CServiceNetworkMessage* message = m_pSendQueue.pop();
message->Release();
}
// release current in-flight message
if (m_pCurrentReceiveMessage != NULL)
{
m_pCurrentReceiveMessage->Release();
m_pCurrentReceiveMessage = NULL;
}
// Reset internal send/recv state
m_messageDataSentSoFar = 0;
m_messageDataReceivedSoFar = 0;
// Force the state
m_state = eState_Closed;
LOG_VERBOSE(2, "Connection local='%s', remote='%s', this=%p: CLOSED!",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this);
}
void CServiceNetworkConnection::Update()
{
const uint64 currentNetworkTime = m_pManager->GetNetworkTime();
// We requested to close the socket
if (m_bCloseRequested)
{
m_bCloseRequested = false;
Shutdown();
return;
}
// State machine
switch (m_state)
{
// Connection is closed, nothing to do
case eState_Closed:
{
break;
}
// We are still not initialized fully
case eState_Initializing:
{
// receive messages when waiting for connection
ProcessReceivingQueue();
// if we are a client send the connection initialization messages
if (m_endpointType == eEndpoint_Client)
{
// General timeout handling
if (HandleTimeout(currentNetworkTime))
{
// do not send to often
if ((currentNetworkTime - m_lastInitializationSendTime) > kInitializationPerior)
{
// send the initialization message
if (TryInitialize())
{
// message was sent, wait a moment before sending next one
m_lastInitializationSendTime = currentNetworkTime;
}
}
}
}
else if (m_endpointType == eEndpoint_Server)
{
// Server side when waiting for full initialization is sending the "keep alive" messages
// Note that we cannot time out on this end
ProcessKeepAlive();
}
break;
}
// Connection is lost
case eState_Lost:
{
// if we are the client endpoint we can try to reconnect to the server
if (m_endpointType == eEndpoint_Client)
{
// do not try to reconnect to often (floods the network)
if ((currentNetworkTime - m_lastReconnectTime) > kReconnectTryPerior)
{
// reset timer
m_lastReconnectTime = currentNetworkTime;
// try to reconnect
if (TryReconnect())
{
// put the socket back in non blocking mode
AZ::AzSock::SetSocketBlockingMode(m_socket, false);
// give us some slack with timeout after reconnection
m_lastMessageReceivedTime = currentNetworkTime;
// yeah, we got reconnected, try to reinitialize the connection
m_messageDataReceivedSoFar = 0;
m_state = eState_Initializing;
}
}
}
else if (m_endpointType == eEndpoint_Server)
{
// wait for the reconnection timeout
if ((currentNetworkTime - m_lastMessageReceivedTime) > hReconnectTimeOut)
{
// reconnection time out has occurred
LOG_VERBOSE(2, "Connection local='%s', remote='%s', this=%p: reconnection timeout",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this);
// Close
Shutdown();
}
}
break;
}
// Valid connection
case eState_Valid:
{
// Is it time to send a keep alive message?
ProcessKeepAlive();
// Process the queue of messages
ProcessSendingQueue();
ProcessReceivingQueue();
// General timeout handling
if (m_endpointType == eEndpoint_Client)
{
HandleTimeout(currentNetworkTime);
}
break;
}
default:
{
// should not happen
break;
}
}
}
bool CServiceNetworkConnection::HandleTimeout(const uint64 currentNetworkTime)
{
// Connections never time out when there is a debugger attached
#if defined(WIN32) || defined(WIN64)
if (IsDebuggerPresent())
{
// connection is still alive
return true;
}
#endif
// Connection time out when there is a long time without any activity from server side (no keep alive or other messages)
const uint64 timeSinceLastMessage = currentNetworkTime - m_lastMessageReceivedTime;
if (timeSinceLastMessage > kTimeout)
{
// Connection has timed out
LOG_VERBOSE(1, "Connection local='%s', remote='%s', this=%p: timed out",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this);
// Put in lost state, wait a while before reconnecting
m_lastReconnectTime = currentNetworkTime;
m_state = eState_Lost;
// Close the socket now
AZ::AzSock::CloseSocket(m_socket);
m_socket = AZ_SOCKET_INVALID;
// Connection was lost
return false;
}
// Connection still alive
return true;
}
bool CServiceNetworkConnection::TryInitialize()
{
// This is sent only be clients trying to establish connection with server
// Connection ID is sent over to the server so he can easily identify re connections (even when port changes)
// Initialization data header
InitHeader header;
header.m_cmd = eCommand_Initialize;
header.m_pad0 = 0;
header.m_pad1 = 0;
header.m_pad2 = 0;
header.m_tryCount = m_reconnectTryCount;
header.m_guid0 = m_connectionID.lopart;
header.m_guid1 = m_connectionID.hipart;
// Swap the endianess in header (for sending)
header.Swap();
// Try send
const bool autoHandleErrors = false; // we do not need errors here
const uint32 dataLeft = sizeof(header) - m_messageDataSentSoFar;
const uint32 ret = TrySend(&header, dataLeft, autoHandleErrors);
m_messageDataSentSoFar += ret;
// Full packet was sent
if (m_messageDataSentSoFar == sizeof(header))
{
// We sent the initialization message
LOG_VERBOSE(1, "Connection local='%s', remote='%s', this=%p: init message sent, try counter=%d",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this,
m_reconnectTryCount);
// we sent the initialization packet, reset
m_messageDataSentSoFar = 0;
return true;
}
// Still not valid
return false;
}
bool CServiceNetworkConnection::TryReconnect()
{
// We can't reconnect with disabled communication
if (m_bDisableCommunication)
{
return false;
}
// Create new socket if needed
if (!AZ::AzSock::IsAzSocketValid(m_socket))
{
m_socket = AZ::AzSock::Socket();
if (!AZ::AzSock::IsAzSocketValid(m_socket))
{
// We sent the initialization message
LOG_VERBOSE(1, "Connection local='%s', remote='%s', this=%p: failed to recreate socket",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this);
return false;
}
}
// Translate remote address
AZ::AzSock::AzSocketAddress addr;
TranslateAddress(m_remoteAddress, addr);
// When reconnecting always use the blocking mode
AZ::AzSock::SetSocketBlockingMode(m_socket, true);
// every time we reconnect increment the internal counter so the receiving end (server)
// will be able to identity the up-to-date connection (and discard the older one)
CRY_ASSERT(m_endpointType == eEndpoint_Client);
m_reconnectTryCount += 1;
// Connect (blocking)
const int result = AZ::AzSock::Connect(m_socket, addr);
if (result == 0)
{
// Spew to log (important info)
LOG_VERBOSE(0, "Connection local='%s', remote='%s', this=%p: SUCCESSFULLY RECONNECTED",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this);
// When connected put socket in the non blocking mode
AZ::AzSock::SetSocketBlockingMode(m_socket, false);
// connected!
return true;
}
// not connection, should not happen often
LOG_VERBOSE(2, "Connection local='%s', remote='%s', this=%p: failed to reconnect",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this);
// still not connected
return false;
}
void CServiceNetworkConnection::SendKeepAlive(const uint64 currentNetworkTime)
{
// Keep alive message is just ONE byte (makes it easier)
uint8 message = eCommand_KeepAlive;
if (1 == TrySend(&message, 1, false))
{
// Throttle the sending
m_lastKeepAliveSendTime = currentNetworkTime;
// At high verbose level we need even this :)
LOG_VERBOSE(3, "Connection local='%s', remote='%s', this=%p: keep alive SENT",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this);
}
}
void CServiceNetworkConnection::ProcessSendingQueue()
{
// Get the top message from the send queue
if (NULL == m_pSendedMessages)
{
m_pSendedMessages = m_pSendQueue.pop();
if (NULL == m_pSendedMessages)
{
return;
}
}
// Get the size of the data to transmit
const uint32 messageSize = m_pSendedMessages->GetSize();
const uint32 headerSize = sizeof(Header);
// Nothing sent yet, send header
if (m_messageDataSentSoFar < headerSize)
{
// prepare header - endian safe
Header header;
header.m_cmd = eCommand_Data;
header.m_size = messageSize;
// Swap the header for reading
header.Swap();
// send the header
const uint32 dataLeft = headerSize - m_messageDataSentSoFar;
const uint32 sent = TrySend((const char*)&header + m_messageDataSentSoFar, dataLeft, true);
m_messageDataSentSoFar += sent;
}
// Send message data
const uint32 endOfDataOffset = messageSize + headerSize;
if (m_messageDataSentSoFar >= headerSize && m_messageDataSentSoFar < endOfDataOffset)
{
// send and advance
const uint32 dataLeft = endOfDataOffset - m_messageDataSentSoFar;
const uint32 dataOffset = m_messageDataSentSoFar - headerSize;
const uint32 sent = TrySend((const char*)m_pSendedMessages->GetPointer() + dataOffset, dataLeft, true);
m_messageDataSentSoFar += sent;
}
// All the data from the message was sent, release the message from this queue
// Note: this message may still be in some other sent queues for connections
if (m_messageDataSentSoFar >= endOfDataOffset)
{
// At high verbose level we need even this :)
LOG_VERBOSE(3, "Connection local='%s', remote='%s', this=%p: message ID %d (size=%d) removed from queue",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this,
m_pSendedMessages->GetId(),
m_pSendedMessages->GetSize());
CryInterlockedAdd(&m_sendQueueDataSize, -(int)m_pSendedMessages->GetSize());
// stats (not collected in release builds)
#ifndef RELEASE
CryInterlockedIncrement((volatile int*) &m_statsNumPacketsSend);
#endif
// release local message reference
m_pSendedMessages->Release();
m_pSendedMessages = NULL;
// rewind to zero to indicate fresh message
m_messageDataSentSoFar = 0;
}
}
void CServiceNetworkConnection::ProcessKeepAlive()
{
const uint64 currentNetworkTime = m_pManager->GetNetworkTime();
if ((currentNetworkTime - m_lastKeepAliveSendTime) > kKeepAlivePeriod)
{
// Well, if we are in the middle of something make sure we do not interrupt it with KeepAlive
if (m_messageDataSentSoFar == 0)
{
SendKeepAlive(currentNetworkTime);
}
}
}
void CServiceNetworkConnection::ProcessReceivingQueue()
{
// To much data already, do not process
const uint32 kReceivedDataLimit = m_pManager->GetReceivedDataQueueLimit();
if (m_receiveQueueDataSize > kReceivedDataLimit)
{
return;
}
// Internal offset
const uint32 kOffsetHeader = 1;
const uint32 kOffsetData = 5;
// Dummy receive
while (m_messageDummyReadLength > 0)
{
// batch size
const uint32 kTempBufferSize = 256;
// read dummy data
uint8 tempBuffer[ kTempBufferSize ];
const uint32 maxRead = min(kTempBufferSize, m_messageDummyReadLength);
const uint32 readCount = TryReceive(tempBuffer, maxRead, false);
m_messageDummyReadLength -= readCount;
// got less
if (readCount < maxRead)
{
break;
}
}
// Do not process normal messages until we receive all of the bogus data
if (m_messageDummyReadLength > 0)
{
return;
}
// First byte - header
if (m_messageDataReceivedSoFar == 0)
{
// message header, read type
uint8 messageType = 0;
const int result = TryReceive(&messageType, 1, true);
if (result == 1)
{
// keep alive received, if we are not yet fully initialized it's the signal that we are :)
if (messageType == eCommand_KeepAlive)
{
// we got confirmed by server
if (m_state == eState_Initializing)
{
// change state
CRY_ASSERT(m_endpointType == eEndpoint_Client);
m_state = eState_Valid;
// At high verbose level we need even this :)
LOG_VERBOSE(1, "Connection local='%s', remote='%s', this=%p: connection confirmed by server",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this);
}
else
{
// At low-level verbose log even this
LOG_VERBOSE(3, "Connection local='%s', remote='%s', this=%p: keep alive RECEIVED",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this);
}
// update the keep alive data timer
m_lastKeepAliveSendTime = m_pManager->GetNetworkTime();
m_lastMessageReceivedTime = m_pManager->GetNetworkTime();
}
else if (messageType == eCommand_Data)
{
// wait for the message length
m_messageReceiveLength = 0;
m_messageDataReceivedSoFar = kOffsetHeader;
m_lastMessageReceivedTime = m_pManager->GetNetworkTime();
// At low-level verbose log even this
LOG_VERBOSE(3, "Connection local='%s', remote='%s', this=%p: got data message header",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this);
}
else if (messageType == eCommand_Initialize)
{
// let the system process the message
m_messageDummyReadLength = sizeof(InitHeader) - 1;
// At low-level verbose log even this
LOG_VERBOSE(3, "Connection local='%s', remote='%s', this=%p: outdated initheader received",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this);
}
else
{
// Serious error
LOG_VERBOSE(0, "Connection local='%s', remote='%s', this=%p: received invalid command (%d)",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this,
messageType);
// reset the connection
Reset();
}
}
}
// Message length
if (m_messageDataReceivedSoFar >= kOffsetHeader && m_messageDataReceivedSoFar < kOffsetData)
{
// receive the message length
const uint32 dataOffset = m_messageDataReceivedSoFar - kOffsetHeader;
const uint32 dataLeft = sizeof(uint32) - dataOffset;
const uint32 len = TryReceive((char*)&m_messageReceiveLength + dataOffset, dataLeft, true);
m_messageDataReceivedSoFar += len;
// Update last message time
if (len > 0)
{
m_lastMessageReceivedTime = m_pManager->GetNetworkTime();
}
// full length received
if (m_messageDataReceivedSoFar == 5)
{
// Swap endianess (for BE platforms)
// NOTE: if this is a little bit confusing, see how the eLittleEndian and eBigEndian are defined
SwapEndian(m_messageReceiveLength, eLittleEndian);
// Sanity check on the message size
if (m_messageReceiveLength > kMaximumMessageSize)
{
// Serious error
LOG_VERBOSE(0, "Connection local='%s', remote='%s', this=%p: unsupported message size (%d)",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this,
m_messageReceiveLength);
Reset();
}
else if (m_messageReceiveLength > 0)
{
// Create new message that we will add the data into
CRY_ASSERT(m_pCurrentReceiveMessage == NULL);
m_pCurrentReceiveMessage = static_cast< CServiceNetworkMessage* >(m_pManager->AllocMessageBuffer(m_messageReceiveLength));
CRY_ASSERT(m_pCurrentReceiveMessage != NULL);
// Serious error
LOG_VERBOSE(2, "Connection local='%s', remote='%s', this=%p: created receive buffer ID %d, (size=%d)",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this,
m_pCurrentReceiveMessage ? m_pCurrentReceiveMessage->GetId() : 0,
m_messageReceiveLength);
}
}
}
// Message data
if (m_messageDataReceivedSoFar >= kOffsetData)
{
CRY_ASSERT(m_pCurrentReceiveMessage != NULL);
PREFAST_ASSUME(m_pCurrentReceiveMessage);
// Message fully received, put in the receive queue (at the end!)
const uint32 dataOffset = m_messageDataReceivedSoFar - kOffsetData;
const uint32 dataLeft = m_pCurrentReceiveMessage->GetSize() - dataOffset;
const uint32 len = TryReceive((char*)m_pCurrentReceiveMessage->GetPointer() + dataOffset, dataLeft, true);
m_messageDataReceivedSoFar += len;
// connection got lost
if (m_state == eState_Lost)
{
return;
}
// Update last message time
if (len > 0)
{
m_lastMessageReceivedTime = m_pManager->GetNetworkTime();
}
// Full message received!
if (m_messageDataReceivedSoFar == (kOffsetData + m_pCurrentReceiveMessage->GetSize()))
{
// Serious error
LOG_VERBOSE(2, "Connection local='%s', remote='%s', this=%p: full message received(%d), adding to queue",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this,
m_pCurrentReceiveMessage->GetSize());
// Put in the receive queue, only if no communication is disabled
if (m_bDisableCommunication)
{
m_pCurrentReceiveMessage->Release();
}
else
{
m_pReceiveQueue.push(m_pCurrentReceiveMessage);
}
// Stats (not collected in release builds)
#ifndef RELEASE
CryInterlockedIncrement((volatile int*) &m_statsNumPacketsReceived);
#endif
// Reset
m_pCurrentReceiveMessage = NULL;
m_messageDataReceivedSoFar = 0;
}
}
}
uint32 CServiceNetworkConnection::TrySend(const void* dataBuffer, uint32 dataSize, bool autoHandleErrors /*=true*/)
{
// Send the data
const int ret = AZ::AzSock::Send(m_socket, (const char*) dataBuffer, dataSize, 0);
if (AZ::AzSock::SocketErrorOccured(ret))
{
// We would block, that's not an error
if (ret == static_cast<AZ::s32>(AZ::AzSock::AzSockError::eASE_EWOULDBLOCK))
{
return 0;
}
// Report connection problems
LOG_VERBOSE(1, "Connection local='%s', remote='%s', this=%p: send() error: %d",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this,
ret);
// Put connection in the lost state
if (autoHandleErrors)
{
Reset();
}
// Nothing was sent (according to our logic)
return 0;
}
// Update stats
#ifndef RELEASE
CryInterlockedAdd((volatile int*) &m_statsNumDataSend, ret);
#endif
// Return the true amount of data sent
return ret;
}
uint32 CServiceNetworkConnection::TryReceive(void* dataBuffer, uint32 dataSize, bool autoHandleErrors)
{
// Send the data
const int ret = AZ::AzSock::Recv(m_socket, (char*) dataBuffer, dataSize, 0);
if (AZ::AzSock::SocketErrorOccured(ret))
{
// We would block, that's not an error
if (ret == static_cast<AZ::s32>(AZ::AzSock::AzSockError::eASE_EWOULDBLOCK))
{
return 0;
}
// Connection was closed
if (ret == static_cast<AZ::s32>(AZ::AzSock::AzSockError::eASE_ECONNRESET))
{
// Report connection problems
LOG_VERBOSE(1, "Connection local='%s', remote='%s', this=%p: CLOSED BY PEER",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this);
// Shutdown socket
Shutdown();
return 0;
}
// Report connection problems
LOG_VERBOSE(1, "Connection local='%s', remote='%s', this=%p: recv() error: %d",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this,
ret);
// Put connection in the lost state
if (autoHandleErrors)
{
Reset();
}
// Nothing was sent (according to our logic)
return 0;
}
// Update stats
#ifndef RELEASE
CryInterlockedAdd((volatile int*) &m_statsNumDataReceived, ret);
#endif
// Return the true amount of data sent
return ret;
}
bool CServiceNetworkConnection::SendMsg(IServiceNetworkMessage* message)
{
// Invalid message
if (NULL == message || message->GetSize() == 0)
{
return false;
}
// Communication layer is disabled, not possible to send any more messages
if (m_bDisableCommunication)
{
return false;
}
// Process data size limits
{
const uint32 sizeAfterThisMessage = m_sendQueueDataSize + message->GetSize();
const uint32 sendQueueLimit = m_pManager->GetSendDataQueueLimit();
if (sizeAfterThisMessage > sendQueueLimit)
{
// Report connection problems
LOG_VERBOSE(0, "Connection local='%s', remote='%s', this=%p: to much data on send queue",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this);
// To much data on the queue already, we will not be sending this message
return false;
}
// Keep the local reference so the source data does not get deleted
message->AddRef();
// Update the queue data size
CryInterlockedAdd(&m_sendQueueDataSize, message->GetSize());
// Append the message to the sending queue
m_pSendQueue.push(static_cast< CServiceNetworkMessage* >(message));
}
// Well, we can't tell any more than that
return true;
}
IServiceNetworkMessage* CServiceNetworkConnection::ReceiveMsg()
{
// Anything on the queue ?
IServiceNetworkMessage* message = NULL;
if (!m_pReceiveQueue.empty())
{
message = m_pReceiveQueue.pop();
// Report connection problems
LOG_VERBOSE(3, "Connection local='%s', remote='%s', this=%p: message ID %d (size=%d) popped by receive end",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this,
message->GetId(),
message->GetSize());
}
return message;
}
bool CServiceNetworkConnection::IsAlive() const
{
return m_state != eState_Closed;
}
uint32 CServiceNetworkConnection::GetMessageSendCount() const
{
return m_statsNumPacketsSend;
}
uint32 CServiceNetworkConnection::GetMessageReceivedCount() const
{
return m_statsNumPacketsReceived;
}
uint64 CServiceNetworkConnection::GetMessageSendDataSize() const
{
return m_statsNumDataSend;
}
uint64 CServiceNetworkConnection::GetMessageReceivedDataSize() const
{
return m_statsNumDataReceived;
}
bool CServiceNetworkConnection::HandleReconnect(AZSOCKET socket, const uint32 tryCount)
{
CRY_ASSERT(m_endpointType == eEndpoint_Server);
// connection is older
if (tryCount < m_reconnectTryCount)
{
LOG_VERBOSE(3, "Connection local='%s', remote='%s', this=%p: reconnection request OLDER (%d<%d)",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this,
tryCount,
m_reconnectTryCount);
return false;
}
// should not happen
if (tryCount == m_reconnectTryCount)
{
LOG_VERBOSE(0, "Connection local='%s', remote='%s', this=%p: reconnection request COLLISION (%d==%d)",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this,
tryCount,
m_reconnectTryCount);
return false;
}
// newer connection, close current socket
AZ::AzSock::Shutdown(m_socket, SD_BOTH);
AZ::AzSock::CloseSocket(m_socket);
// reset send/receive counters (will resend last message from the queue)
m_messageDataReceivedSoFar = 0;
m_messageDataSentSoFar = 0;
// Set new socket and update reconnection counter
m_socket = socket;
m_reconnectTryCount = tryCount;
// revive the connection
m_state = eState_Valid;
LOG_VERBOSE(0, "Connection local='%s', remote='%s', this=%p: successfull reconnection with counter (%d)",
m_localAddress.ToString().c_str(),
m_remoteAddress.ToString().c_str(),
(UINT_PTR) this,
m_reconnectTryCount);
// processed
return true;
}
//-----------------------------------------------------------------------------
CServiceNetworkListener::CServiceNetworkListener(CServiceNetwork* pManager, AZSOCKET socket, const ServiceNetworkAddress& address)
: m_pManager(pManager)
, m_socket(socket)
, m_localAddress(address)
, m_closeRequestReceived(false)
, m_refCount(1)
{
LOG_VERBOSE(3, "Listener() local='%s', this=%p",
m_localAddress.ToString().c_str(),
(UINT_PTR) this);
}
CServiceNetworkListener::~CServiceNetworkListener()
{
AZ_Assert(!AZ::AzSock::IsAzSocketValid(m_socket), "AZSocket still valid on ServiceNetworkListener destructor");
LOG_VERBOSE(3, "~Listener() local='%s', this=%p",
m_localAddress.ToString().c_str(),
(UINT_PTR) this);
}
void CServiceNetworkListener::Update()
{
// We requested to close this listener
if (m_closeRequestReceived)
{
LOG_VERBOSE(3, "Listener local='%s', this=%p: closing due to request",
m_localAddress.ToString().c_str(),
(UINT_PTR) this);
// Close the socket
if (AZ::AzSock::IsAzSocketValid(m_socket))
{
AZ::AzSock::CloseSocket(m_socket);
m_socket = AZ_SOCKET_INVALID;
}
// Close all connections
for (TConnectionList::iterator it = m_pLocalConnections.begin();
it != m_pLocalConnections.end(); ++it)
{
(*it)->Close();
(*it)->Release();
}
m_pLocalConnections.clear();
m_closeRequestReceived = false;
return;
}
// Process connection requests
ProcessIncomingConnections();
// Service the pending connection
ProcessPendingConnections();
// Remove any local connection that got dead
for (TConnectionList::iterator it = m_pLocalConnections.begin();
it != m_pLocalConnections.end(); /*++it*/)
{
if (!(*it)->IsAlive())
{
LOG_VERBOSE(2, "Listener local='%s', this=%p: removing dead connection '%s' (%p)",
m_localAddress.ToString().c_str(),
(UINT_PTR) this,
(*it)->GetRemoteAddress().ToString().c_str(),
(UINT_PTR) (*it));
(*it)->Release();
it = m_pLocalConnections.erase(it);
}
else
{
++it;
}
}
}
const ServiceNetworkAddress& CServiceNetworkListener::GetLocalAddress() const
{
return m_localAddress;
}
uint32 CServiceNetworkListener::GetConnectionCount() const
{
// not safe ?
return m_pLocalConnections.size();
}
IServiceNetworkConnection* CServiceNetworkListener::Accept()
{
// Look for any connection on the list that is in the "initialized" state
{
CryAutoLock<CryMutex> lock(m_accessLock);
for (TConnectionList::iterator it = m_pLocalConnections.begin();
it != m_pLocalConnections.end(); ++it)
{
// we are looking for connections in the "eState_Initializing" which mean that they are valid but not yet recognized by the outside world
CServiceNetworkConnection* con = (*it);
if (con->m_state == CServiceNetworkConnection::eState_Initializing)
{
LOG_VERBOSE(1, "Listener local='%s', this=%p: accepting connection from '%s' (%p)",
m_localAddress.ToString().c_str(),
(UINT_PTR) this,
con->GetRemoteAddress().ToString().c_str(),
(UINT_PTR) con);
// switch state to "valid"
con->m_state = CServiceNetworkConnection::eState_Valid;
// when returning outside increment the ref count (we still want to keep our internal reference)
con->AddRef();
return con;
}
}
}
// No pending connections
return NULL;
}
bool CServiceNetworkListener::IsAlive() const
{
return AZ::AzSock::IsAzSocketValid(m_socket);
}
void CServiceNetworkListener::AddRef()
{
CryInterlockedIncrement(&m_refCount);
}
void CServiceNetworkListener::Release()
{
if (0 == CryInterlockedDecrement(&m_refCount))
{
delete this;
}
}
void CServiceNetworkListener::Close()
{
LOG_VERBOSE(2, "Listener local='%s', this=%p: close requested",
m_localAddress.ToString().c_str(),
(UINT_PTR) this);
m_closeRequestReceived = true;
}
void CServiceNetworkListener::ProcessIncomingConnections()
{
// Accept all possible connections as soon as possible (the connect side is blocking)
for (;; )
{
// Get the pending connection from TCP/IP layers
AZ::AzSock::AzSocketAddress remoteAddrInet;
AZSOCKET sock = AZ::AzSock::Accept(m_socket, remoteAddrInet);
// No more connections
if (SocketConnectionsFull(AZ::AzSock::AzSockError(sock)))
{
break;
}
// Different error
if (!AZ::AzSock::IsAzSocketValid(sock))
{
// Connection has other problems
LOG_VERBOSE(1, "Listener local='%s', this=%p: accept() error: %d",
m_localAddress.ToString().c_str(),
(UINT_PTR) this,
(int)sock);
break;
}
// Get the remote address
ServiceNetworkAddress remoteAddress;
TranslateAddress(remoteAddrInet, remoteAddress);
// Any way create a new pending connection information
PendingConnection* pendingConnection = new PendingConnection;
pendingConnection->m_remoteAddress = remoteAddress;
pendingConnection->m_dataReceivedSoFar = 0;
pendingConnection->m_socket = sock;
// Connection has other problems
LOG_VERBOSE(2, "Listener local='%s', this=%p: new pending connection from '%s'",
m_localAddress.ToString().c_str(),
(UINT_PTR) this,
remoteAddress.ToString().c_str());
// Add to list (not locked because its used only from net thread)
m_pPendingConnections.push_back(pendingConnection);
}
}
void CServiceNetworkListener::ProcessPendingConnections()
{
// Process only pending connections, not locked because its used only from net thread
for (TPendingConnectionList::iterator it = m_pPendingConnections.begin();
it != m_pPendingConnections.end(); /*++it*/)
{
PendingConnection& con = *(*it);
// Read the incoming data
CRY_ASSERT(con.m_dataReceivedSoFar < sizeof(con.m_initHeader));
const uint32 dataLeft = sizeof(con.m_initHeader) - con.m_dataReceivedSoFar;
const int size = AZ::AzSock::Recv(con.m_socket, (char*)&con.m_initHeader + con.m_dataReceivedSoFar, dataLeft, 0);
// The only no-action case: no data yet
if (size == static_cast<AZ::s32>(AZ::AzSock::AzSockError::eASE_EWOULDBLOCK))
{
++it;
continue;
}
// Something more important
if (AZ::AzSock::SocketErrorOccured(size))
{
// well, some problem on the way, remove the pending connection from the list (client will resend)
LOG_VERBOSE(1, "Listener local='%s', this=%p: pending connection from '%s' lost: %d",
m_localAddress.ToString().c_str(),
(UINT_PTR) this,
con.m_remoteAddress.ToString().c_str(),
(int)size);
}
else
{
// data was received
CRY_ASSERT(size < (int)dataLeft);
con.m_dataReceivedSoFar += size;
// still not enough data
if (con.m_dataReceivedSoFar < sizeof(con.m_initHeader))
{
++it;
continue;
}
// validate header
if (con.m_initHeader.m_cmd == CServiceNetworkConnection::eCommand_Initialize)
{
// Swap the header after reading
con.m_initHeader.Swap();
// Extract the connection ID
const CryGUID connectionId = CryGUID::Construct(con.m_initHeader.m_guid0, con.m_initHeader.m_guid1);
// try find existing connection with the same connection GUID (reconnection)
CServiceNetworkConnection* existingConnection = NULL;
for (TConnectionList::const_iterator jt = m_pLocalConnections.begin();
jt != m_pLocalConnections.end(); ++jt)
{
if ((*jt)->GetGUID() == connectionId)
{
existingConnection = *jt;
break;
}
}
// if existing connection was found that we probably were trying to reconnect
if (existingConnection != NULL)
{
// we already have this connection on our list and we got reconnected with the some GUID
// this usually means that the client has lost communication with server for some time
LOG_VERBOSE(1, "Listener local='%s', this=%p: reconnection from '%s'",
m_localAddress.ToString().c_str(),
(UINT_PTR) this,
con.m_remoteAddress.ToString().c_str());
// substitute the connection with newer one (if it is really newer)
if (!existingConnection->HandleReconnect(con.m_socket, con.m_initHeader.m_tryCount))
{
// well, we didn't use this connect (it was older than the current one, close it)
AZ::AzSock::Shutdown(con.m_socket, SD_BOTH);
AZ::AzSock::CloseSocket(con.m_socket);
}
else
{
// add to global list of active debug connections (so we can start receiving data)
m_pManager->RegisterConnection(*existingConnection);
}
}
else
{
// no previous connection registered, create one now
CServiceNetworkConnection* newConnection = new CServiceNetworkConnection(
m_pManager,
CServiceNetworkConnection::eEndpoint_Server, // this connection is created from listener side which is considered the "server"
con.m_socket,
connectionId,
m_localAddress,
con.m_remoteAddress);
// this is the first time we see this connection on set the proper connection counter
newConnection->m_reconnectTryCount = con.m_initHeader.m_tryCount;
// happy moment, log it
LOG_VERBOSE(0, "Listener local='%s', this=%p: confirmed connection from '%s'",
m_localAddress.ToString().c_str(),
(UINT_PTR) this,
con.m_remoteAddress.ToString().c_str());
// make sure connection is in valid state
CRY_ASSERT(newConnection->m_state == CServiceNetworkConnection::eState_Initializing);
// add to local list
CRY_ASSERT(newConnection->IsInitialized() == false);
{
CryAutoLock<CryMutex> lock(m_accessLock);
m_pLocalConnections.push_back(newConnection);
newConnection->AddRef();
}
// add to global list of active debug connections (so we can start receiving data)
m_pManager->RegisterConnection(*newConnection);
}
}
else
{
// well, some problem on the way, remove the pending connection from the list (client will resend)
LOG_VERBOSE(0, "Listener local='%s', this=%p: invalid connection data received from '%s'",
m_localAddress.ToString().c_str(),
(UINT_PTR) this,
con.m_remoteAddress.ToString().c_str());
// close the socket
AZ::AzSock::Shutdown(con.m_socket, SD_BOTH);
AZ::AzSock::CloseSocket(con.m_socket);
}
}
// any way, delete the connection from the pending list
delete (*it);
it = m_pPendingConnections.erase(it);
}
}
//-----------------------------------------------------------------------------
CServiceNetwork::CServiceNetwork()
: m_networkTime(0)
, m_bExitRequested(false)
, m_bufferID(1)
{
// Create the CVAR
m_pVerboseLevel = gEnv->pConsole->RegisterInt("net_debugVerboseLevel", 0, VF_DEV_ONLY);
// Send/receive Queue size limits
m_pReceiveDataQueueLimit = gEnv->pConsole->RegisterInt("net_receiveQueueSize", 20 << 20, VF_DEV_ONLY);
m_pSendDataQueueLimit = gEnv->pConsole->RegisterInt("net_sendQueueSize", 5 << 20, VF_DEV_ONLY);
// Reinitialize the random number generator with independent seed value
m_guidGenerator.Seed((uint32)GetNetworkTime());
// Start thread
m_pThread = new TServiceNetworkThread();
m_pThread->Start(*this);
}
CServiceNetwork::~CServiceNetwork()
{
// Signal the network thread to stop
if (NULL != m_pThread)
{
m_pThread->Cancel();
m_pThread->WaitForThread();
delete m_pThread;
}
// Release all closeing connections
for (TConnectionsToCloseArray::const_iterator it = m_connectionsToClose.begin();
it != m_connectionsToClose.end(); ++it)
{
(*it).pConnection->Release();
}
// Release and close all connections
for (TConnectionArray::const_iterator it = m_pConnections.begin();
it != m_pConnections.end(); ++it)
{
(*it)->Close();
(*it)->Release();
}
// Release all listeners
for (TListenerArray::const_iterator it = m_pListeners.begin();
it != m_pListeners.end(); ++it)
{
(*it)->Release();
}
// Release the CVars
SAFE_RELEASE(m_pVerboseLevel);
SAFE_RELEASE(m_pReceiveDataQueueLimit);
SAFE_RELEASE(m_pSendDataQueueLimit);
}
#ifndef RELEASE
bool CServiceNetwork::CheckVerbose(const uint32 level) const
{
const int verboseLevel = m_pVerboseLevel->GetIVal();
return (int)level < verboseLevel;
}
void CServiceNetwork::Log(const char* txt, ...) const
{
// format the print buffer
char buffer[512];
va_list ap;
va_start(ap, txt);
vsprintf_s(buffer, sizeof(buffer), txt, ap);
va_end(ap);
// pass to log
gEnv->pLog->LogAlways(buffer);
}
#endif
void CServiceNetwork::Cancel()
{
m_bExitRequested = true;
if (m_pThread)
{
m_pThread->Stop();
}
}
void CServiceNetwork::Run()
{
CryThreadSetName(THREADID_NULL, "ServiceNetworkThread");
#if defined(AZ_RESTRICTED_PLATFORM)
#include AZ_RESTRICTED_FILE(ServiceNetwork_cpp)
#endif
TListenerArray updatingListeners;
TConnectionArray updatingConnections;
TConnectionsToCloseArray updatingConnectionsToClose;
// Process messages
while (!m_bExitRequested)
{
// Well, copy the lists for the duration of update
{
CryAutoLock<CryMutex> lock(m_accessMutex);
updatingListeners = m_pListeners;
updatingConnections = m_pConnections;
updatingConnectionsToClose = m_connectionsToClose;
}
if ((!gEnv) || (!gEnv->pTimer))
{
Sleep(5);
continue;
}
// Update network time
m_networkTime = gEnv->pTimer->GetAsyncTime().GetMilliSecondsAsInt64();
// Process the listeners (accepts and pending connections)
for (TListenerArray::const_iterator it = updatingListeners.begin();
it != updatingListeners.end(); ++it)
{
(*it)->Update();
// Remove dead listeners from the main list
if (!(*it)->IsAlive())
{
CryAutoLock<CryMutex> lock(m_accessMutex);
// remove from array
TListenerArray::iterator jt = std::find(m_pListeners.begin(), m_pListeners.end(), *it);
CRY_ASSERT(jt != m_pListeners.end());
m_pListeners.erase(jt);
// release internal reference (may delete object if no longer used on main thread)
(*it)->Release();
}
}
// Process the closing connections
for (TConnectionsToCloseArray::const_iterator it = updatingConnectionsToClose.begin();
it != updatingConnectionsToClose.end(); ++it)
{
const ConnectionToClose& info = *it;
bool bTimeout = false;
if (info.maxWaitTime && m_networkTime > info.maxWaitTime)
{
bTimeout = true;
}
// should we close it now ?
if (bTimeout || !it->pConnection->IsAlive() || it->pConnection->IsSendingQueueEmpty())
{
info.pConnection->Close();
info.pConnection->Release();
// erase from list
{
CryAutoLock<CryMutex> lock(m_accessMutex);
for (TConnectionsToCloseArray::iterator jt = m_connectionsToClose.begin();
jt != m_connectionsToClose.end(); ++jt)
{
if ((*jt).pConnection == info.pConnection)
{
m_connectionsToClose.erase(jt);
break;
}
}
}
}
}
// Process the connections
for (TConnectionArray::const_iterator it = updatingConnections.begin();
it != updatingConnections.end(); ++it)
{
(*it)->Update();
// Remove dead connections from the main list
if (!(*it)->IsAlive())
{
CryAutoLock<CryMutex> lock(m_accessMutex);
// remove from array
TConnectionArray::iterator jt = std::find(m_pConnections.begin(), m_pConnections.end(), *it);
CRY_ASSERT(jt != m_pConnections.end());
m_pConnections.erase(jt);
// release internal reference (may delete object if no longer used on main thread)
(*it)->Release();
}
}
// Internal delay
// TODO: this is guess work right now
Sleep(5);
}
}
void CServiceNetwork::SetVerbosityLevel(const uint32 level)
{
// propagate the value to CVar (so it is consistent across the engine)
if (NULL != m_pVerboseLevel)
{
m_pVerboseLevel->Set((int)level);
}
}
IServiceNetworkMessage* CServiceNetwork::AllocMessageBuffer(const uint32 size)
{
// Allocate message with new ID
const uint32 bufferID = CryInterlockedIncrement(&m_bufferID);
return new CServiceNetworkMessage(bufferID, size);
}
IDataWriteStream* CServiceNetwork::CreateMessageWriter()
{
return new CDataWriteStreamBuffer();
}
IDataReadStream* CServiceNetwork::CreateMessageReader(const void* pData, const uint32 dataSize)
{
if (pData != NULL && dataSize > 0)
{
return new CDataReadStreamMemoryBuffer(pData, dataSize);
}
return NULL;
}
ServiceNetworkAddress CServiceNetwork::GetHostAddress(const string& addressString, uint16 optionalPort /*=0*/) const
{
// cut the address into base and port part
string hostname = addressString.c_str();
const int pos = addressString.rfind(':');
if (pos != -1)
{
// substitute the port number from the part in string
if (optionalPort == 0)
{
const char* portNumberStr = addressString.c_str() + pos + 1;
optionalPort = (uint16)atoi(portNumberStr);
}
// remove the port part from base address
hostname = addressString.Left(pos);
}
AZ::AzSock::AzSocketAddress socketAddress;
socketAddress.SetAddress(hostname.c_str(), optionalPort);
// log on hi verbose mode
LOG_VERBOSE(3, "GetHostAddress(%s) -> %s", addressString.c_str(), socketAddress.GetAddress().c_str());
// format the network address
ServiceNetworkAddress out;
TranslateAddress(socketAddress, out);
return out;
}
IServiceNetworkListener* CServiceNetwork::CreateListener(uint16 localPort)
{
// Create socket
AZSOCKET createdSocket = AZ::AzSock::Socket();
if (!AZ::AzSock::IsAzSocketValid(createdSocket))
{
// Connection has other problems
LOG_VERBOSE(0, "CreateListener(%d): socket() failed: %s", localPort, AZ::AzSock::GetStringForError(createdSocket));
return NULL;
}
// Disable merging of small blocks to fight high latency connection
// NOTE: consoles support this mode by default
const int ret3 = AZ::AzSock::EnableTCPNoDelay(createdSocket, true);
if (AZ::AzSock::SocketErrorOccured(ret3))
{
// Connection has other problems
LOG_VERBOSE(0, "CreateListener(%d): setsockopt() failed: %s", localPort, AZ::AzSock::GetStringForError(ret3));
AZ::AzSock::CloseSocket(createdSocket);
return NULL;
}
// Reuse address
{
const int optRet = AZ::AzSock::SetSocketOption(createdSocket, AZ::AzSock::AzSocketOption::REUSEADDR, true);
if (AZ::AzSock::SocketErrorOccured(optRet))
{
// Connection has other problems
LOG_VERBOSE(0, "CreateListener(%d): setsockopt() (reuse) failed", localPort);
// cleanup
AZ::AzSock::CloseSocket(createdSocket);
return NULL;
}
}
// Put the listener socket in the non blocking mode
if (!AZ::AzSock::SetSocketBlockingMode(createdSocket, false))
{
// Connection has other problems
LOG_VERBOSE(0, "CreateListener(%d): setsockopt() failed", localPort);
// cleanup
AZ::AzSock::CloseSocket(createdSocket);
return NULL;
}
// Setup local bind address
AZ::AzSock::AzSocketAddress service;
service.SetAddrPort(localPort);
// Bind socket
const int ret = AZ::AzSock::Bind(createdSocket, service);
if (AZ::AzSock::SocketErrorOccured(ret))
{
// Connection has other problems
LOG_VERBOSE(0, "CreateListener(%d): bind() failed: %s", localPort, AZ::AzSock::GetStringForError(createdSocket));
// cleanup
AZ::AzSock::CloseSocket(createdSocket);
return NULL;
}
// Listen for incoming connection requests on the created socket
const int ret2 = AZ::AzSock::Listen(createdSocket, 64 /*backLogSize*/);
if (AZ::AzSock::SocketErrorOccured(ret2))
{
// Connection has other problems
LOG_VERBOSE(0, "CreateListener(%d): listen() failed: %s", localPort, AZ::AzSock::GetStringForError(createdSocket));
// cleanup
AZ::AzSock::CloseSocket(createdSocket);
return NULL;
}
// Get our local address
AZ::AzSock::AzSocketAddress localAddressInet;
AZ::AzSock::GetSockName(createdSocket, localAddressInet);
// Translate to debug network address data
ServiceNetworkAddress localAddress;
TranslateAddress(localAddressInet, localAddress);
// Spew to log (important info)
LOG_VERBOSE(0, "bind() to '%s'", localAddress.ToString().c_str());
// Create the listener wrapper
CServiceNetworkListener* listener = new CServiceNetworkListener(this, createdSocket, localAddress);
// Listener was created
LOG_VERBOSE(0, "CreateListener(%d): listener created, local address=%s", localPort, listener->GetLocalAddress().ToString().c_str());
// Add to list of local listeners
{
CryAutoLock<CryMutex> lock(m_accessMutex);
m_pListeners.push_back(listener);
listener->AddRef();
}
// Return wrapping interface
return listener;
}
void CServiceNetwork::RegisterConnection(CServiceNetworkConnection& con)
{
CryAutoLock<CryMutex> lock(m_accessMutex);
// Make sure to register each connection only once
TConnectionArray::const_iterator it = std::find(m_pConnections.begin(), m_pConnections.end(), &con);
if (it == m_pConnections.end())
{
// Low-level info
LOG_VERBOSE(3, "RegisterConnection(): registered connection from '%s' to '%s', %p",
con.GetLocalAddress().ToString().c_str(),
con.GetRemoteAddress().ToString().c_str(),
(UINT_PTR) &con);
// add to connections list, that means we need to also increment the refcount
m_pConnections.push_back(&con);
con.AddRef();
}
}
void CServiceNetwork::RegisterForDeferredClose(CServiceNetworkConnection& con, const uint32 timeout)
{
CryAutoLock<CryMutex> lock(m_accessMutex);
// Low-level info
LOG_VERBOSE(3, "RegisterConnection(): registered connection from '%s' to '%s', %p for defered close, timeout=%d",
con.GetLocalAddress().ToString().c_str(),
con.GetRemoteAddress().ToString().c_str(),
(UINT_PTR) &con,
timeout);
// Add to connections list, that means we need to also increment the refcount
ConnectionToClose info;
info.pConnection = &con;
info.maxWaitTime = (timeout > 0) ? (GetNetworkTime() + timeout) : 0;
m_connectionsToClose.push_back(info);
// Keep internal reference
con.AddRef();
}
IServiceNetworkConnection* CServiceNetwork::Connect(const ServiceNetworkAddress& remoteAddress)
{
// Create new socket if needed
AZSOCKET socket = AZ::AzSock::Socket();
if (!AZ::AzSock::IsAzSocketValid(socket))
{
// Connection has problems
LOG_VERBOSE(0, "Connect(%s): socket() failed: %s", remoteAddress.ToString().c_str(), AZ::AzSock::GetStringForError(socket));
return NULL;
}
// Translate remote address
AZ::AzSock::AzSocketAddress addr;
TranslateAddress(remoteAddress, addr);
// Spew to log (important info)
LOG_VERBOSE(1, "Connecting to '%s'...", remoteAddress.ToString().c_str());
// Connect (blocking)
const int result = AZ::AzSock::Connect(socket, addr);
if (AZ::AzSock::SocketErrorOccured(result))
{
// Spew to log (important info)
LOG_VERBOSE(0, "connect() to '%s' failed: %s", remoteAddress.ToString().c_str(), AZ::AzSock::GetStringForError(result));
return NULL;
}
// Get the address of local socket endpoint
// Get our local address
AZ::AzSock::AzSocketAddress localAddressInet;
AZ::AzSock::GetSockName(socket, localAddressInet);
// Translate to debug network address data
ServiceNetworkAddress localAddress;
TranslateAddress(localAddressInet, localAddress);
// Spew to log (important info)
LOG_VERBOSE(1, "connected() from '%s' to '%s'", localAddress.ToString().c_str(), remoteAddress.ToString().c_str());
// Allocate some connection ID
const uint64 loPart = m_guidGenerator.GenerateUint64();
const uint64 hiPart = m_guidGenerator.GenerateUint64();
const CryGUID connectionID = CryGUID::Construct(loPart, hiPart);
// Spew to log (important info)
LOG_VERBOSE(3, "New connection GUID: %08x-%08x-%08x-%08x", (uint64)(hiPart >> 32), (uint64)(hiPart & 0xFFFFFFFF), (uint64)(loPart >> 32), (uint64)(loPart & 0xFFFFFFFF));
// Create connection wrapper
CServiceNetworkConnection* newConnection = new CServiceNetworkConnection(
this,
CServiceNetworkConnection::eEndpoint_Client, // we are the client, we are connecting to remove destination
socket,
connectionID,
localAddress,
remoteAddress);
// Add to list of connections
{
CryAutoLock<CryMutex> lock(m_accessMutex);
m_pConnections.push_back(newConnection);
// since we add the object to our internal list keep an extra reference to it
newConnection->AddRef();
}
// Well, good luck and have fun :)
return newConnection;
}
//-----------------------------------------------------------------------------
// Do not remove (can mess up the uber file builds)
#undef LOG_VERBOSE
//-----------------------------------------------------------------------------