space-pew/Lidgren.Network/NetConnection.cs

564 lines
18 KiB
C#
Raw Permalink Normal View History

2015-12-04 10:23:49 +01:00
using System;
using System.Net;
using System.Threading;
using System.Diagnostics;
namespace Lidgren.Network
{
/// <summary>
/// Represents a connection to a remote peer
/// </summary>
[DebuggerDisplay("RemoteUniqueIdentifier={RemoteUniqueIdentifier} RemoteEndPoint={remoteEndPoint}")]
public partial class NetConnection
{
private const int m_infrequentEventsSkipFrames = 8; // number of heartbeats to skip checking for infrequent events (ping, timeout etc)
private const int m_messageCoalesceFrames = 3; // number of heartbeats to wait for more incoming messages before sending packet
internal NetPeer m_peer;
internal NetPeerConfiguration m_peerConfiguration;
internal NetConnectionStatus m_status;
internal NetConnectionStatus m_visibleStatus;
internal IPEndPoint m_remoteEndPoint;
internal NetSenderChannelBase[] m_sendChannels;
internal NetReceiverChannelBase[] m_receiveChannels;
internal NetOutgoingMessage m_localHailMessage;
internal long m_remoteUniqueIdentifier;
internal NetQueue<NetTuple<NetMessageType, int>> m_queuedOutgoingAcks;
internal NetQueue<NetTuple<NetMessageType, int>> m_queuedIncomingAcks;
private int m_sendBufferWritePtr;
private int m_sendBufferNumMessages;
private object m_tag;
internal NetConnectionStatistics m_statistics;
/// <summary>
/// Gets or sets the application defined object containing data about the connection
/// </summary>
public object Tag
{
get { return m_tag; }
set { m_tag = value; }
}
/// <summary>
/// Gets the peer which holds this connection
/// </summary>
public NetPeer Peer { get { return m_peer; } }
/// <summary>
/// Gets the current status of the connection (synced to the last status message read)
/// </summary>
public NetConnectionStatus Status { get { return m_visibleStatus; } }
/// <summary>
/// Gets various statistics for this connection
/// </summary>
public NetConnectionStatistics Statistics { get { return m_statistics; } }
/// <summary>
/// Gets the remote endpoint for the connection
/// </summary>
public IPEndPoint RemoteEndPoint { get { return m_remoteEndPoint; } }
/// <summary>
/// Gets the unique identifier of the remote NetPeer for this connection
/// </summary>
public long RemoteUniqueIdentifier { get { return m_remoteUniqueIdentifier; } }
/// <summary>
/// Gets the local hail message that was sent as part of the handshake
/// </summary>
public NetOutgoingMessage LocalHailMessage { get { return m_localHailMessage; } }
// gets the time before automatically resending an unacked message
internal float GetResendDelay()
{
float avgRtt = m_averageRoundtripTime;
if (avgRtt <= 0)
avgRtt = 0.1f; // "default" resend is based on 100 ms roundtrip time
return 0.025f + (avgRtt * 2.1f); // 25 ms + double rtt
}
internal NetConnection(NetPeer peer, IPEndPoint remoteEndPoint)
{
m_peer = peer;
m_peerConfiguration = m_peer.Configuration;
m_status = NetConnectionStatus.None;
m_visibleStatus = NetConnectionStatus.None;
m_remoteEndPoint = remoteEndPoint;
m_sendChannels = new NetSenderChannelBase[NetConstants.NumTotalChannels];
m_receiveChannels = new NetReceiverChannelBase[NetConstants.NumTotalChannels];
m_queuedOutgoingAcks = new NetQueue<NetTuple<NetMessageType, int>>(4);
m_queuedIncomingAcks = new NetQueue<NetTuple<NetMessageType, int>>(4);
m_statistics = new NetConnectionStatistics(this);
m_averageRoundtripTime = -1.0f;
m_currentMTU = m_peerConfiguration.MaximumTransmissionUnit;
}
/// <summary>
/// Change the internal endpoint to this new one. Used when, during handshake, a switch in port is detected (due to NAT)
/// </summary>
internal void MutateEndPoint(IPEndPoint endPoint)
{
m_remoteEndPoint = endPoint;
}
internal void SetStatus(NetConnectionStatus status, string reason)
{
// user or library thread
if (status == m_status)
return;
m_status = status;
if (reason == null)
reason = string.Empty;
if (m_status == NetConnectionStatus.Connected)
{
m_timeoutDeadline = (float)NetTime.Now + m_peerConfiguration.m_connectionTimeout;
m_peer.LogVerbose("Timeout deadline initialized to " + m_timeoutDeadline);
}
if (m_peerConfiguration.IsMessageTypeEnabled(NetIncomingMessageType.StatusChanged))
{
NetIncomingMessage info = m_peer.CreateIncomingMessage(NetIncomingMessageType.StatusChanged, 4 + reason.Length + (reason.Length > 126 ? 2 : 1));
info.m_senderConnection = this;
info.m_senderEndPoint = m_remoteEndPoint;
info.Write((byte)m_status);
info.Write(reason);
m_peer.ReleaseMessage(info);
}
else
{
// app dont want those messages, update visible status immediately
m_visibleStatus = m_status;
}
}
internal void Heartbeat(float now, uint frameCounter)
{
m_peer.VerifyNetworkThread();
NetException.Assert(m_status != NetConnectionStatus.InitiatedConnect && m_status != NetConnectionStatus.RespondedConnect);
if ((frameCounter % m_infrequentEventsSkipFrames) == 0)
{
if (now > m_timeoutDeadline)
{
//
// connection timed out
//
m_peer.LogVerbose("Connection timed out at " + now + " deadline was " + m_timeoutDeadline);
ExecuteDisconnect("Connection timed out", true);
return;
}
// send ping?
if (m_status == NetConnectionStatus.Connected)
{
if (now > m_sentPingTime + m_peer.m_configuration.m_pingInterval)
SendPing();
// handle expand mtu
MTUExpansionHeartbeat(now);
}
if (m_disconnectRequested)
{
ExecuteDisconnect(m_disconnectMessage, m_disconnectReqSendBye);
return;
}
}
bool connectionReset; // TODO: handle connection reset
//
// Note: at this point m_sendBufferWritePtr and m_sendBufferNumMessages may be non-null; resends may already be queued up
//
byte[] sendBuffer = m_peer.m_sendBuffer;
int mtu = m_currentMTU;
if ((frameCounter % m_messageCoalesceFrames) == 0) // coalesce a few frames
{
//
// send ack messages
//
while (m_queuedOutgoingAcks.Count > 0)
{
int acks = (mtu - (m_sendBufferWritePtr + 5)) / 3; // 3 bytes per actual ack
if (acks > m_queuedOutgoingAcks.Count)
acks = m_queuedOutgoingAcks.Count;
NetException.Assert(acks > 0);
m_sendBufferNumMessages++;
// write acks header
sendBuffer[m_sendBufferWritePtr++] = (byte)NetMessageType.Acknowledge;
sendBuffer[m_sendBufferWritePtr++] = 0; // no sequence number
sendBuffer[m_sendBufferWritePtr++] = 0; // no sequence number
int len = (acks * 3) * 8; // bits
sendBuffer[m_sendBufferWritePtr++] = (byte)len;
sendBuffer[m_sendBufferWritePtr++] = (byte)(len >> 8);
// write acks
for (int i = 0; i < acks; i++)
{
NetTuple<NetMessageType, int> tuple;
m_queuedOutgoingAcks.TryDequeue(out tuple);
//m_peer.LogVerbose("Sending ack for " + tuple.Item1 + "#" + tuple.Item2);
sendBuffer[m_sendBufferWritePtr++] = (byte)tuple.Item1;
sendBuffer[m_sendBufferWritePtr++] = (byte)tuple.Item2;
sendBuffer[m_sendBufferWritePtr++] = (byte)(tuple.Item2 >> 8);
}
if (m_queuedOutgoingAcks.Count > 0)
{
// send packet and go for another round of acks
NetException.Assert(m_sendBufferWritePtr > 0 && m_sendBufferNumMessages > 0);
m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndPoint, m_sendBufferNumMessages, out connectionReset);
m_statistics.PacketSent(m_sendBufferWritePtr, 1);
m_sendBufferWritePtr = 0;
m_sendBufferNumMessages = 0;
}
}
//
// Parse incoming acks (may trigger resends)
//
NetTuple<NetMessageType, int> incAck;
while (m_queuedIncomingAcks.TryDequeue(out incAck))
{
//m_peer.LogVerbose("Received ack for " + acktp + "#" + seqNr);
NetSenderChannelBase chan = m_sendChannels[(int)incAck.Item1 - 1];
// If we haven't sent a message on this channel there is no reason to ack it
if (chan == null)
continue;
chan.ReceiveAcknowledge(now, incAck.Item2);
}
}
//
// send queued messages
//
if (m_peer.m_executeFlushSendQueue)
{
for (int i = m_sendChannels.Length - 1; i >= 0; i--) // Reverse order so reliable messages are sent first
{
var channel = m_sendChannels[i];
NetException.Assert(m_sendBufferWritePtr < 1 || m_sendBufferNumMessages > 0);
if (channel != null)
channel.SendQueuedMessages(now);
NetException.Assert(m_sendBufferWritePtr < 1 || m_sendBufferNumMessages > 0);
}
}
//
// Put on wire data has been written to send buffer but not yet sent
//
if (m_sendBufferWritePtr > 0)
{
m_peer.VerifyNetworkThread();
NetException.Assert(m_sendBufferWritePtr > 0 && m_sendBufferNumMessages > 0);
m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndPoint, m_sendBufferNumMessages, out connectionReset);
m_statistics.PacketSent(m_sendBufferWritePtr, m_sendBufferNumMessages);
m_sendBufferWritePtr = 0;
m_sendBufferNumMessages = 0;
}
}
// Queue an item for immediate sending on the wire
// This method is called from the ISenderChannels
internal void QueueSendMessage(NetOutgoingMessage om, int seqNr)
{
m_peer.VerifyNetworkThread();
int sz = om.GetEncodedSize();
//if (sz > m_currentMTU)
// m_peer.LogWarning("Message larger than MTU! Fragmentation must have failed!");
bool connReset; // TODO: handle connection reset
// can fit this message together with previously written to buffer?
if (m_sendBufferWritePtr + sz > m_currentMTU)
{
if (m_sendBufferWritePtr > 0 && m_sendBufferNumMessages > 0)
{
// previous message in buffer; send these first
m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndPoint, m_sendBufferNumMessages, out connReset);
m_statistics.PacketSent(m_sendBufferWritePtr, m_sendBufferNumMessages);
m_sendBufferWritePtr = 0;
m_sendBufferNumMessages = 0;
}
}
// encode it into buffer regardless if it (now) fits within MTU or not
m_sendBufferWritePtr = om.Encode(m_peer.m_sendBuffer, m_sendBufferWritePtr, seqNr);
m_sendBufferNumMessages++;
if (m_sendBufferWritePtr > m_currentMTU)
{
// send immediately; we're already over MTU
m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndPoint, m_sendBufferNumMessages, out connReset);
m_statistics.PacketSent(m_sendBufferWritePtr, m_sendBufferNumMessages);
m_sendBufferWritePtr = 0;
m_sendBufferNumMessages = 0;
}
}
/// <summary>
/// Send a message to this remote connection
/// </summary>
/// <param name="msg">The message to send</param>
/// <param name="method">How to deliver the message</param>
/// <param name="sequenceChannel">Sequence channel within the delivery method</param>
public NetSendResult SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel)
{
return m_peer.SendMessage(msg, this, method, sequenceChannel);
}
// called by SendMessage() and NetPeer.SendMessage; ie. may be user thread
internal NetSendResult EnqueueMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel)
{
if (m_status != NetConnectionStatus.Connected)
return NetSendResult.FailedNotConnected;
NetMessageType tp = (NetMessageType)((int)method + sequenceChannel);
msg.m_messageType = tp;
// TODO: do we need to make this more thread safe?
int channelSlot = (int)method - 1 + sequenceChannel;
NetSenderChannelBase chan = m_sendChannels[channelSlot];
if (chan == null)
chan = CreateSenderChannel(tp);
if ((method != NetDeliveryMethod.Unreliable && method != NetDeliveryMethod.UnreliableSequenced) && msg.GetEncodedSize() > m_currentMTU)
m_peer.ThrowOrLog("Reliable message too large! Fragmentation failure?");
var retval = chan.Enqueue(msg);
//if (retval == NetSendResult.Sent && m_peerConfiguration.m_autoFlushSendQueue == false)
// retval = NetSendResult.Queued; // queued since we're not autoflushing
return retval;
}
// may be on user thread
private NetSenderChannelBase CreateSenderChannel(NetMessageType tp)
{
NetSenderChannelBase chan;
lock (m_sendChannels)
{
NetDeliveryMethod method = NetUtility.GetDeliveryMethod(tp);
int sequenceChannel = (int)tp - (int)method;
int channelSlot = (int)method - 1 + sequenceChannel;
if (m_sendChannels[channelSlot] != null)
{
// we were pre-empted by another call to this method
chan = m_sendChannels[channelSlot];
}
else
{
switch (method)
{
case NetDeliveryMethod.Unreliable:
case NetDeliveryMethod.UnreliableSequenced:
chan = new NetUnreliableSenderChannel(this, NetUtility.GetWindowSize(method));
break;
case NetDeliveryMethod.ReliableOrdered:
chan = new NetReliableSenderChannel(this, NetUtility.GetWindowSize(method));
break;
case NetDeliveryMethod.ReliableSequenced:
case NetDeliveryMethod.ReliableUnordered:
default:
chan = new NetReliableSenderChannel(this, NetUtility.GetWindowSize(method));
break;
}
m_sendChannels[channelSlot] = chan;
}
}
return chan;
}
// received a library message while Connected
internal void ReceivedLibraryMessage(NetMessageType tp, int ptr, int payloadLength)
{
m_peer.VerifyNetworkThread();
float now = (float)NetTime.Now;
switch (tp)
{
case NetMessageType.Connect:
m_peer.LogDebug("Received handshake message (" + tp + ") despite connection being in place");
break;
case NetMessageType.ConnectResponse:
// handshake message must have been lost
HandleConnectResponse(now, tp, ptr, payloadLength);
break;
case NetMessageType.ConnectionEstablished:
// do nothing, all's well
break;
case NetMessageType.LibraryError:
m_peer.ThrowOrLog("LibraryError received by ReceivedLibraryMessage; this usually indicates a malformed message");
break;
case NetMessageType.Disconnect:
NetIncomingMessage msg = m_peer.SetupReadHelperMessage(ptr, payloadLength);
m_disconnectRequested = true;
m_disconnectMessage = msg.ReadString();
m_disconnectReqSendBye = false;
//ExecuteDisconnect(msg.ReadString(), false);
break;
case NetMessageType.Acknowledge:
for (int i = 0; i < payloadLength; i+=3)
{
NetMessageType acktp = (NetMessageType)m_peer.m_receiveBuffer[ptr++]; // netmessagetype
int seqNr = m_peer.m_receiveBuffer[ptr++];
seqNr |= (m_peer.m_receiveBuffer[ptr++] << 8);
// need to enqueue this and handle it in the netconnection heartbeat; so be able to send resends together with normal sends
m_queuedIncomingAcks.Enqueue(new NetTuple<NetMessageType, int>(acktp, seqNr));
}
break;
case NetMessageType.Ping:
int pingNr = m_peer.m_receiveBuffer[ptr++];
SendPong(pingNr);
break;
case NetMessageType.Pong:
NetIncomingMessage pmsg = m_peer.SetupReadHelperMessage(ptr, payloadLength);
int pongNr = pmsg.ReadByte();
float remoteSendTime = pmsg.ReadSingle();
ReceivedPong(now, pongNr, remoteSendTime);
break;
case NetMessageType.ExpandMTURequest:
SendMTUSuccess(payloadLength);
break;
case NetMessageType.ExpandMTUSuccess:
if (m_peer.Configuration.AutoExpandMTU == false)
{
m_peer.LogDebug("Received ExpandMTURequest altho AutoExpandMTU is turned off!");
break;
}
NetIncomingMessage emsg = m_peer.SetupReadHelperMessage(ptr, payloadLength);
int size = emsg.ReadInt32();
HandleExpandMTUSuccess(now, size);
break;
case NetMessageType.NatIntroduction:
// Unusual situation where server is actually already known, but got a nat introduction - oh well, lets handle it as usual
m_peer.HandleNatIntroduction(ptr);
break;
default:
m_peer.LogWarning("Connection received unhandled library message: " + tp);
break;
}
}
internal void ReceivedMessage(NetIncomingMessage msg)
{
m_peer.VerifyNetworkThread();
NetMessageType tp = msg.m_receivedMessageType;
int channelSlot = (int)tp - 1;
NetReceiverChannelBase chan = m_receiveChannels[channelSlot];
if (chan == null)
chan = CreateReceiverChannel(tp);
chan.ReceiveMessage(msg);
}
private NetReceiverChannelBase CreateReceiverChannel(NetMessageType tp)
{
m_peer.VerifyNetworkThread();
// create receiver channel
NetReceiverChannelBase chan;
NetDeliveryMethod method = NetUtility.GetDeliveryMethod(tp);
switch (method)
{
case NetDeliveryMethod.Unreliable:
chan = new NetUnreliableUnorderedReceiver(this);
break;
case NetDeliveryMethod.ReliableOrdered:
chan = new NetReliableOrderedReceiver(this, NetConstants.ReliableOrderedWindowSize);
break;
case NetDeliveryMethod.UnreliableSequenced:
chan = new NetUnreliableSequencedReceiver(this);
break;
case NetDeliveryMethod.ReliableUnordered:
chan = new NetReliableUnorderedReceiver(this, NetConstants.ReliableOrderedWindowSize);
break;
case NetDeliveryMethod.ReliableSequenced:
chan = new NetReliableSequencedReceiver(this, NetConstants.ReliableSequencedWindowSize);
break;
default:
throw new NetException("Unhandled NetDeliveryMethod!");
}
int channelSlot = (int)tp - 1;
NetException.Assert(m_receiveChannels[channelSlot] == null);
m_receiveChannels[channelSlot] = chan;
return chan;
}
internal void QueueAck(NetMessageType tp, int sequenceNumber)
{
m_queuedOutgoingAcks.Enqueue(new NetTuple<NetMessageType, int>(tp, sequenceNumber));
}
/// <summary>
/// Zero windowSize indicates that the channel is not yet instantiated (used)
/// Negative freeWindowSlots means this amount of messages are currently queued but delayed due to closed window
/// </summary>
public void GetSendQueueInfo(NetDeliveryMethod method, int sequenceChannel, out int windowSize, out int freeWindowSlots)
{
int channelSlot = (int)method - 1 + sequenceChannel;
var chan = m_sendChannels[channelSlot];
if (chan == null)
{
windowSize = NetUtility.GetWindowSize(method);
freeWindowSlots = windowSize;
return;
}
windowSize = chan.WindowSize;
freeWindowSlots = chan.GetAllowedSends() - chan.m_queuedSends.Count;
return;
}
public bool CanSendImmediately(NetDeliveryMethod method, int sequenceChannel)
{
int channelSlot = (int)method - 1 + sequenceChannel;
var chan = m_sendChannels[channelSlot];
if (chan == null)
return true;
return (chan.GetAllowedSends() - chan.m_queuedSends.Count) > 0;
}
internal void Shutdown(string reason)
{
ExecuteDisconnect(reason, true);
}
/// <summary>
/// Returns a string that represents this object
/// </summary>
public override string ToString()
{
return "[NetConnection to " + m_remoteEndPoint + "]";
}
}
}