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; } }