diff --git a/Lidgren.Network/Lidgren.Network.csproj b/Lidgren.Network/Lidgren.Network.csproj
index ca95d3a..190d349 100644
--- a/Lidgren.Network/Lidgren.Network.csproj
+++ b/Lidgren.Network/Lidgren.Network.csproj
@@ -62,6 +62,7 @@
+
diff --git a/Lidgren.Network/NetConnection.Latency.cs b/Lidgren.Network/NetConnection.Latency.cs
index 66e86d2..f6e9bd2 100644
--- a/Lidgren.Network/NetConnection.Latency.cs
+++ b/Lidgren.Network/NetConnection.Latency.cs
@@ -37,6 +37,8 @@ namespace Lidgren.Network
int len = om.Encode(m_peer.m_sendBuffer, 0, 0);
bool connectionReset;
m_peer.SendPacket(len, m_remoteEndpoint, 1, out connectionReset);
+
+ m_statistics.PacketSent(len, 1);
}
internal void SendPong(int pingNumber)
@@ -50,6 +52,8 @@ namespace Lidgren.Network
int len = om.Encode(m_peer.m_sendBuffer, 0, 0);
bool connectionReset;
m_peer.SendPacket(len, m_remoteEndpoint, 1, out connectionReset);
+
+ m_statistics.PacketSent(len, 1);
}
internal void ReceivedPong(float now, int pongNumber)
diff --git a/Lidgren.Network/NetConnection.MTU.cs b/Lidgren.Network/NetConnection.MTU.cs
new file mode 100644
index 0000000..4818a55
--- /dev/null
+++ b/Lidgren.Network/NetConnection.MTU.cs
@@ -0,0 +1,176 @@
+using System;
+
+namespace Lidgren.Network
+{
+ public partial class NetConnection
+ {
+ private enum ExpandMTUStatus
+ {
+ None,
+ InProgress,
+ Finished
+ }
+
+ private const int c_protocolMaxMTU = (int)((((float)ushort.MaxValue / 8.0f) - 1.0f));
+
+ private ExpandMTUStatus m_expandMTUStatus;
+
+ private int m_largestSuccessfulMTU;
+ private int m_smallestFailedMTU;
+
+ private int m_lastSentMTUAttemptSize;
+ private double m_lastSentMTUAttemptTime;
+ private int m_mtuAttemptFails;
+
+ internal int m_currentMTU;
+
+ internal void InitExpandMTU(double now)
+ {
+ m_lastSentMTUAttemptTime = now + m_peerConfiguration.m_expandMTUFrequency + 1.0f; // wait a tiny bit before starting to expand mtu
+ m_largestSuccessfulMTU = 512;
+ m_smallestFailedMTU = -1;
+ m_currentMTU = m_peerConfiguration.MaximumTransmissionUnit;
+ }
+
+ private void MTUExpansionHeartbeat(double now)
+ {
+ if (m_expandMTUStatus == ExpandMTUStatus.Finished)
+ return;
+
+ if (m_expandMTUStatus == ExpandMTUStatus.None)
+ {
+ if (m_peerConfiguration.m_autoExpandMTU == false)
+ {
+ FinalizeMTU(m_currentMTU);
+ return;
+ }
+
+ // begin expansion
+ ExpandMTU(now, true);
+ return;
+ }
+
+ if (now > m_lastSentMTUAttemptTime + m_peerConfiguration.ExpandMTUFrequency)
+ {
+ m_mtuAttemptFails++;
+ if (m_mtuAttemptFails == 3)
+ {
+ FinalizeMTU(m_currentMTU);
+ return;
+ }
+
+ // timed out; ie. failed
+ m_smallestFailedMTU = m_lastSentMTUAttemptSize;
+ ExpandMTU(now, false);
+ }
+ }
+
+ private void ExpandMTU(double now, bool succeeded)
+ {
+ int tryMTU;
+
+ // we've nevered encountered failure
+ if (m_smallestFailedMTU == -1)
+ {
+ // we've never encountered failure; expand by 25% each time
+ tryMTU = (int)((float)m_currentMTU * 1.25f);
+ //m_peer.LogDebug("Trying MTU " + tryMTU);
+ }
+ else
+ {
+ // we HAVE encountered failure; so try in between
+ tryMTU = (int)(((float)m_smallestFailedMTU + (float)m_largestSuccessfulMTU) / 2.0f);
+ //m_peer.LogDebug("Trying MTU " + m_smallestFailedMTU + " <-> " + m_largestSuccessfulMTU + " = " + tryMTU);
+ }
+
+ if (tryMTU > c_protocolMaxMTU)
+ tryMTU = c_protocolMaxMTU;
+
+ if (tryMTU == m_largestSuccessfulMTU)
+ {
+ //m_peer.LogDebug("Found optimal MTU - exiting");
+ FinalizeMTU(m_largestSuccessfulMTU);
+ return;
+ }
+
+ SendExpandMTU(now, tryMTU);
+ }
+
+ private void SendExpandMTU(double now, int size)
+ {
+ NetOutgoingMessage om = m_peer.CreateMessage(size);
+ byte[] tmp = new byte[size];
+ om.Write(tmp);
+ om.m_messageType = NetMessageType.ExpandMTURequest;
+ int len = om.Encode(m_peer.m_sendBuffer, 0, 0);
+
+ bool ok = m_peer.SendMTUPacket(len, m_remoteEndpoint);
+ if (ok == false)
+ {
+ //m_peer.LogDebug("Send MTU failed for size " + size);
+
+ // failure
+ if (m_smallestFailedMTU == -1 || size < m_smallestFailedMTU)
+ {
+ m_smallestFailedMTU = size;
+ m_mtuAttemptFails++;
+ if (m_mtuAttemptFails >= m_peerConfiguration.ExpandMTUFailAttempts)
+ {
+ FinalizeMTU(m_largestSuccessfulMTU);
+ return;
+ }
+ }
+ ExpandMTU(now, false);
+ return;
+ }
+
+ m_lastSentMTUAttemptSize = size;
+ m_lastSentMTUAttemptTime = now;
+
+ //m_peer.LogDebug("Requesting MTU expand " + size + " bytes");
+ m_statistics.PacketSent(len, 1);
+ }
+
+ private void FinalizeMTU(int size)
+ {
+ if (m_expandMTUStatus == ExpandMTUStatus.Finished)
+ return;
+ m_expandMTUStatus = ExpandMTUStatus.Finished;
+ m_currentMTU = size;
+ m_peer.LogVerbose("Maximum Transmission Unit set to: " + m_currentMTU + " bytes");
+ return;
+ }
+
+ private void SendMTUSuccess(int size)
+ {
+ NetOutgoingMessage om = m_peer.CreateMessage(1);
+ om.Write(size);
+ om.m_messageType = NetMessageType.ExpandMTUSuccess;
+ int len = om.Encode(m_peer.m_sendBuffer, 0, 0);
+ bool connectionReset;
+ m_peer.SendPacket(len, m_remoteEndpoint, 1, out connectionReset);
+
+ // m_peer.LogDebug("Received MTU expand request for " + size + " bytes");
+
+ m_statistics.PacketSent(len, 1);
+ }
+
+ private void HandleExpandMTUSuccess(double now, int size)
+ {
+ if (m_largestSuccessfulMTU < size)
+ m_largestSuccessfulMTU = size;
+
+ if (size < m_currentMTU)
+ {
+ //m_peer.LogDebug("Received low MTU expand success (size " + size + "); current mtu is " + m_currentMTU);
+ return;
+ }
+
+ //m_peer.LogDebug("Expanding MTU to " + size);
+ m_currentMTU = size;
+ m_largestSuccessfulMTU = size;
+
+ ExpandMTU(now, true);
+ }
+ }
+}
diff --git a/Lidgren.Network/NetConnection.cs b/Lidgren.Network/NetConnection.cs
index 869fd2e..dcffbc2 100644
--- a/Lidgren.Network/NetConnection.cs
+++ b/Lidgren.Network/NetConnection.cs
@@ -81,6 +81,7 @@ namespace Lidgren.Network
m_queuedAcks = new NetQueue>(4);
m_statistics = new NetConnectionStatistics(this);
m_averageRoundtripTime = -1.0f;
+ m_currentMTU = m_peerConfiguration.MaximumTransmissionUnit;
}
internal void SetStatus(NetConnectionStatus status, string reason)
@@ -139,6 +140,9 @@ namespace Lidgren.Network
SendPing();
}
+ // handle expand mtu
+ MTUExpansionHeartbeat(now);
+
if (m_disconnectRequested)
{
ExecuteDisconnect(m_disconnectMessage, true);
@@ -153,7 +157,7 @@ namespace Lidgren.Network
//
byte[] sendBuffer = m_peer.m_sendBuffer;
- int mtu = m_peerConfiguration.m_maximumTransmissionUnit;
+ int mtu = m_currentMTU;
if ((frameCounter % 3) == 0) // coalesce a few frames
{
@@ -236,10 +240,10 @@ namespace Lidgren.Network
m_peer.VerifyNetworkThread();
int sz = om.GetEncodedSize();
- if (sz > m_peerConfiguration.m_maximumTransmissionUnit)
+ if (sz > m_currentMTU)
m_peer.LogWarning("Message larger than MTU! Fragmentation must have failed!");
- if (m_sendBufferWritePtr + sz > m_peerConfiguration.m_maximumTransmissionUnit)
+ if (m_sendBufferWritePtr + sz > m_currentMTU)
{
bool connReset; // TODO: handle connection reset
NetException.Assert(m_sendBufferWritePtr > 0 && m_sendBufferNumMessages > 0); // or else the message should have been fragmented earlier
@@ -279,7 +283,7 @@ namespace Lidgren.Network
if (chan == null)
chan = CreateSenderChannel(tp);
- if (msg.GetEncodedSize() > m_peerConfiguration.m_maximumTransmissionUnit)
+ if (msg.GetEncodedSize() > m_currentMTU)
throw new NetException("Message too large! Fragmentation failure?");
return chan.Enqueue(msg);
@@ -351,6 +355,14 @@ namespace Lidgren.Network
int pongNr = m_peer.m_receiveBuffer[ptr++];
ReceivedPong(now, pongNr);
break;
+ case NetMessageType.ExpandMTURequest:
+ SendMTUSuccess(payloadLength);
+ break;
+ case NetMessageType.ExpandMTUSuccess:
+ NetIncomingMessage emsg = m_peer.SetupReadHelperMessage(ptr, payloadLength);
+ int size = emsg.ReadInt32();
+ HandleExpandMTUSuccess(now, size);
+ break;
default:
m_peer.LogWarning("Connection received unhandled library message: " + tp);
break;
diff --git a/Lidgren.Network/NetMessageType.cs b/Lidgren.Network/NetMessageType.cs
index c91817d..a14e99c 100644
--- a/Lidgren.Network/NetMessageType.cs
+++ b/Lidgren.Network/NetMessageType.cs
@@ -169,5 +169,7 @@ namespace Lidgren.Network
DiscoveryResponse = 137,
NatPunchMessage = 138, // send between peers
NatIntroduction = 139, // send to master server
+ ExpandMTURequest = 140,
+ ExpandMTUSuccess = 141,
}
}
\ No newline at end of file
diff --git a/Lidgren.Network/NetPeer.Fragmentation.cs b/Lidgren.Network/NetPeer.Fragmentation.cs
index e039c82..0eed9ab 100644
--- a/Lidgren.Network/NetPeer.Fragmentation.cs
+++ b/Lidgren.Network/NetPeer.Fragmentation.cs
@@ -33,8 +33,9 @@ namespace Lidgren.Network
// create fragmentation specifics
int totalBytes = msg.LengthBytes;
- int mtu = m_configuration.MaximumTransmissionUnit;
-
+
+ // determine minimum mtu for all recipients
+ int mtu = GetMTU(recipients);
int bytesPerChunk = NetFragmentationHelper.GetBestChunkSize(group, totalBytes, mtu);
int numChunks = totalBytes / bytesPerChunk;
diff --git a/Lidgren.Network/NetPeer.Internal.cs b/Lidgren.Network/NetPeer.Internal.cs
index cd3a736..983b943 100644
--- a/Lidgren.Network/NetPeer.Internal.cs
+++ b/Lidgren.Network/NetPeer.Internal.cs
@@ -43,6 +43,9 @@ namespace Lidgren.Network
{
NetException.Assert(msg.m_incomingMessageType != NetIncomingMessageType.Error);
+ if (msg.MessageType == NetIncomingMessageType.UnconnectedData)
+ Console.WriteLine("x");
+
if (msg.m_isFragment)
{
HandleReleasedFragment(msg);
@@ -465,6 +468,7 @@ namespace Lidgren.Network
internal void AcceptConnection(NetConnection conn)
{
// LogDebug("Accepted connection " + conn);
+ conn.InitExpandMTU(NetTime.Now);
if (m_handshakes.Remove(conn.m_remoteEndpoint) == false)
LogWarning("AcceptConnection called but m_handshakes did not contain it!");
diff --git a/Lidgren.Network/NetPeer.LatencySimulation.cs b/Lidgren.Network/NetPeer.LatencySimulation.cs
index ba2226d..7d7e520 100644
--- a/Lidgren.Network/NetPeer.LatencySimulation.cs
+++ b/Lidgren.Network/NetPeer.LatencySimulation.cs
@@ -121,6 +121,8 @@ namespace Lidgren.Network
int bytesSent = m_socket.SendTo(data, 0, numBytes, SocketFlags.None, target);
if (numBytes != bytesSent)
LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!");
+
+ // LogDebug("Sent " + numBytes + " bytes");
}
catch (SocketException sx)
{
@@ -150,7 +152,76 @@ namespace Lidgren.Network
return true;
}
+ internal bool SendMTUPacket(int numBytes, IPEndPoint target)
+ {
+ try
+ {
+ m_socket.DontFragment = true;
+ int bytesSent = m_socket.SendTo(m_sendBuffer, 0, numBytes, SocketFlags.None, target);
+ if (numBytes != bytesSent)
+ LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!");
+
+ m_statistics.PacketSent(numBytes, 1);
+ }
+ catch (SocketException sx)
+ {
+ if (sx.SocketErrorCode == SocketError.MessageSize)
+ return false;
+ if (sx.SocketErrorCode == SocketError.WouldBlock)
+ {
+ // send buffer full?
+ LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration");
+ return true;
+ }
+ if (sx.SocketErrorCode == SocketError.ConnectionReset)
+ return true;
+ LogError("Failed to send packet: (" + sx.SocketErrorCode + ") " + sx);
+ }
+ catch (Exception ex)
+ {
+ LogError("Failed to send packet: " + ex);
+ }
+ finally
+ {
+ m_socket.DontFragment = false;
+ }
+ return true;
+ }
#else
+ internal bool SendMTUPacket(int numBytes, IPEndPoint target)
+ {
+ try
+ {
+ m_socket.DontFragment = true;
+ int bytesSent = m_socket.SendTo(m_sendBuffer, 0, numBytes, SocketFlags.None, target);
+ if (numBytes != bytesSent)
+ LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!");
+ }
+ catch (SocketException sx)
+ {
+ if (sx.SocketErrorCode == SocketError.MessageSize)
+ return false;
+ if (sx.SocketErrorCode == SocketError.WouldBlock)
+ {
+ // send buffer full?
+ LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration");
+ return true;
+ }
+ if (sx.SocketErrorCode == SocketError.ConnectionReset)
+ return true;
+ LogError("Failed to send packet: (" + sx.SocketErrorCode + ") " + sx);
+ }
+ catch (Exception ex)
+ {
+ LogError("Failed to send packet: " + ex);
+ }
+ finally
+ {
+ m_socket.DontFragment = false;
+ }
+ return true;
+ }
+
//
// Release - just send the packet straight away
//
diff --git a/Lidgren.Network/NetPeer.Send.cs b/Lidgren.Network/NetPeer.Send.cs
index a768faa..a55825e 100644
--- a/Lidgren.Network/NetPeer.Send.cs
+++ b/Lidgren.Network/NetPeer.Send.cs
@@ -44,7 +44,7 @@ namespace Lidgren.Network
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
int len = msg.LengthBytes;
- if (len <= m_configuration.MaximumTransmissionUnit)
+ if (len <= recipient.m_currentMTU)
{
Interlocked.Increment(ref msg.m_recyclingCount);
return recipient.EnqueueMessage(msg, method, sequenceChannel);
@@ -57,6 +57,18 @@ namespace Lidgren.Network
}
}
+ internal int GetMTU(IList recipients)
+ {
+ int mtu = int.MaxValue;
+ foreach (NetConnection conn in recipients)
+ {
+ int cmtu = conn.m_currentMTU;
+ if (cmtu < mtu)
+ mtu = cmtu;
+ }
+ return mtu;
+ }
+
public void SendMessage(NetOutgoingMessage msg, IList recipients, NetDeliveryMethod method, int sequenceChannel)
{
if (msg == null)
@@ -68,6 +80,8 @@ namespace Lidgren.Network
if (msg.m_isSent)
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
+ int mtu = GetMTU(recipients);
+
int len = msg.LengthBytes;
if (len <= m_configuration.MaximumTransmissionUnit)
{
diff --git a/Lidgren.Network/NetPeerConfiguration.cs b/Lidgren.Network/NetPeerConfiguration.cs
index 5898f9c..0755f5f 100644
--- a/Lidgren.Network/NetPeerConfiguration.cs
+++ b/Lidgren.Network/NetPeerConfiguration.cs
@@ -35,7 +35,6 @@ namespace Lidgren.Network
private IPAddress m_localAddress;
internal bool m_acceptIncomingConnections;
internal int m_maximumConnections;
- internal int m_maximumTransmissionUnit;
internal int m_defaultOutgoingMessageCapacity;
internal float m_pingInterval;
internal bool m_useMessageRecycling;
@@ -52,6 +51,13 @@ namespace Lidgren.Network
internal float m_minimumOneWayLatency;
internal float m_randomOneWayLatency;
+ // MTU
+ internal int m_maximumTransmissionUnit;
+ internal bool m_autoExpandMTU;
+ internal float m_expandMTUFrequency;
+ internal float m_expandMTUFactor;
+ internal int m_expandMTUFailAttempts;
+
public NetPeerConfiguration(string appIdentifier)
{
if (string.IsNullOrEmpty(appIdentifier))
@@ -83,6 +89,9 @@ namespace Lidgren.Network
// Total 1408 bytes
// Note that lidgren headers (5 bytes) are not included here; since it's part of the "mtu payload"
m_maximumTransmissionUnit = 1408;
+ m_autoExpandMTU = true;
+ m_expandMTUFrequency = 2.0f;
+ m_expandMTUFailAttempts = 5;
m_loss = 0.0f;
m_minimumOneWayLatency = 0.0f;
@@ -293,6 +302,38 @@ namespace Lidgren.Network
set { m_acceptIncomingConnections = value; }
}
+ ///
+ /// Gets or sets if the NetPeer should send large messages to try to expand the maximum transmission unit size
+ ///
+ public bool AutoExpandMTU
+ {
+ get { return m_autoExpandMTU; }
+ set
+ {
+ if (m_isLocked)
+ throw new NetException(c_isLockedMessage);
+ m_autoExpandMTU = value;
+ }
+ }
+
+ ///
+ /// Gets or sets how often to send large messages to expand MTU if AutoExpandMTU is enabled
+ ///
+ public float ExpandMTUFrequency
+ {
+ get { return m_expandMTUFrequency; }
+ set { m_expandMTUFrequency = value; }
+ }
+
+ ///
+ /// Gets or sets the number of failed expand mtu attempts to perform before setting final MTU
+ ///
+ public int ExpandMTUFailAttempts
+ {
+ get { return m_expandMTUFailAttempts; }
+ set { m_expandMTUFailAttempts = value; }
+ }
+
#if DEBUG
///
/// Gets or sets the simulated amount of sent packets lost from 0.0f to 1.0f
diff --git a/Samples/Chat/ChatClient/Program.cs b/Samples/Chat/ChatClient/Program.cs
index bf0e3e6..f255473 100644
--- a/Samples/Chat/ChatClient/Program.cs
+++ b/Samples/Chat/ChatClient/Program.cs
@@ -69,7 +69,7 @@ namespace ChatClient
Output(chat);
break;
default:
- Output("Unhandled type: " + im.MessageType);
+ Output("Unhandled type: " + im.MessageType + " " + im.LengthBytes + " bytes");
break;
}
}
diff --git a/Samples/Chat/ChatServer/Program.cs b/Samples/Chat/ChatServer/Program.cs
index 2435972..fe87439 100644
--- a/Samples/Chat/ChatServer/Program.cs
+++ b/Samples/Chat/ChatServer/Program.cs
@@ -77,7 +77,7 @@ namespace ChatServer
s_server.SendMessage(om, all, NetDeliveryMethod.ReliableOrdered, 0);
break;
default:
- Output("Unhandled type: " + im.MessageType);
+ Output("Unhandled type: " + im.MessageType + " " + im.LengthBytes + " bytes " + im.DeliveryMethod + "|" + im.SequenceChannel);
break;
}
}