diff --git a/Documentation/Documentation.chm b/Documentation/Documentation.chm index e3b9979..00ec493 100644 Binary files a/Documentation/Documentation.chm and b/Documentation/Documentation.chm differ diff --git a/Lidgren.Network/NetConnection.Handshake.cs b/Lidgren.Network/NetConnection.Handshake.cs index bdfa5c4..b930eb0 100644 --- a/Lidgren.Network/NetConnection.Handshake.cs +++ b/Lidgren.Network/NetConnection.Handshake.cs @@ -11,12 +11,15 @@ namespace Lidgren.Network internal bool m_connectionInitiator; internal string m_disconnectMessage; internal NetIncomingMessage m_remoteHailMessage; + internal float m_lastHandshakeSendTime; + internal int m_handshakeAttempts; /// /// The message that the remote part specified via Connect() or Approve() - can be null. /// public NetIncomingMessage RemoteHailMessage { get { return m_remoteHailMessage; } } + // heartbeat called when connection still is in m_handshakes of NetPeer internal void UnconnectedHeartbeat(float now) { m_peer.VerifyNetworkThread(); @@ -33,25 +36,50 @@ namespace Lidgren.Network // reconnect ExecuteDisconnect("Reconnecting", true); break; + case NetConnectionStatus.InitiatedConnect: // send another connect attempt - SendConnect(); + SendConnect(now); break; + case NetConnectionStatus.Disconnected: throw new NetException("This connection is Disconnected; spent. A new one should have been created"); case NetConnectionStatus.Disconnecting: // let disconnect finish first - return; + break; + case NetConnectionStatus.None: default: - SendConnect(); + SendConnect(now); break; } return; } - // TODO: handle dangling connections + if (now - m_lastHandshakeSendTime > m_peerConfiguration.m_resendHandshakeInterval) + { + if (m_handshakeAttempts > m_peerConfiguration.m_maximumHandshakeAttempts) + { + // failed to connect + ExecuteDisconnect("Failed to establish connection - no response from remote host", true); + return; + } + + // resend handshake + switch (m_status) + { + case NetConnectionStatus.InitiatedConnect: + SendConnect(now); + break; + case NetConnectionStatus.RespondedConnect: + SendConnectResponse(now, true); + break; + default: + m_peer.LogWarning("Time to resend handshake, but status is " + m_status); + break; + } + } } internal void ExecuteDisconnect(string reason, bool sendByeMessage) @@ -72,33 +100,48 @@ namespace Lidgren.Network SendDisconnect(reason, true); SetStatus(NetConnectionStatus.Disconnected, reason); + + // in case we're still in handshake + m_peer.m_handshakes.Remove(m_remoteEndpoint); + m_disconnectRequested = false; m_connectRequested = false; + m_handshakeAttempts = 0; } - internal void SendConnect() + internal void SendConnect(float now) { + m_peer.VerifyNetworkThread(); + NetOutgoingMessage om = m_peer.CreateMessage(m_peerConfiguration.AppIdentifier.Length + 1 + 4); om.m_messageType = NetMessageType.Connect; om.Write(m_peerConfiguration.AppIdentifier); om.Write(m_peer.m_uniqueIdentifier); - om.Write((float)NetTime.Now); + om.Write(now); WriteLocalHail(om); m_peer.SendLibrary(om, m_remoteEndpoint); - SetStatus(NetConnectionStatus.InitiatedConnect, "Locally requested connect"); m_connectRequested = false; + m_lastHandshakeSendTime = now; + m_handshakeAttempts++; + + if (m_handshakeAttempts > 1) + m_peer.LogDebug("Resending Connect..."); + SetStatus(NetConnectionStatus.InitiatedConnect, "Locally requested connect"); } - internal void SendConnectResponse(bool onLibraryThread) + internal void SendConnectResponse(float now, bool onLibraryThread) { + if (onLibraryThread) + m_peer.VerifyNetworkThread(); + NetOutgoingMessage om = m_peer.CreateMessage(m_peerConfiguration.AppIdentifier.Length + 1 + 4); om.m_messageType = NetMessageType.ConnectResponse; om.Write(m_peerConfiguration.AppIdentifier); om.Write(m_peer.m_uniqueIdentifier); - om.Write((float)NetTime.Now); + om.Write(now); WriteLocalHail(om); @@ -107,11 +150,20 @@ namespace Lidgren.Network else m_peer.m_unsentUnconnectedMessages.Enqueue(new NetTuple(m_remoteEndpoint, om)); + m_lastHandshakeSendTime = now; + m_handshakeAttempts++; + + if (m_handshakeAttempts > 1) + m_peer.LogDebug("Resending ConnectResponse..."); + SetStatus(NetConnectionStatus.RespondedConnect, "Remotely requested connect"); } internal void SendDisconnect(string reason, bool onLibraryThread) { + if (onLibraryThread) + m_peer.VerifyNetworkThread(); + NetOutgoingMessage om = m_peer.CreateMessage(reason); om.m_messageType = NetMessageType.Disconnect; if (onLibraryThread) @@ -141,6 +193,8 @@ namespace Lidgren.Network om.Write((float)NetTime.Now); m_peer.SendLibrary(om, m_remoteEndpoint); + m_handshakeAttempts = 0; + InitializePing(); if (m_status != NetConnectionStatus.Connected) SetStatus(NetConnectionStatus.Connected, "Connected to " + NetUtility.ToHexString(m_remoteUniqueIdentifier)); @@ -152,7 +206,8 @@ namespace Lidgren.Network public void Approve() { m_localHailMessage = null; - SendConnectResponse(false); + m_handshakeAttempts = 0; + SendConnectResponse((float)NetTime.Now, false); } /// @@ -162,7 +217,8 @@ namespace Lidgren.Network public void Approve(NetOutgoingMessage localHail) { m_localHailMessage = localHail; - SendConnectResponse(false); + m_handshakeAttempts = 0; + SendConnectResponse((float)NetTime.Now, false); } /// @@ -223,14 +279,14 @@ namespace Lidgren.Network return; } - SendConnectResponse(true); + SendConnectResponse((float)now, true); } return; } if (m_status == NetConnectionStatus.RespondedConnect) { // our ConnectResponse must have been lost - SendConnectResponse(true); + SendConnectResponse((float)now, true); return; } m_peer.LogDebug("Unhandled Connect: " + tp + ", status is " + m_status + " length: " + payloadLength); diff --git a/Lidgren.Network/NetConnection.cs b/Lidgren.Network/NetConnection.cs index 89c8642..2dab8d0 100644 --- a/Lidgren.Network/NetConnection.cs +++ b/Lidgren.Network/NetConnection.cs @@ -90,10 +90,14 @@ namespace Lidgren.Network if (status == m_status) return; + m_status = status; if (reason == null) reason = string.Empty; + // new status equals potentially new handshake attempts + m_handshakeAttempts = 0; + if (m_status == NetConnectionStatus.Connected) { m_timeoutDeadline = (float)NetTime.Now + m_peerConfiguration.m_connectionTimeout; diff --git a/Lidgren.Network/NetPeer.cs b/Lidgren.Network/NetPeer.cs index bb87e1e..5b523d4 100644 --- a/Lidgren.Network/NetPeer.cs +++ b/Lidgren.Network/NetPeer.cs @@ -229,7 +229,7 @@ namespace Lidgren.Network break; case NetConnectionStatus.RespondedConnect: // send another response - hs.SendConnectResponse(false); + hs.SendConnectResponse((float)NetTime.Now, false); break; default: // weird diff --git a/Lidgren.Network/NetPeerConfiguration.cs b/Lidgren.Network/NetPeerConfiguration.cs index df07cfb..08affe0 100644 --- a/Lidgren.Network/NetPeerConfiguration.cs +++ b/Lidgren.Network/NetPeerConfiguration.cs @@ -44,6 +44,8 @@ namespace Lidgren.Network internal int m_port; internal int m_receiveBufferSize; internal int m_sendBufferSize; + internal float m_resendHandshakeInterval; + internal int m_maximumHandshakeAttempts; // bad network simulation internal float m_loss; @@ -79,6 +81,8 @@ namespace Lidgren.Network m_pingInterval = 4.0f; m_connectionTimeout = 25.0f; m_useMessageRecycling = true; + m_resendHandshakeInterval = 3.0f; + m_maximumHandshakeAttempts = 5; // Maximum transmission unit // Ethernet can take 1500 bytes of payload, so lets stay below that. @@ -302,6 +306,24 @@ namespace Lidgren.Network set { m_acceptIncomingConnections = value; } } + /// + /// Gets or sets the number of seconds between handshake attempts + /// + public float ResendHandshakeInterval + { + get { return m_resendHandshakeInterval; } + set { m_resendHandshakeInterval = value; } + } + + /// + /// Gets or sets the maximum number of handshake attempts before failing to connect + /// + public int MaximumHandshakeAttempts + { + get { return m_maximumHandshakeAttempts; } + set { m_maximumHandshakeAttempts = value; } + } + /// /// Gets or sets if the NetPeer should send large messages to try to expand the maximum transmission unit size ///