NazaraEngine/src/Nazara/Network/RUdpConnection.cpp

641 lines
17 KiB
C++

// Copyright (C) 2015 Jérôme Leclercq
// This file is part of the "Nazara Engine - Utility module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Network/RUdpConnection.hpp>
#include <Nazara/Core/CallOnExit.hpp>
#include <Nazara/Core/Log.hpp>
#include <Nazara/Network/NetPacket.hpp>
#include <Nazara/Network/Debug.hpp>
namespace Nz
{
/*!
* \ingroup network
* \class Nz::RUdpConnection
* \brief Network class that represents a reliable UDP connection
*/
/*!
* \brief Constructs a RUdpConnection object by default
*/
RUdpConnection::RUdpConnection() :
m_peerIterator(0),
m_forceAckSendTime(10'000), //< 10ms
m_pingInterval(1'000'000), //< 1s
m_protocol(0x4E4E6574), //< "NNet"
m_timeBeforePing(500'000), //< 0.5s
m_timeBeforeTimeOut(10'000'000), //< 10s
m_currentTime(0),
m_isSimulationEnabled(false),
m_shouldAcceptConnections(true)
{
}
/*!
* \brief Connects to the IpAddress
* \return true
*
* \param remoteAddress Address to connect to
*
* \remark Produces a NazaraAssert if socket is not bound
* \remark Produces a NazaraAssert if remote is invalid
* \remark Produces a NazaraAssert if port is not specified
*/
bool RUdpConnection::Connect(const IpAddress& remoteAddress)
{
NazaraAssert(m_socket.GetState() == SocketState_Bound, "Socket must be bound first");
NazaraAssert(remoteAddress.IsValid(), "Invalid remote address");
NazaraAssert(remoteAddress.GetPort() != 0, "Remote address has no port");
PeerData& client = RegisterPeer(remoteAddress, PeerState_Connecting);
client.stateData1 = s_randomGenerator();
NetPacket connectionRequestPacket(NetCode_RequestConnection);
connectionRequestPacket << client.stateData1;
EnqueuePacket(client, PacketPriority_Immediate, PacketReliability_Reliable, connectionRequestPacket);
return true;
}
/*!
* \brief Connects to the hostname
* \return true If successful
*
* \param hostName Hostname of the remote
* \param protocol Net protocol to use
* \param service Specify the protocol used
* \param error Optional argument to get the error
*/
bool RUdpConnection::Connect(const String& hostName, NetProtocol protocol, const String& service, ResolveError* error)
{
std::vector<HostnameInfo> results = IpAddress::ResolveHostname(protocol, hostName, service, error);
if (results.empty())
{
m_lastError = SocketError_ResolveError;
return false;
}
IpAddress hostnameAddress;
for (const HostnameInfo& result : results)
{
if (!result.address)
continue;
if (result.socketType != SocketType_UDP)
continue;
hostnameAddress = result.address;
break; //< Take first valid address
}
return Connect(hostnameAddress);
}
/*!
* \brief Listens to a socket
* \return true If successfully bound
*
* \param remoteAddress Address to listen to
*/
bool RUdpConnection::Listen(const IpAddress& address)
{
if (!InitSocket(address.GetProtocol()))
return false;
return m_socket.Bind(address) == SocketState_Bound;
}
/*!
* \brief Polls the message
* \return true If there is a message
*
* \param message Message to poll
*
* \remark Produces a NazaraAssert if message is invalid
*/
bool RUdpConnection::PollMessage(RUdpMessage* message)
{
NazaraAssert(message, "Invalid message");
if (m_receivedMessages.empty())
return false;
*message = std::move(m_receivedMessages.front());
m_receivedMessages.pop();
return true;
}
/*!
* \brief Sends the packet to a peer
* \return true If peer exists (false may result from disconnected client)
*
* \param peerIp IpAddress of the peer
* \param priority Priority of the packet
* \param reliability Policy of reliability of the packet
* \param packet Packet to send
*/
bool RUdpConnection::Send(const IpAddress& peerIp, PacketPriority priority, PacketReliability reliability, const NetPacket& packet)
{
auto it = m_peerByIP.find(peerIp);
if (it == m_peerByIP.end())
return false; /// Silently fail (probably a disconnected client)
EnqueuePacket(m_peers[it->second], priority, reliability, packet);
return true;
}
/*!
* \brief Updates the reliable connection
*/
void RUdpConnection::Update()
{
m_currentTime = m_clock.GetMicroseconds();
NetPacket receivedPacket;
IpAddress senderIp;
while (m_socket.ReceivePacket(&receivedPacket, &senderIp))
OnPacketReceived(senderIp, std::move(receivedPacket));
//for (unsigned int i = m_activeClients.FindFirst(); i != m_activeClients.npos; i = m_activeClients.FindNext(i))
//{
// PeerData& clientData = m_peers[i];
CallOnExit resetIterator([this] () { m_peerIterator = m_peers.size(); });
for (m_peerIterator = 0; m_peerIterator < m_peers.size(); ++m_peerIterator)
{
PeerData& peer = m_peers[m_peerIterator];
UInt32 timeSinceLastPacket = static_cast<UInt32>(m_currentTime - peer.lastPacketTime);
if (timeSinceLastPacket > m_timeBeforeTimeOut)
{
DisconnectPeer(peer.index);
continue;
}
else if (timeSinceLastPacket > m_timeBeforePing)
{
if (m_currentTime - peer.lastPingTime > m_pingInterval)
{
NetPacket pingPacket(NetCode_Ping);
EnqueuePacket(peer, PacketPriority_Low, PacketReliability_Unreliable, pingPacket);
}
}
if (peer.state == PeerState_WillAck && m_currentTime - peer.stateData1 > m_forceAckSendTime)
{
NetPacket acknowledgePacket(NetCode_Acknowledge);
EnqueuePacket(peer, PacketPriority_Low, PacketReliability_Reliable, acknowledgePacket);
}
for (unsigned int priority = PacketPriority_Highest; priority <= PacketPriority_Lowest; ++priority)
{
std::vector<PendingPacket>& pendingPackets = peer.pendingPackets[priority];
for (PendingPacket& packetData : pendingPackets)
SendPacket(peer, std::move(packetData));
pendingPackets.clear();
}
auto it = peer.pendingAckQueue.begin();
while (it != peer.pendingAckQueue.end())
{
if (m_currentTime - it->timeSent > 2 * peer.roundTripTime)
{
OnPacketLost(peer, std::move(*it));
it = peer.pendingAckQueue.erase(it);
}
else
++it;
}
}
//m_activeClients.Reset();
}
/*!
* \brief Disconnects a peer
*
* \param peerIndex Index of the peer
*
* \remark Produces a NazaraNotice
*/
void RUdpConnection::DisconnectPeer(std::size_t peerIndex)
{
PeerData& peer = m_peers[peerIndex];
NazaraNotice(m_socket.GetBoundAddress().ToString() + ": " + peer.address.ToString() + " has been disconnected due to time-out");
OnPeerDisconnected(this, peer.address);
// Remove from IP lookup table
m_peerByIP.erase(peer.address);
// Can we safely "remove" this slot?
if (m_peerIterator >= m_peers.size() - 1 || peerIndex > m_peerIterator)
{
// Yes we can
PeerData& newSlot = m_peers[peerIndex];
newSlot = std::move(m_peers.back());
newSlot.index = peerIndex; //< Update the moved slot index before resizing (in case it's the last one)
}
else
{
// Nope, let's be tricky
PeerData& current = m_peers[m_peerIterator];
PeerData& newSlot = m_peers[peerIndex];
newSlot = std::move(current);
newSlot.index = peerIndex; //< Update the moved slot index
current = std::move(m_peers.back());
current.index = m_peerIterator; //< Update the moved slot index
--m_peerIterator;
}
// Pop the last entry (from where we moved our slot)
m_peers.pop_back();
}
/*!
* \brief Enqueues a packet in the sending list
*
* \param peer Data relative to the peer
* \param priority Priority of the packet
* \param reliability Policy of reliability of the packet
* \param packet Packet to send
*/
void RUdpConnection::EnqueuePacket(PeerData& peer, PacketPriority priority, PacketReliability reliability, const NetPacket& packet)
{
UInt16 protocolBegin = static_cast<UInt16>(m_protocol & 0xFFFF);
UInt16 protocolEnd = static_cast<UInt16>((m_protocol & 0xFFFF0000) >> 16);
NetPacket data(packet.GetNetCode(), MessageHeader + packet.GetDataSize() + MessageFooter);
data << protocolBegin;
data.GetStream()->SetCursorPos(NetPacket::HeaderSize + MessageHeader);
data.Write(packet.GetConstData() + NetPacket::HeaderSize, packet.GetDataSize());
data << protocolEnd;
EnqueuePacketInternal(peer, priority, reliability, std::move(data));
}
/*!
* \brief Enqueues internally a packet in the sending list
*
* \param peer Data relative to the peer
* \param priority Priority of the packet
* \param reliability Policy of reliability of the packet
* \param packet Packet to send
*/
void RUdpConnection::EnqueuePacketInternal(PeerData& peer, PacketPriority priority, PacketReliability reliability, NetPacket&& data)
{
PendingPacket pendingPacket;
pendingPacket.data = std::move(data);
pendingPacket.priority = priority;
pendingPacket.reliability = reliability;
peer.pendingPackets[priority].emplace_back(std::move(pendingPacket));
m_activeClients.UnboundedSet(peer.index);
}
/*!
* \brief Inits the internal socket
* \return true If successful
*
* \param protocol Net protocol to use
*/
bool RUdpConnection::InitSocket(NetProtocol protocol)
{
CallOnExit updateLastError([this]
{
m_lastError = m_socket.GetLastError();
});
if (!m_socket.Create(protocol))
return false;
m_socket.EnableBlocking(false);
return true;
}
/*!
* \brief Processes the acks
*
* \param peer Data relative to the peer
* \param lastAck Last index of the ack
* \param ackBits Bits for acking
*/
void RUdpConnection::ProcessAcks(PeerData& peer, SequenceIndex lastAck, UInt32 ackBits)
{
auto it = peer.pendingAckQueue.begin();
while (it != peer.pendingAckQueue.end())
{
bool acked = false;
if (lastAck == it->sequenceId)
acked = true;
else if (!IsAckMoreRecent(it->sequenceId, lastAck))
{
unsigned int difference = ComputeSequenceDifference(lastAck, it->sequenceId);
if (difference <= 32)
acked = (ackBits >> (difference - 1)) & 1;
}
if (acked)
{
it = peer.pendingAckQueue.erase(it);
}
else
++it;
}
}
/*!
* \brief Registers a peer
* \return Data relative to the peer
*
* \param address Address of the peer
* \param state Status of the peer
*/
RUdpConnection::PeerData& RUdpConnection::RegisterPeer(const IpAddress& address, PeerState state)
{
PeerData data;
data.address = address;
data.localSequence = 0;
data.remoteSequence = 0;
data.index = m_peers.size();
data.lastPacketTime = m_currentTime;
data.lastPingTime = m_currentTime;
data.roundTripTime = 1'000'000; ///< Okay that's quite a lot
data.state = state;
m_activeClients.UnboundedSet(data.index);
m_peerByIP[address] = data.index;
m_peers.emplace_back(std::move(data));
return m_peers.back();
}
/*!
* \brief Operation to do when client requests a connection
*
* \param address Address of the peer
* \param sequenceId Sequence index for the ack
* \param token Token for connection
*/
void RUdpConnection::OnClientRequestingConnection(const IpAddress& address, SequenceIndex sequenceId, UInt64 token)
{
// Call hook to check if client should be accepted or not
OnPeerConnection(this, address);
PeerData& client = RegisterPeer(address, PeerState_Aknowledged);
client.remoteSequence = sequenceId;
/// Acknowledge connection
NetPacket connectionAcceptedPacket(NetCode_AcknowledgeConnection);
//connectionAcceptedPacket << address;
connectionAcceptedPacket << ~token;
EnqueuePacket(client, PacketPriority_Immediate, PacketReliability_Reliable, connectionAcceptedPacket);
}
/*!
* \brief Operation to do when a packet is lost
*
* \param peer Data relative to the peer
* \param packet Pending packet
*/
void RUdpConnection::OnPacketLost(PeerData& peer, PendingAckPacket&& packet)
{
//NazaraNotice(m_socket.GetBoundAddress().ToString() + ": Lost packet " + String::Number(packet.sequenceId));
if (IsReliable(packet.reliability))
EnqueuePacketInternal(peer, packet.priority, packet.reliability, std::move(packet.data));
}
/*!
* \brief Operation to do when receiving a packet
*
* \param peerIndex Index of the peer
*
* \remark Produces a NazaraNotice
*/
void RUdpConnection::OnPacketReceived(const IpAddress& peerIp, NetPacket&& packet)
{
UInt16 protocolBegin;
UInt16 protocolEnd;
SequenceIndex sequenceId;
SequenceIndex lastAck;
UInt32 ackBits;
packet.GetStream()->SetCursorPos(packet.GetSize() - MessageFooter);
packet >> protocolEnd;
packet.GetStream()->SetCursorPos(NetPacket::HeaderSize);
packet >> protocolBegin;
UInt32 protocolId = static_cast<UInt32>(protocolEnd) << 16 | protocolBegin;
if (protocolId != m_protocol)
return; ///< Ignore
packet >> sequenceId >> lastAck >> ackBits;
auto it = m_peerByIP.find(peerIp);
if (it == m_peerByIP.end())
{
switch (packet.GetNetCode())
{
case NetCode_RequestConnection:
{
UInt64 token;
packet >> token;
NazaraNotice(m_socket.GetBoundAddress().ToString() + ": Received NetCode_RequestConnection from " + peerIp.ToString() + ": " + String::Number(token));
if (!m_shouldAcceptConnections)
return; //< Ignore
OnClientRequestingConnection(peerIp, sequenceId, token);
break;
}
default:
return; //< Ignore
}
}
else
{
PeerData& peer = m_peers[it->second];
peer.lastPacketTime = m_currentTime;
if (peer.receivedQueue.find(sequenceId) != peer.receivedQueue.end())
return; //< Ignore
if (m_isSimulationEnabled && m_packetLossProbability(s_randomGenerator))
{
NazaraNotice(m_socket.GetBoundAddress().ToString() + ": Lost packet " + String::Number(sequenceId) + " from " + peerIp.ToString() + " for simulation purpose");
return;
}
///< Receiving a packet from an acknowledged client means the connection works in both ways
if (peer.state == PeerState_Aknowledged && packet.GetNetCode() != NetCode_RequestConnection)
{
peer.state = PeerState_Connected;
OnPeerAcknowledged(this, peerIp);
}
if (IsAckMoreRecent(sequenceId, peer.remoteSequence))
peer.remoteSequence = sequenceId;
ProcessAcks(peer, lastAck, ackBits);
peer.receivedQueue.insert(sequenceId);
switch (packet.GetNetCode())
{
case NetCode_Acknowledge:
return; //< Do not switch to will ack mode (to prevent infinite replies, just let's ping/pong do that)
case NetCode_AcknowledgeConnection:
{
if (peer.state == PeerState_Connected)
break;
IpAddress externalAddress;
UInt64 token;
packet /*>> externalAddress*/ >> token;
NazaraNotice(m_socket.GetBoundAddress().ToString() + ": Received NetCode_AcknowledgeConnection from " + peerIp.ToString() + ": " + String::Number(token));
if (token == ~peer.stateData1)
{
peer.state = PeerState_Connected;
OnConnectedToPeer(this);
}
else
{
NazaraNotice("Received wrong token (" + String::Number(token) + " instead of " + String::Number(~peer.stateData1) + ") from client " + peer.address);
return; //< Ignore
}
break;
}
case NetCode_RequestConnection:
NazaraNotice(m_socket.GetBoundAddress().ToString() + ": Received NetCode_RequestConnection from " + peerIp.ToString());
return; //< Ignore
case NetCode_Ping:
{
NazaraNotice(m_socket.GetBoundAddress().ToString() + ": Received NetCode_Ping from " + peerIp.ToString());
NetPacket pongPacket(NetCode_Pong);
EnqueuePacket(peer, PacketPriority_Low, PacketReliability_Unreliable, pongPacket);
break;
}
case NetCode_Pong:
NazaraNotice(m_socket.GetBoundAddress().ToString() + ": Received NetCode_Pong from " + peerIp.ToString());
break;
default:
{
NazaraNotice(m_socket.GetBoundAddress().ToString() + ": Received 0x" + String::Number(packet.GetNetCode(), 16) + " from " + peerIp.ToString());
RUdpMessage receivedMessage;
receivedMessage.from = peerIp;
receivedMessage.data = std::move(packet);
m_receivedMessages.emplace(std::move(receivedMessage));
break;
}
}
if (!HasPendingPackets(peer))
{
peer.state = PeerState_WillAck;
peer.stateData1 = m_currentTime;
}
}
}
/*!
* \brief Sends a packet to a peer
*
* \param peer Data relative to the peer
* \param packet Pending packet
*/
void RUdpConnection::SendPacket(PeerData& peer, PendingPacket&& packet)
{
if (peer.state == PeerState_WillAck)
peer.state = PeerState_Connected;
SequenceIndex remoteSequence = peer.remoteSequence;
UInt32 previousAcks = 0;
for (SequenceIndex ack : peer.receivedQueue)
{
if (ack == remoteSequence)
continue;
unsigned int difference = ComputeSequenceDifference(remoteSequence, ack);
if (difference <= 32U)
previousAcks |= (1U << (difference - 1));
}
SequenceIndex sequenceId = ++peer.localSequence;
packet.data.GetStream()->SetCursorPos(NetPacket::HeaderSize + sizeof(UInt16)); ///< Protocol begin has already been filled
packet.data << sequenceId;
packet.data << remoteSequence;
packet.data << previousAcks;
m_socket.SendPacket(peer.address, packet.data);
PendingAckPacket pendingAckPacket;
pendingAckPacket.data = std::move(packet.data);
pendingAckPacket.priority = packet.priority;
pendingAckPacket.reliability = packet.reliability;
pendingAckPacket.sequenceId = sequenceId;
pendingAckPacket.timeSent = m_currentTime;
peer.pendingAckQueue.emplace_back(std::move(pendingAckPacket));
}
/*!
* \brief Initializes the RUdpConnection class
* \return true
*/
bool RUdpConnection::Initialize()
{
std::random_device device;
s_randomGenerator.seed(device());
return true;
}
/*!
* \brief Uninitializes the RUdpConnection class
*/
void RUdpConnection::Uninitialize()
{
}
std::mt19937_64 RUdpConnection::s_randomGenerator;
}