You've already forked lidgren-network-gen3
mirror of
https://github.com/lidgren/lidgren-network-gen3.git
synced 2026-05-16 15:16:33 +09:00
major update; gen 3.5
This commit is contained in:
@@ -22,11 +22,18 @@ using System.Text;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Fixed size vector of booleans
|
||||
/// </summary>
|
||||
public sealed class NetBitVector
|
||||
{
|
||||
private readonly int m_capacity;
|
||||
private readonly int[] m_data;
|
||||
private int m_numBitsSet;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of bits/booleans stored in this vector
|
||||
/// </summary>
|
||||
public int Capacity { get { return m_capacity; } }
|
||||
|
||||
public NetBitVector(int bitsCapacity)
|
||||
@@ -35,12 +42,21 @@ namespace Lidgren.Network
|
||||
m_data = new int[(bitsCapacity + 31) / 32];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if all bits/booleans are set to zero/false
|
||||
/// </summary>
|
||||
public bool IsEmpty()
|
||||
{
|
||||
foreach (int v in m_data)
|
||||
if (v != 0)
|
||||
return false;
|
||||
return true;
|
||||
return (m_numBitsSet == 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of bits/booleans set to one/true
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int Count()
|
||||
{
|
||||
return m_numBitsSet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -62,6 +78,8 @@ namespace Lidgren.Network
|
||||
cur |= firstBit << lastIndex;
|
||||
|
||||
m_data[lenMinusOne] = cur;
|
||||
|
||||
throw new NetException("TODO: update m_numBitsSet");
|
||||
}
|
||||
|
||||
public int GetFirstSetIndex()
|
||||
@@ -82,20 +100,41 @@ namespace Lidgren.Network
|
||||
return (idx * 32) + a;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bit/bool at the specified index
|
||||
/// </summary>
|
||||
public bool Get(int bitIndex)
|
||||
{
|
||||
NetException.Assert(bitIndex >= 0 && bitIndex < m_capacity);
|
||||
|
||||
return (m_data[bitIndex / 32] & (1 << (bitIndex % 32))) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets or clears the bit/bool at the specified index
|
||||
/// </summary>
|
||||
public void Set(int bitIndex, bool value)
|
||||
{
|
||||
NetException.Assert(bitIndex >= 0 && bitIndex < m_capacity);
|
||||
|
||||
int idx = bitIndex / 32;
|
||||
if (value)
|
||||
{
|
||||
if ((m_data[idx] & (1 << (bitIndex % 32))) == 0)
|
||||
m_numBitsSet++;
|
||||
m_data[idx] |= (1 << (bitIndex % 32));
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((m_data[idx] & (1 << (bitIndex % 32))) != 0)
|
||||
m_numBitsSet--;
|
||||
m_data[idx] &= (~(1 << (bitIndex % 32)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bit/bool at the specified index
|
||||
/// </summary>
|
||||
[System.Runtime.CompilerServices.IndexerName("Bit")]
|
||||
public bool this[int index]
|
||||
{
|
||||
@@ -103,9 +142,13 @@ namespace Lidgren.Network
|
||||
set { Set(index, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets all bits/booleans to zero/false
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
Array.Clear(m_data, 0, m_data.Length);
|
||||
m_numBitsSet = 0;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
||||
@@ -21,6 +21,9 @@ using System.Net;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Specialized version of NetPeer used for a "client" connection. It does not accept any incoming connections and maintains a ServerConnection property
|
||||
/// </summary>
|
||||
public class NetClient : NetPeer
|
||||
{
|
||||
/// <summary>
|
||||
@@ -53,9 +56,9 @@ namespace Lidgren.Network
|
||||
config.AcceptIncomingConnections = false;
|
||||
}
|
||||
|
||||
public override NetConnection Connect(IPEndPoint remoteEndpoint, NetOutgoingMessage approvalMessage)
|
||||
public override NetConnection Connect(IPEndPoint remoteEndpoint, NetOutgoingMessage hailMessage)
|
||||
{
|
||||
lock(m_connections)
|
||||
lock (m_connections)
|
||||
{
|
||||
if (m_connections.Count > 0)
|
||||
{
|
||||
@@ -63,7 +66,7 @@ namespace Lidgren.Network
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return base.Connect(remoteEndpoint, approvalMessage);
|
||||
return base.Connect(remoteEndpoint, hailMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -84,13 +87,13 @@ namespace Lidgren.Network
|
||||
/// <summary>
|
||||
/// Sends message to server
|
||||
/// </summary>
|
||||
public bool SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method)
|
||||
public NetSendResult SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method)
|
||||
{
|
||||
NetConnection serverConnection = ServerConnection;
|
||||
if (serverConnection == null)
|
||||
{
|
||||
//LogError("Cannot send message, no server connection!");
|
||||
return false;
|
||||
LogWarning("Cannot send message, no server connection!");
|
||||
return NetSendResult.Failed;
|
||||
}
|
||||
|
||||
return serverConnection.SendMessage(msg, method, 0);
|
||||
@@ -99,13 +102,13 @@ namespace Lidgren.Network
|
||||
/// <summary>
|
||||
/// Sends message to server
|
||||
/// </summary>
|
||||
public bool SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel)
|
||||
public NetSendResult SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel)
|
||||
{
|
||||
NetConnection serverConnection = ServerConnection;
|
||||
if (serverConnection == null)
|
||||
{
|
||||
//LogError("Cannot send message, no server connection!");
|
||||
return false;
|
||||
LogWarning("Cannot send message, no server connection!");
|
||||
return NetSendResult.Failed;
|
||||
}
|
||||
|
||||
return serverConnection.SendMessage(msg, method, sequenceChannel);
|
||||
|
||||
@@ -1,263 +1,361 @@
|
||||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetConnection
|
||||
{
|
||||
internal bool m_connectRequested;
|
||||
internal string m_disconnectByeMessage;
|
||||
internal bool m_disconnectRequested;
|
||||
internal bool m_connectionInitiator;
|
||||
internal double m_connectInitationTime; // regardless of initiator
|
||||
internal NetOutgoingMessage m_approvalMessage;
|
||||
internal string m_disconnectMessage;
|
||||
internal NetIncomingMessage m_remoteHailMessage;
|
||||
|
||||
internal void SetStatus(NetConnectionStatus status, string reason)
|
||||
/// <summary>
|
||||
/// The message that the remote part specified via Connect() or Approve() - can be null.
|
||||
/// </summary>
|
||||
public NetIncomingMessage RemoteHailMessage { get { return m_remoteHailMessage; } }
|
||||
|
||||
internal void UnconnectedHeartbeat(float now)
|
||||
{
|
||||
if (status == m_status)
|
||||
m_peer.VerifyNetworkThread();
|
||||
|
||||
if (m_disconnectRequested)
|
||||
ExecuteDisconnect(m_disconnectMessage, true);
|
||||
|
||||
if (m_connectRequested)
|
||||
{
|
||||
switch (m_status)
|
||||
{
|
||||
case NetConnectionStatus.Connected:
|
||||
case NetConnectionStatus.RespondedConnect:
|
||||
// reconnect
|
||||
ExecuteDisconnect("Reconnecting", true);
|
||||
break;
|
||||
case NetConnectionStatus.InitiatedConnect:
|
||||
// send another connect attempt
|
||||
SendConnect();
|
||||
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;
|
||||
case NetConnectionStatus.None:
|
||||
default:
|
||||
SendConnect();
|
||||
break;
|
||||
}
|
||||
return;
|
||||
m_status = status;
|
||||
if (reason == null)
|
||||
reason = string.Empty;
|
||||
}
|
||||
|
||||
if (m_peerConfiguration.IsMessageTypeEnabled(NetIncomingMessageType.StatusChanged))
|
||||
{
|
||||
NetIncomingMessage info = m_owner.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_owner.ReleaseMessage(info);
|
||||
}
|
||||
else
|
||||
{
|
||||
// app dont want those messages, update visible status immediately
|
||||
m_visibleStatus = m_status;
|
||||
}
|
||||
// TODO: handle dangling connections
|
||||
}
|
||||
|
||||
private void SendConnect()
|
||||
internal void ExecuteDisconnect(string reason, bool sendByeMessage)
|
||||
{
|
||||
m_owner.VerifyNetworkThread();
|
||||
m_peer.VerifyNetworkThread();
|
||||
|
||||
switch (m_status)
|
||||
// m_peer.LogDebug("Executing disconnect");
|
||||
|
||||
// clear send queues
|
||||
for (int i = 0; i < m_sendChannels.Length; i++)
|
||||
{
|
||||
case NetConnectionStatus.Connected:
|
||||
// reconnect
|
||||
m_disconnectByeMessage = "Reconnecting";
|
||||
ExecuteDisconnect(true);
|
||||
FinishDisconnect();
|
||||
break;
|
||||
case NetConnectionStatus.Connecting:
|
||||
case NetConnectionStatus.None:
|
||||
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;
|
||||
NetSenderChannelBase channel = m_sendChannels[i];
|
||||
if (channel != null)
|
||||
channel.Reset();
|
||||
}
|
||||
|
||||
if (sendByeMessage)
|
||||
SendDisconnect(reason, true);
|
||||
|
||||
SetStatus(NetConnectionStatus.Disconnected, reason);
|
||||
m_disconnectRequested = false;
|
||||
m_connectRequested = false;
|
||||
|
||||
// start handshake
|
||||
|
||||
int len = 2 + m_peerConfiguration.AppIdentifier.Length + 8 + 4 + (m_approvalMessage == null ? 0 : m_approvalMessage.LengthBytes);
|
||||
NetOutgoingMessage om = m_owner.CreateMessage(len);
|
||||
om.m_libType = NetMessageLibraryType.Connect;
|
||||
om.Write(m_peerConfiguration.AppIdentifier);
|
||||
om.Write(m_owner.m_uniqueIdentifier);
|
||||
|
||||
if (m_approvalMessage == null)
|
||||
{
|
||||
om.WriteVariableUInt32(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
om.WriteVariableUInt32((uint)m_approvalMessage.LengthBits);
|
||||
om.Write(m_approvalMessage);
|
||||
}
|
||||
m_owner.LogVerbose("Sending Connect");
|
||||
|
||||
m_owner.SendLibraryImmediately(om, m_remoteEndpoint);
|
||||
|
||||
m_connectInitationTime = NetTime.Now;
|
||||
SetStatus(NetConnectionStatus.Connecting, "Connecting");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
internal void SendConnectResponse()
|
||||
internal void SendConnect()
|
||||
{
|
||||
double now = NetTime.Now;
|
||||
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);
|
||||
|
||||
NetOutgoingMessage reply = m_owner.CreateMessage(4);
|
||||
reply.m_libType = NetMessageLibraryType.ConnectResponse;
|
||||
reply.Write((float)now);
|
||||
WriteLocalHail(om);
|
||||
|
||||
m_peer.SendLibrary(om, m_remoteEndpoint);
|
||||
|
||||
m_owner.LogVerbose("Sending LibraryConnectResponse");
|
||||
m_owner.SendLibraryImmediately(reply, m_remoteEndpoint);
|
||||
SetStatus(NetConnectionStatus.InitiatedConnect, "Locally requested connect");
|
||||
m_connectRequested = false;
|
||||
}
|
||||
|
||||
internal void SendConnectResponse(bool onLibraryThread)
|
||||
{
|
||||
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);
|
||||
|
||||
WriteLocalHail(om);
|
||||
|
||||
if (onLibraryThread)
|
||||
m_peer.SendLibrary(om, m_remoteEndpoint);
|
||||
else
|
||||
m_peer.m_unsentUnconnectedMessages.Enqueue(new NetTuple<System.Net.IPEndPoint, NetOutgoingMessage>(m_remoteEndpoint, om));
|
||||
|
||||
SetStatus(NetConnectionStatus.RespondedConnect, "Remotely requested connect");
|
||||
}
|
||||
|
||||
internal void SendDisconnect(string reason, bool onLibraryThread)
|
||||
{
|
||||
NetOutgoingMessage om = m_peer.CreateMessage(reason);
|
||||
om.m_messageType = NetMessageType.Disconnect;
|
||||
if (onLibraryThread)
|
||||
m_peer.SendLibrary(om, m_remoteEndpoint);
|
||||
else
|
||||
m_peer.m_unsentUnconnectedMessages.Enqueue(new NetTuple<System.Net.IPEndPoint, NetOutgoingMessage>(m_remoteEndpoint, om));
|
||||
}
|
||||
|
||||
private void WriteLocalHail(NetOutgoingMessage om)
|
||||
{
|
||||
if (m_localHailMessage != null)
|
||||
{
|
||||
byte[] hi = m_localHailMessage.PeekDataBuffer();
|
||||
if (hi != null && hi.Length >= m_localHailMessage.LengthBytes)
|
||||
{
|
||||
if (om.LengthBytes + m_localHailMessage.LengthBytes > m_peerConfiguration.m_maximumTransmissionUnit - 10)
|
||||
throw new NetException("Hail message too large; can maximally be " + (m_peerConfiguration.m_maximumTransmissionUnit - 10 - om.LengthBytes));
|
||||
om.Write(m_localHailMessage.PeekDataBuffer(), 0, m_localHailMessage.LengthBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void SendConnectionEstablished()
|
||||
{
|
||||
double now = NetTime.Now;
|
||||
NetOutgoingMessage om = m_peer.CreateMessage(0);
|
||||
om.m_messageType = NetMessageType.ConnectionEstablished;
|
||||
m_peer.SendLibrary(om, m_remoteEndpoint);
|
||||
|
||||
NetOutgoingMessage ce = m_owner.CreateMessage(4);
|
||||
ce.m_libType = NetMessageLibraryType.ConnectionEstablished;
|
||||
ce.Write((float)now);
|
||||
|
||||
m_owner.LogVerbose("Sending LibraryConnectionEstablished");
|
||||
m_owner.SendLibraryImmediately(ce, m_remoteEndpoint);
|
||||
m_sentPingTime = (float)NetTime.Now - (m_peerConfiguration.PingInterval / 2.0f); // delay ping for a little while
|
||||
if (m_status != NetConnectionStatus.Connected)
|
||||
SetStatus(NetConnectionStatus.Connected, "Connected to " + NetUtility.ToHexString(m_remoteUniqueIdentifier));
|
||||
}
|
||||
|
||||
internal void ExecuteDisconnect(bool sendByeMessage)
|
||||
/// <summary>
|
||||
/// Approves this connection; sending a connection response to the remote host
|
||||
/// </summary>
|
||||
public void Approve()
|
||||
{
|
||||
m_owner.VerifyNetworkThread();
|
||||
m_localHailMessage = null;
|
||||
SendConnectResponse(false);
|
||||
}
|
||||
|
||||
if (m_status == NetConnectionStatus.Disconnected || m_status == NetConnectionStatus.None)
|
||||
return;
|
||||
/// <summary>
|
||||
/// Approves this connection; sending a connection response to the remote host
|
||||
/// </summary>
|
||||
/// <param name="localHail">The local hail message that will be set as RemoteHailMessage on the remote host</param>
|
||||
public void Approve(NetOutgoingMessage localHail)
|
||||
{
|
||||
m_localHailMessage = localHail;
|
||||
SendConnectResponse(false);
|
||||
}
|
||||
|
||||
if (sendByeMessage)
|
||||
/// <summary>
|
||||
/// Denies this connection; disconnecting it
|
||||
/// </summary>
|
||||
public void Deny()
|
||||
{
|
||||
Deny("");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Denies this connection; disconnecting it
|
||||
/// </summary>
|
||||
/// <param name="reason">The stated reason for the disconnect, readable as a string in the StatusChanged message on the remote host</param>
|
||||
public void Deny(string reason)
|
||||
{
|
||||
// send disconnect; remove from handshakes
|
||||
SendDisconnect(reason, false);
|
||||
|
||||
// remove from handshakes
|
||||
m_peer.m_handshakes.Remove(m_remoteEndpoint); // TODO: make this more thread safe? we're on user thread
|
||||
}
|
||||
|
||||
internal void ReceivedHandshake(NetMessageType tp, int ptr, int payloadLength)
|
||||
{
|
||||
m_peer.VerifyNetworkThread();
|
||||
|
||||
byte[] hail;
|
||||
switch (tp)
|
||||
{
|
||||
NetOutgoingMessage om = m_owner.CreateLibraryMessage(NetMessageLibraryType.Disconnect, m_disconnectByeMessage);
|
||||
SendLibrary(om);
|
||||
}
|
||||
|
||||
m_owner.LogVerbose("Executing Disconnect(" + m_disconnectByeMessage + ")");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
internal void FinishDisconnect()
|
||||
{
|
||||
m_owner.VerifyNetworkThread();
|
||||
|
||||
if (m_status == NetConnectionStatus.Disconnected || m_status == NetConnectionStatus.None)
|
||||
return;
|
||||
|
||||
m_owner.LogVerbose("Finishing Disconnect(" + m_disconnectByeMessage + ")");
|
||||
|
||||
if (m_unsentMessages.Count > 0)
|
||||
m_owner.LogDebug(m_unsentMessages.Count + " unsent messages were not sent before disconnected");
|
||||
|
||||
// release some held memory
|
||||
m_unackedSends.Clear();
|
||||
m_acknowledgesToSend.Clear();
|
||||
foreach(var wma in m_withheldMessages)
|
||||
if (wma != null)
|
||||
wma.Clear();
|
||||
m_fragmentGroups.Clear();
|
||||
|
||||
SetStatus(NetConnectionStatus.Disconnected, m_disconnectByeMessage);
|
||||
m_disconnectByeMessage = null;
|
||||
m_connectionInitiator = false;
|
||||
}
|
||||
|
||||
private void HandleIncomingHandshake(NetMessageLibraryType libType, int ptr, int payloadBitsLength)
|
||||
{
|
||||
m_owner.VerifyNetworkThread();
|
||||
|
||||
switch (libType)
|
||||
{
|
||||
case NetMessageLibraryType.Connect:
|
||||
|
||||
m_owner.LogDebug("Received Connect");
|
||||
|
||||
if (m_status == NetConnectionStatus.Connecting)
|
||||
case NetMessageType.Connect:
|
||||
if (m_status == NetConnectionStatus.None)
|
||||
{
|
||||
// our connectresponse must have been lost, send another one
|
||||
SendConnectResponse();
|
||||
// Whee! Server full has already been checked
|
||||
bool ok = ValidateHandshakeData(ptr, payloadLength, out hail);
|
||||
if (ok)
|
||||
{
|
||||
if (hail != null)
|
||||
{
|
||||
m_remoteHailMessage = m_peer.CreateIncomingMessage(NetIncomingMessageType.Data, hail);
|
||||
m_remoteHailMessage.LengthBits = (hail.Length * 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_remoteHailMessage = null;
|
||||
}
|
||||
|
||||
if (m_peerConfiguration.IsMessageTypeEnabled(NetIncomingMessageType.ConnectionApproval))
|
||||
{
|
||||
// ok, let's not add connection just yet
|
||||
NetIncomingMessage appMsg = m_peer.CreateIncomingMessage(NetIncomingMessageType.ConnectionApproval, m_remoteHailMessage.LengthBytes);
|
||||
appMsg.m_senderConnection = this;
|
||||
appMsg.m_senderEndpoint = this.m_remoteEndpoint;
|
||||
appMsg.Write(m_remoteHailMessage.m_data, 0, m_remoteHailMessage.LengthBytes);
|
||||
m_peer.ReleaseMessage(appMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
SendConnectResponse(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (m_connectionInitiator)
|
||||
m_owner.LogError("Received Connect; altho we're connect initiator! Status is " + m_status);
|
||||
else
|
||||
m_owner.LogError("NetConnection.HandleIncomingHandshake() passed LibraryConnect but status is " + m_status + "!? now is " + NetTime.Now + " LastHeardFrom is " + m_lastHeardFrom);
|
||||
if (m_status == NetConnectionStatus.RespondedConnect)
|
||||
{
|
||||
// our ConnectResponse must have been lost
|
||||
SendConnectResponse(true);
|
||||
return;
|
||||
}
|
||||
m_peer.LogDebug("Unhandled handshake: " + tp + ", status is " + m_status + " length: " + payloadLength);
|
||||
break;
|
||||
case NetMessageLibraryType.ConnectResponse:
|
||||
if (!m_connectionInitiator)
|
||||
case NetMessageType.ConnectResponse:
|
||||
switch (m_status)
|
||||
{
|
||||
m_owner.LogError("NetConnection.HandleIncomingHandshake() passed LibraryConnectResponse, but we're not initiator!");
|
||||
// weird, just drop it
|
||||
return;
|
||||
case NetConnectionStatus.InitiatedConnect:
|
||||
// awesome
|
||||
bool ok = ValidateHandshakeData(ptr, payloadLength, out hail);
|
||||
if (ok)
|
||||
{
|
||||
if (hail != null)
|
||||
{
|
||||
m_remoteHailMessage = m_peer.CreateIncomingMessage(NetIncomingMessageType.Data, hail);
|
||||
m_remoteHailMessage.LengthBits = (hail.Length * 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_remoteHailMessage = null;
|
||||
}
|
||||
|
||||
m_peer.AcceptConnection(this);
|
||||
SendConnectionEstablished();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case NetConnectionStatus.RespondedConnect:
|
||||
// hello, wtf?
|
||||
break;
|
||||
case NetConnectionStatus.Disconnecting:
|
||||
case NetConnectionStatus.Disconnected:
|
||||
case NetConnectionStatus.None:
|
||||
// wtf? anyway, bye!
|
||||
break;
|
||||
case NetConnectionStatus.Connected:
|
||||
// my ConnectionEstablished must have been lost, send another one
|
||||
SendConnectionEstablished();
|
||||
return;
|
||||
}
|
||||
|
||||
m_owner.LogDebug("Received ConnectResponse");
|
||||
|
||||
if (m_status == NetConnectionStatus.Connecting)
|
||||
{
|
||||
m_owner.m_statistics.m_bytesAllocated += NetUtility.BytesToHoldBits(payloadBitsLength);
|
||||
|
||||
float remoteNetTime = BitConverter.ToSingle(m_owner.m_receiveBuffer, ptr);
|
||||
ptr += 4;
|
||||
|
||||
// excellent, handshake making progress; send connectionestablished
|
||||
SetStatus(NetConnectionStatus.Connected, "Connected");
|
||||
|
||||
SendConnectionEstablished();
|
||||
|
||||
// setup initial ping estimation
|
||||
InitializeLatency((float)(NetTime.Now - m_connectInitationTime), remoteNetTime);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_status == NetConnectionStatus.Connected)
|
||||
{
|
||||
// received (another) connectresponse; our connectionestablished must have been lost, send another one
|
||||
SendConnectionEstablished();
|
||||
return;
|
||||
}
|
||||
|
||||
m_owner.LogWarning("NetConnection.HandleIncomingHandshake() passed " + libType + ", but status is " + m_status);
|
||||
break;
|
||||
case NetMessageLibraryType.ConnectionEstablished:
|
||||
|
||||
m_owner.LogDebug("Received ConnectionEstablished");
|
||||
|
||||
if (!m_connectionInitiator && m_status == NetConnectionStatus.Connecting)
|
||||
case NetMessageType.ConnectionEstablished:
|
||||
switch (m_status)
|
||||
{
|
||||
float remoteNetTime = BitConverter.ToSingle(m_owner.m_receiveBuffer, ptr);
|
||||
|
||||
// handshake done
|
||||
InitializeLatency((float)(NetTime.Now - m_connectInitationTime), remoteNetTime);
|
||||
|
||||
SetStatus(NetConnectionStatus.Connected, "Connected");
|
||||
return;
|
||||
case NetConnectionStatus.Connected:
|
||||
// ok...
|
||||
break;
|
||||
case NetConnectionStatus.Disconnected:
|
||||
case NetConnectionStatus.Disconnecting:
|
||||
case NetConnectionStatus.None:
|
||||
// too bad, almost made it
|
||||
break;
|
||||
case NetConnectionStatus.InitiatedConnect:
|
||||
// weird, should have been ConnectResponse...
|
||||
break;
|
||||
case NetConnectionStatus.RespondedConnect:
|
||||
// awesome
|
||||
m_peer.AcceptConnection(this);
|
||||
m_sentPingTime = (float)NetTime.Now - (m_peerConfiguration.PingInterval / 2.0f); // delay ping for a little while
|
||||
SetStatus(NetConnectionStatus.Connected, "Connected to " + NetUtility.ToHexString(m_remoteUniqueIdentifier));
|
||||
return;
|
||||
}
|
||||
|
||||
m_owner.LogWarning("NetConnection.HandleIncomingHandshake() passed " + libType + ", but initiator is " + m_connectionInitiator + " and status is " + m_status);
|
||||
break;
|
||||
case NetMessageLibraryType.Disconnect:
|
||||
// extract bye message
|
||||
NetIncomingMessage im = m_owner.CreateIncomingMessage(NetIncomingMessageType.Data, m_owner.m_receiveBuffer, ptr, NetUtility.BytesToHoldBits(payloadBitsLength));
|
||||
im.m_bitLength = payloadBitsLength;
|
||||
m_disconnectByeMessage = im.ReadString();
|
||||
FinishDisconnect();
|
||||
case NetMessageType.Disconnect:
|
||||
// ouch
|
||||
string reason = "Ouch";
|
||||
try
|
||||
{
|
||||
NetIncomingMessage inc = m_peer.SetupReadHelperMessage(ptr, payloadLength);
|
||||
reason = inc.ReadString();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
SetStatus(NetConnectionStatus.Disconnected, reason);
|
||||
break;
|
||||
default:
|
||||
// huh?
|
||||
m_owner.LogWarning("Unhandled library type in " + this + ": " + libType);
|
||||
m_peer.LogDebug("Unhandled handshake: " + tp + " length: " + payloadLength);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateHandshakeData(int ptr, int payloadLength, out byte[] hail)
|
||||
{
|
||||
hail = null;
|
||||
|
||||
// create temporary incoming message
|
||||
NetIncomingMessage msg = m_peer.SetupReadHelperMessage(ptr, payloadLength);
|
||||
try
|
||||
{
|
||||
string remoteAppIdentifier = msg.ReadString();
|
||||
long remoteUniqueIdentifier = msg.ReadInt64();
|
||||
|
||||
int remainingBytes = payloadLength - (msg.PositionInBytes - ptr);
|
||||
if (remainingBytes > 0)
|
||||
hail = msg.ReadBytes(remainingBytes);
|
||||
|
||||
if (remoteAppIdentifier != m_peer.m_configuration.AppIdentifier)
|
||||
{
|
||||
// wrong app identifier
|
||||
ExecuteDisconnect("Wrong application identifier!", true);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_remoteUniqueIdentifier = remoteUniqueIdentifier;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
// whatever; we failed
|
||||
ExecuteDisconnect("Handshake data validation failed", true);
|
||||
m_peer.LogWarning("ReadRemoteHandshakeData failed: " + ex.Message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Disconnect(string byeMessage)
|
||||
{
|
||||
// user or library thread
|
||||
if (m_status == NetConnectionStatus.None || m_status == NetConnectionStatus.Disconnected)
|
||||
return;
|
||||
|
||||
m_peer.LogVerbose("Disconnect requested for " + this);
|
||||
m_disconnectMessage = byeMessage;
|
||||
|
||||
if (m_status != NetConnectionStatus.Disconnected && m_status != NetConnectionStatus.None)
|
||||
SetStatus(NetConnectionStatus.Disconnecting, byeMessage);
|
||||
|
||||
m_disconnectRequested = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,161 +1,71 @@
|
||||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetConnection
|
||||
{
|
||||
//
|
||||
// Connection keepalive and latency calculation
|
||||
//
|
||||
internal float m_averageRoundtripTime = 0.05f;
|
||||
private byte m_lastSentPingNumber;
|
||||
private double m_lastPingSendTime;
|
||||
private double m_nextPing;
|
||||
internal double m_lastSendRespondedTo;
|
||||
|
||||
// remote + diff = local
|
||||
// diff = local - remote
|
||||
// local - diff = remote
|
||||
internal double m_remoteToLocalNetTime = double.MinValue;
|
||||
private float m_sentPingTime;
|
||||
private int m_sentPingNumber;
|
||||
private float m_averageRoundtripTime;
|
||||
private float m_timeoutDeadline = float.MaxValue;
|
||||
|
||||
public float AverageRoundtripTime { get { return m_averageRoundtripTime; } }
|
||||
|
||||
internal void InitializeLatency(float rtt, float remoteNetTime)
|
||||
internal void SendPing()
|
||||
{
|
||||
m_averageRoundtripTime = Math.Max(0.005f, rtt - 0.005f); // TODO: find out why initial ping always overshoots
|
||||
m_nextPing = NetTime.Now + m_peerConfiguration.m_pingFrequency / 2.0f;
|
||||
m_peer.VerifyNetworkThread();
|
||||
|
||||
double currentRemoteNetTime = remoteNetTime - (rtt * 0.5);
|
||||
m_remoteToLocalNetTime = (float)(NetTime.Now - currentRemoteNetTime);
|
||||
m_sentPingNumber++;
|
||||
if (m_sentPingNumber >= 256)
|
||||
m_sentPingNumber = 0;
|
||||
m_sentPingTime = (float)NetTime.Now;
|
||||
NetOutgoingMessage om = m_peer.CreateMessage(1);
|
||||
om.Write((byte)m_sentPingNumber);
|
||||
om.m_messageType = NetMessageType.Ping;
|
||||
|
||||
StringBuilder bdr = new StringBuilder();
|
||||
bdr.Append("Initialized average roundtrip time to: ");
|
||||
bdr.Append(NetTime.ToReadable(m_averageRoundtripTime));
|
||||
bdr.Append(" remote time diff to ");
|
||||
bdr.Append(NetTime.ToReadable(m_remoteToLocalNetTime));
|
||||
m_owner.LogDebug(bdr.ToString());
|
||||
int len = om.Encode(m_peer.m_sendBuffer, 0, 0);
|
||||
bool connectionReset;
|
||||
m_peer.SendPacket(len, m_remoteEndpoint, 1, out connectionReset);
|
||||
}
|
||||
|
||||
internal void HandleIncomingPing(byte pingNumber)
|
||||
internal void SendPong(int pingNumber)
|
||||
{
|
||||
double now = NetTime.Now;
|
||||
m_peer.VerifyNetworkThread();
|
||||
|
||||
// send pong
|
||||
NetOutgoingMessage pong = m_owner.CreateMessage(1 + 8);
|
||||
pong.m_libType = NetMessageLibraryType.Pong;
|
||||
pong.Write((byte)pingNumber);
|
||||
pong.Write(now);
|
||||
NetOutgoingMessage om = m_peer.CreateMessage(1);
|
||||
om.Write((byte)pingNumber);
|
||||
om.m_messageType = NetMessageType.Pong;
|
||||
|
||||
m_owner.LogDebug("Sending pong for remote ping #" + pingNumber);
|
||||
|
||||
m_owner.SendLibraryImmediately(pong, m_remoteEndpoint);
|
||||
int len = om.Encode(m_peer.m_sendBuffer, 0, 0);
|
||||
bool connectionReset;
|
||||
m_peer.SendPacket(len, m_remoteEndpoint, 1, out connectionReset);
|
||||
}
|
||||
|
||||
internal void HandleIncomingPong(double receiveNow, byte pingNumber, double remoteNetTime)
|
||||
internal void ReceivedPong(float now, int pongNumber)
|
||||
{
|
||||
// verify it´s the correct ping number
|
||||
if (pingNumber != m_lastSentPingNumber)
|
||||
if (pongNumber != m_sentPingNumber)
|
||||
{
|
||||
m_owner.LogError("Received wrong pong number; got " + pingNumber + " expected " + m_lastSentPingNumber);
|
||||
|
||||
// but still, a pong must have been triggered by a ping...
|
||||
double diff = receiveNow - m_lastSendRespondedTo;
|
||||
if (diff > 0)
|
||||
m_lastSendRespondedTo += (diff / 2); // postpone timing out a bit
|
||||
|
||||
m_peer.LogVerbose("Ping/Pong mismatch; dropped message?");
|
||||
return;
|
||||
}
|
||||
|
||||
double now = NetTime.Now; // need exact number
|
||||
|
||||
m_lastHeardFrom = now;
|
||||
if (m_lastPingSendTime > m_lastSendRespondedTo)
|
||||
m_lastSendRespondedTo = m_lastPingSendTime;
|
||||
m_timeoutDeadline = now + m_peerConfiguration.m_connectionTimeout;
|
||||
|
||||
double rtt = now - m_lastPingSendTime;
|
||||
float rtt = now - m_sentPingTime;
|
||||
NetException.Assert(rtt >= 0);
|
||||
|
||||
// update clock sync
|
||||
double currentRemoteNetTime = remoteNetTime - (rtt * 0.5);
|
||||
double foundDiffRemoteToLocal = receiveNow - currentRemoteNetTime;
|
||||
|
||||
if (m_remoteToLocalNetTime == double.MinValue)
|
||||
m_remoteToLocalNetTime = foundDiffRemoteToLocal;
|
||||
if (m_averageRoundtripTime < 0)
|
||||
{
|
||||
m_averageRoundtripTime = rtt; // initial estimate
|
||||
m_peer.LogDebug("Initiated average roundtrip time to " + NetTime.ToReadable(m_averageRoundtripTime));
|
||||
}
|
||||
else
|
||||
m_remoteToLocalNetTime = ((m_remoteToLocalNetTime + foundDiffRemoteToLocal) * 0.5);
|
||||
|
||||
m_averageRoundtripTime = (m_averageRoundtripTime * 0.7f) + (float)(rtt * 0.3f);
|
||||
m_owner.LogVerbose("Found rtt: " + NetTime.ToReadable(rtt) + ", new average: " + NetTime.ToReadable(m_averageRoundtripTime) + " new time diff: " + NetTime.ToReadable(m_remoteToLocalNetTime));
|
||||
}
|
||||
|
||||
internal void KeepAliveHeartbeat(double now)
|
||||
{
|
||||
// do keepalive and latency pings
|
||||
if (m_status == NetConnectionStatus.Disconnected || m_status == NetConnectionStatus.None)
|
||||
return;
|
||||
|
||||
// force ack sending?
|
||||
if (now > m_nextForceAckTime)
|
||||
{
|
||||
// send keepalive thru regular channels to give acks something to piggyback on
|
||||
NetOutgoingMessage pig = m_owner.CreateMessage(1);
|
||||
pig.m_libType = NetMessageLibraryType.KeepAlive;
|
||||
SendLibrary(pig);
|
||||
m_nextForceAckTime = now + m_peerConfiguration.m_maxAckDelayTime;
|
||||
m_averageRoundtripTime = (m_averageRoundtripTime * 0.7f) + (float)(rtt * 0.3f);
|
||||
m_peer.LogVerbose("Updated average roundtrip time to " + NetTime.ToReadable(m_averageRoundtripTime));
|
||||
}
|
||||
|
||||
// timeout
|
||||
if (now > m_lastSendRespondedTo + m_peerConfiguration.m_connectionTimeout)
|
||||
{
|
||||
m_owner.LogDebug(this + " timed out; now is " + now + " last responded to is: " + m_lastSendRespondedTo);
|
||||
Disconnect("Timed out after " + (now - m_lastSendRespondedTo) + " seconds");
|
||||
return;
|
||||
}
|
||||
|
||||
// ping time?
|
||||
if (now >= m_nextPing)
|
||||
{
|
||||
//
|
||||
// send ping
|
||||
//
|
||||
now = NetTime.Now; // need exact number
|
||||
m_lastSentPingNumber++;
|
||||
m_lastPingSendTime = now;
|
||||
|
||||
m_owner.LogDebug("Sending ping #" + m_lastSentPingNumber);
|
||||
|
||||
// in case of not heard for a while
|
||||
if (m_lastSendRespondedTo > 0 && (now > m_lastSendRespondedTo + (m_owner.Configuration.m_pingFrequency * 1.5f)))
|
||||
m_nextPing = now + (m_owner.Configuration.m_pingFrequency * 0.5f); // double ping rate
|
||||
else
|
||||
m_nextPing = now + m_owner.Configuration.m_pingFrequency;
|
||||
|
||||
NetOutgoingMessage ping = m_owner.CreateMessage(1);
|
||||
ping.m_libType = NetMessageLibraryType.Ping;
|
||||
ping.Write((byte)m_lastSentPingNumber);
|
||||
|
||||
m_owner.SendLibraryImmediately(ping, m_remoteEndpoint);
|
||||
}
|
||||
m_peer.LogVerbose("Timeout deadline pushed to " + m_timeoutDeadline);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,9 @@ using System.Diagnostics;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Statistics for a NetConnection instance
|
||||
/// </summary>
|
||||
public sealed class NetConnectionStatistics
|
||||
{
|
||||
private readonly NetConnection m_connection;
|
||||
@@ -77,23 +80,12 @@ namespace Lidgren.Network
|
||||
/// </summary>
|
||||
public int ResentMessages { get { return m_resentMessages; } }
|
||||
|
||||
public double LastSendRespondedTo { get { return m_connection.m_lastSendRespondedTo; } }
|
||||
|
||||
public int MostSends
|
||||
{
|
||||
get
|
||||
{
|
||||
int most = 0;
|
||||
foreach (var a in m_connection.m_unackedSends)
|
||||
if (a.NumSends > most)
|
||||
most = a.NumSends;
|
||||
return most;
|
||||
}
|
||||
}
|
||||
// public double LastSendRespondedTo { get { return m_connection.m_lastSendRespondedTo; } }
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
internal void PacketSent(int numBytes, int numMessages)
|
||||
{
|
||||
NetException.Assert(numBytes > 0 && numMessages > 0);
|
||||
m_sentPackets++;
|
||||
m_sentBytes += numBytes;
|
||||
m_sentMessages += numMessages;
|
||||
@@ -102,6 +94,7 @@ namespace Lidgren.Network
|
||||
[Conditional("DEBUG")]
|
||||
internal void PacketReceived(int numBytes, int numMessages)
|
||||
{
|
||||
NetException.Assert(numBytes > 0 && numMessages > 0);
|
||||
m_receivedPackets++;
|
||||
m_receivedBytes += numBytes;
|
||||
m_receivedMessages += numMessages;
|
||||
@@ -116,24 +109,47 @@ namespace Lidgren.Network
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder bdr = new StringBuilder();
|
||||
bdr.AppendLine("Average roundtrip time: " + NetTime.ToReadable(m_connection.m_averageRoundtripTime));
|
||||
//bdr.AppendLine("Average roundtrip time: " + NetTime.ToReadable(m_connection.m_averageRoundtripTime));
|
||||
bdr.AppendLine("Sent " + m_sentBytes + " bytes in " + m_sentMessages + " messages in " + m_sentPackets + " packets");
|
||||
bdr.AppendLine("Received " + m_receivedBytes + " bytes in " + m_receivedMessages + " messages in " + m_receivedPackets + " packets");
|
||||
|
||||
if (m_resentMessages > 0)
|
||||
bdr.AppendLine("Resent messages: " + m_resentMessages);
|
||||
|
||||
int numUnsent = m_connection.m_unsentMessages.Count;
|
||||
if (numUnsent > 0)
|
||||
bdr.AppendLine("Unsent messages: " + numUnsent);
|
||||
int numStored = m_connection.GetStoredMessagesCount();
|
||||
if (numStored > 0)
|
||||
bdr.AppendLine("Stored messages: " + numStored);
|
||||
int numWithheld = m_connection.GetWithheldMessagesCount();
|
||||
if (numWithheld > 0)
|
||||
bdr.AppendLine("Withheld messages: " + numWithheld);
|
||||
int numUnsent = 0;
|
||||
int numStored = 0;
|
||||
foreach (NetSenderChannelBase sendChan in m_connection.m_sendChannels)
|
||||
{
|
||||
if (sendChan == null)
|
||||
continue;
|
||||
numUnsent += sendChan.m_queuedSends.Count;
|
||||
|
||||
var relSendChan = sendChan as NetReliableSenderChannel;
|
||||
if (relSendChan != null)
|
||||
{
|
||||
for (int i = 0; i < relSendChan.m_storedMessages.Length; i++)
|
||||
if (relSendChan.m_storedMessages[i].Message != null)
|
||||
numStored++;
|
||||
}
|
||||
}
|
||||
|
||||
int numWithheld = 0;
|
||||
foreach (NetReceiverChannelBase recChan in m_connection.m_receiveChannels)
|
||||
{
|
||||
var relRecChan = recChan as NetReliableOrderedReceiver;
|
||||
if (relRecChan != null)
|
||||
{
|
||||
for (int i = 0; i < relRecChan.m_withheldMessages.Length; i++)
|
||||
if (relRecChan.m_withheldMessages[i] != null)
|
||||
numWithheld++;
|
||||
}
|
||||
}
|
||||
|
||||
bdr.AppendLine("Unsent messages: " + numUnsent);
|
||||
bdr.AppendLine("Stored messages: " + numStored);
|
||||
bdr.AppendLine("Withheld messages: " + numWithheld);
|
||||
|
||||
return bdr.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,13 +21,17 @@ using System;
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Status of a connection
|
||||
/// Status for a NetConnection instance
|
||||
/// </summary>
|
||||
public enum NetConnectionStatus
|
||||
{
|
||||
None,
|
||||
Connecting,
|
||||
Connected,
|
||||
|
||||
InitiatedConnect, // we sent Connect
|
||||
RespondedConnect, // we got Connect, sent ConnectResponse
|
||||
|
||||
Connected, // we received ConnectResponse (if initiator) or ConnectionEstablished (if passive)
|
||||
|
||||
Disconnecting,
|
||||
Disconnected
|
||||
}
|
||||
|
||||
@@ -20,27 +20,35 @@ using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// All the constants used when compiling the library
|
||||
/// </summary>
|
||||
public static class NetConstants
|
||||
{
|
||||
public const int NumTotalChannels = 99;
|
||||
|
||||
public const int NetChannelsPerDeliveryMethod = 32;
|
||||
|
||||
public const int NumSequenceNumbers = ushort.MaxValue + 1; // 0 is a valid sequence number
|
||||
public const int NumSequenceNumbers = 1024;
|
||||
|
||||
public const int HeaderByteSize = 5;
|
||||
|
||||
public const int UnreliableWindowSize = 128;
|
||||
public const int ReliableOrderedWindowSize = 64;
|
||||
public const int ReliableSequencedWindowSize = 64;
|
||||
|
||||
public const int MaxFragmentationGroups = ushort.MaxValue - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Number of channels which needs a sequence number to work
|
||||
/// </summary>
|
||||
internal const int NumSequencedChannels = ((int)NetMessageType.UserReliableOrdered + NetConstants.NetChannelsPerDeliveryMethod) - (int)NetMessageType.UserSequenced;
|
||||
internal const int NumSequencedChannels = ((int)NetMessageType.UserReliableOrdered1 + NetConstants.NetChannelsPerDeliveryMethod) - (int)NetMessageType.UserSequenced1;
|
||||
|
||||
/// <summary>
|
||||
/// Number of reliable channels
|
||||
/// </summary>
|
||||
internal const int NumReliableChannels = ((int)NetMessageType.UserReliableOrdered + NetConstants.NetChannelsPerDeliveryMethod) - (int)NetMessageType.UserReliableUnordered;
|
||||
|
||||
/// <summary>
|
||||
/// Number of bytes added when message is really a fragment
|
||||
/// </summary>
|
||||
internal const int FragmentHeaderSize = 6;
|
||||
|
||||
internal const int NumReliableChannels = ((int)NetMessageType.UserReliableOrdered1 + NetConstants.NetChannelsPerDeliveryMethod) - (int)NetMessageType.UserReliableUnordered;
|
||||
|
||||
internal const string ConnResetMessage = "Connection was reset by remote host";
|
||||
}
|
||||
}
|
||||
|
||||
23
Lidgren.Network/NetDeliveryMethod.cs
Normal file
23
Lidgren.Network/NetDeliveryMethod.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// How the library deals with resends and handling of late messages
|
||||
/// </summary>
|
||||
public enum NetDeliveryMethod : byte
|
||||
{
|
||||
//
|
||||
// Actually a publicly visible subset of NetMessageType
|
||||
//
|
||||
Unknown = 0,
|
||||
Unreliable = 1,
|
||||
UnreliableSequenced = 2,
|
||||
ReliableUnordered = 34,
|
||||
ReliableSequenced = 35,
|
||||
ReliableOrdered = 67,
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ namespace Lidgren.Network
|
||||
/// String to hash for key
|
||||
/// </summary>
|
||||
public NetXtea(string key)
|
||||
: this(NetSha.Hash(Encoding.ASCII.GetBytes(key)), 32)
|
||||
: this(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(key)), 32)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -138,195 +138,4 @@ namespace Lidgren.Network
|
||||
destination[destinationOffset++] = (byte)value;
|
||||
}
|
||||
}
|
||||
|
||||
public static class NetSha
|
||||
{
|
||||
// TODO: switch to SHA256
|
||||
private static SHA1 m_sha;
|
||||
private static object s_lock = new object();
|
||||
|
||||
public static byte[] Hash(byte[] data)
|
||||
{
|
||||
lock (s_lock)
|
||||
{
|
||||
if (m_sha == null)
|
||||
m_sha = SHA1Managed.Create();
|
||||
return m_sha.ComputeHash(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class NetSRP
|
||||
{
|
||||
public static readonly BigInteger N = new BigInteger(NetUtility.ToByteArray("0115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3"));
|
||||
public static readonly BigInteger g = new BigInteger((uint)2);
|
||||
public static readonly BigInteger k = ComputeMultiplier();
|
||||
|
||||
/// <summary>
|
||||
/// Compute multiplier (k)
|
||||
/// </summary>
|
||||
private static BigInteger ComputeMultiplier()
|
||||
{
|
||||
string one = NetUtility.ToHexString(N.GetBytes());
|
||||
string two = NetUtility.ToHexString(g.GetBytes());
|
||||
byte[] cc = NetUtility.ToByteArray(one + two.PadLeft(one.Length, '0'));
|
||||
BigInteger retval = BigInteger.Modulus(new BigInteger(NetSha.Hash(cc)), N);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a verifier that the server can use to authenticate users later on (v)
|
||||
/// </summary>
|
||||
public static void ComputePasswordVerifier(string username, string password, byte[] salt, out byte[] serverVerifier, out byte[] clientVerifier)
|
||||
{
|
||||
byte[] tmp = Encoding.ASCII.GetBytes(username + ":" + password);
|
||||
byte[] innerHash = NetSha.Hash(tmp);
|
||||
|
||||
byte[] total = new byte[innerHash.Length + salt.Length];
|
||||
Buffer.BlockCopy(salt, 0, total, 0, salt.Length);
|
||||
Buffer.BlockCopy(innerHash, 0, total, salt.Length, innerHash.Length);
|
||||
|
||||
clientVerifier = NetSha.Hash(total);
|
||||
|
||||
// Verifier (v) = g^x (mod N)
|
||||
BigInteger xx = new BigInteger(clientVerifier);
|
||||
serverVerifier = g.ModPow(xx, N).GetBytes();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get 256 random bits
|
||||
/// </summary>
|
||||
public static byte[] CreateRandomKey()
|
||||
{
|
||||
byte[] retval = new byte[32];
|
||||
NetRandom.Instance.NextBytes(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets 80 random bits
|
||||
/// </summary>
|
||||
public static byte[] CreateRandomSalt()
|
||||
{
|
||||
byte[] retval = new byte[10];
|
||||
NetRandom.Instance.NextBytes(retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute client challenge (A)
|
||||
/// </summary>
|
||||
public static byte[] ComputeClientPublicKey(byte[] clientPrivateKey) // a
|
||||
{
|
||||
BigInteger a = new BigInteger(clientPrivateKey);
|
||||
|
||||
BigInteger retval = g.ModPow(a, N);
|
||||
|
||||
string gs = NetUtility.ToHexString(g.GetBytes());
|
||||
|
||||
|
||||
Console.WriteLine("a: " + NetUtility.ToHexString(a.GetBytes()));
|
||||
Console.WriteLine("A: " + NetUtility.ToHexString(retval.GetBytes()));
|
||||
|
||||
return retval.GetBytes();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute server challenge (B)
|
||||
/// </summary>
|
||||
public static byte[] ComputeServerPublicKey(byte[] serverPrivateKey, byte[] verifier) // b
|
||||
{
|
||||
BigInteger salt = new BigInteger(serverPrivateKey);
|
||||
|
||||
var bb = g.ModPow(salt, N);
|
||||
var B = BigInteger.Modulus((bb + (new BigInteger(verifier) * k)), N);
|
||||
|
||||
return B.GetBytes();
|
||||
}
|
||||
|
||||
public static byte[] ComputeU(byte[] clientPublicKey, byte[] serverPublicKey) // u
|
||||
{
|
||||
byte[] A = clientPublicKey;
|
||||
byte[] B = serverPublicKey;
|
||||
|
||||
string one = NetUtility.ToHexString(A);
|
||||
string two = NetUtility.ToHexString(B);
|
||||
string compound = one.PadLeft(66, '0') + two.PadLeft(66, '0');
|
||||
|
||||
byte[] cc = NetUtility.ToByteArray(compound);
|
||||
|
||||
return NetSha.Hash(cc);
|
||||
}
|
||||
|
||||
public static byte[] ComputeServerSessionKey(byte[] clientPublicKey, byte[] verifier, byte[] u, byte[] serverPrivateKey) // Ss
|
||||
{
|
||||
// S = (Av^u) ^ b (mod N)
|
||||
// return vv.modPow(uu, N).multiply(A).mod(N).modPow(bb, N);
|
||||
|
||||
BigInteger verBi = new BigInteger(verifier);
|
||||
BigInteger uBi = new BigInteger(u);
|
||||
BigInteger ABi = new BigInteger(clientPublicKey); // A
|
||||
BigInteger bBi = new BigInteger(serverPrivateKey); // b
|
||||
|
||||
Console.WriteLine("Ss input v: " + NetUtility.ToHexString(verifier));
|
||||
Console.WriteLine("Ss input u: " + NetUtility.ToHexString(u));
|
||||
Console.WriteLine("Ss input A: " + NetUtility.ToHexString(clientPublicKey));
|
||||
Console.WriteLine("Ss input A: " + ABi.ToString(16));
|
||||
Console.WriteLine("Ss input b: " + NetUtility.ToHexString(serverPrivateKey));
|
||||
|
||||
BigInteger retval = verBi.ModPow(uBi, N).Multiply(ABi).Modulus(N).ModPow(bBi, N).Modulus(N);
|
||||
Console.WriteLine("Ss (trad): " + NetUtility.ToHexString(retval.GetBytes()));
|
||||
BigInteger f1 = verBi.ModPow(uBi, N);
|
||||
Console.WriteLine("f1 (trad): " + NetUtility.ToHexString(f1.GetBytes()));
|
||||
|
||||
//return retval.GetBytes();
|
||||
|
||||
|
||||
// own
|
||||
// BigInteger tmp1 = verBi.ModPow(uBi, N).ModPow(bBi, N).Modulus(N);
|
||||
BigInteger tmp1 = (ABi * verBi.ModPow(uBi, N)).ModPow(bBi, N);
|
||||
Console.WriteLine("Ss (own): " + NetUtility.ToHexString(tmp1.GetBytes()));
|
||||
|
||||
|
||||
|
||||
// bc
|
||||
BigIntegerBC verBi2 = new BigIntegerBC(verifier);
|
||||
BigIntegerBC ABi2 = new BigIntegerBC(clientPublicKey); // A
|
||||
BigIntegerBC uBi2 = new BigIntegerBC(u);
|
||||
BigIntegerBC bBi2 = new BigIntegerBC(serverPrivateKey);
|
||||
BigIntegerBC N2 = new BigIntegerBC(N.GetBytes());
|
||||
|
||||
BigIntegerBC retval2 = verBi2.ModPow(uBi2, N2).Multiply(ABi2).Modulus(N2).ModPow(bBi2, N2).Modulus(N2);
|
||||
Console.WriteLine("Ss (bc): " + NetUtility.ToHexString(retval2.ToByteArray()));
|
||||
BigIntegerBC f12 = verBi2.ModPow(uBi2, N2);
|
||||
Console.WriteLine("f1 (bc): " + NetUtility.ToHexString(f12.ToByteArray()));
|
||||
|
||||
|
||||
// own bc
|
||||
BigIntegerBC tmp2 = verBi2.ModPow(uBi2, N2).ModPow(bBi2, N2).Modulus(N2);
|
||||
Console.WriteLine("Ss (ownBC): " + NetUtility.ToHexString(tmp2.ToByteArray()));
|
||||
|
||||
|
||||
|
||||
return retval.GetBytes();
|
||||
|
||||
//return NetSha.Hash(retval.GetBytes());
|
||||
}
|
||||
|
||||
public static byte[] ComputeClientSessionKey(byte[] serverPublicKey, byte[] x, byte[] u, byte[] clientPrivateKey) // Sc
|
||||
{
|
||||
BigInteger xBi = new BigInteger(x);
|
||||
BigInteger BBi = new BigInteger(serverPublicKey); // B
|
||||
BigInteger uBi = new BigInteger(u);
|
||||
BigInteger aBi = new BigInteger(clientPrivateKey); // a
|
||||
|
||||
BigInteger retval = (BBi + (N - ((k * g.ModPow(xBi, N)) % N))).ModPow(aBi + uBi * xBi, N);
|
||||
|
||||
return retval.GetBytes();
|
||||
|
||||
//return NetSha.Hash(retval.GetBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
@@ -23,6 +22,9 @@ using System.Runtime.Serialization;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown in the Lidgren Network Library
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class NetException : Exception
|
||||
{
|
||||
|
||||
174
Lidgren.Network/NetFragmentationHelper.cs
Normal file
174
Lidgren.Network/NetFragmentationHelper.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal static class NetFragmentationHelper
|
||||
{
|
||||
internal static int WriteHeader(
|
||||
byte[] destination,
|
||||
int ptr,
|
||||
int group,
|
||||
int totalBits,
|
||||
int chunkByteSize,
|
||||
int chunkNumber)
|
||||
{
|
||||
uint num1 = (uint)group;
|
||||
while (num1 >= 0x80)
|
||||
{
|
||||
destination[ptr++] = (byte)(num1 | 0x80);
|
||||
num1 = num1 >> 7;
|
||||
}
|
||||
destination[ptr++] = (byte)num1;
|
||||
|
||||
// write variable length fragment total bits
|
||||
uint num2 = (uint)totalBits;
|
||||
while (num2 >= 0x80)
|
||||
{
|
||||
destination[ptr++] = (byte)(num2 | 0x80);
|
||||
num2 = num2 >> 7;
|
||||
}
|
||||
destination[ptr++] = (byte)num2;
|
||||
|
||||
// write variable length fragment chunk size
|
||||
uint num3 = (uint)chunkByteSize;
|
||||
while (num3 >= 0x80)
|
||||
{
|
||||
destination[ptr++] = (byte)(num3 | 0x80);
|
||||
num3 = num3 >> 7;
|
||||
}
|
||||
destination[ptr++] = (byte)num3;
|
||||
|
||||
// write variable length fragment chunk number
|
||||
uint num4 = (uint)chunkNumber;
|
||||
while (num4 >= 0x80)
|
||||
{
|
||||
destination[ptr++] = (byte)(num4 | 0x80);
|
||||
num4 = num4 >> 7;
|
||||
}
|
||||
destination[ptr++] = (byte)num4;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
internal static int ReadHeader(byte[] buffer, int ptr, out int group, out int totalBits, out int chunkByteSize, out int chunkNumber)
|
||||
{
|
||||
int num1 = 0;
|
||||
int num2 = 0;
|
||||
while (true)
|
||||
{
|
||||
byte num3 = buffer[ptr++];
|
||||
num1 |= (num3 & 0x7f) << (num2 & 0x1f);
|
||||
num2 += 7;
|
||||
if ((num3 & 0x80) == 0)
|
||||
{
|
||||
group = num1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
num1 = 0;
|
||||
num2 = 0;
|
||||
while (true)
|
||||
{
|
||||
byte num3 = buffer[ptr++];
|
||||
num1 |= (num3 & 0x7f) << (num2 & 0x1f);
|
||||
num2 += 7;
|
||||
if ((num3 & 0x80) == 0)
|
||||
{
|
||||
totalBits = num1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
num1 = 0;
|
||||
num2 = 0;
|
||||
while (true)
|
||||
{
|
||||
byte num3 = buffer[ptr++];
|
||||
num1 |= (num3 & 0x7f) << (num2 & 0x1f);
|
||||
num2 += 7;
|
||||
if ((num3 & 0x80) == 0)
|
||||
{
|
||||
chunkByteSize = num1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
num1 = 0;
|
||||
num2 = 0;
|
||||
while (true)
|
||||
{
|
||||
byte num3 = buffer[ptr++];
|
||||
num1 |= (num3 & 0x7f) << (num2 & 0x1f);
|
||||
num2 += 7;
|
||||
if ((num3 & 0x80) == 0)
|
||||
{
|
||||
chunkNumber = num1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
internal static int GetFragmentationHeaderSize(int groupId, int totalBytes, int chunkByteSize, int numChunks)
|
||||
{
|
||||
int len = 4;
|
||||
|
||||
// write variable length fragment group id
|
||||
uint num1 = (uint)groupId;
|
||||
while (num1 >= 0x80)
|
||||
{
|
||||
len++;
|
||||
num1 = num1 >> 7;
|
||||
}
|
||||
|
||||
// write variable length fragment total bits
|
||||
uint num2 = (uint)(totalBytes * 8);
|
||||
while (num2 >= 0x80)
|
||||
{
|
||||
len++;
|
||||
num2 = num2 >> 7;
|
||||
}
|
||||
|
||||
// write variable length fragment chunk byte size
|
||||
uint num3 = (uint)chunkByteSize;
|
||||
while (num3 >= 0x80)
|
||||
{
|
||||
len++;
|
||||
num3 = num3 >> 7;
|
||||
}
|
||||
|
||||
// write variable length fragment chunk number
|
||||
uint num4 = (uint)numChunks;
|
||||
while (num4 >= 0x80)
|
||||
{
|
||||
len++;
|
||||
num4 = num4 >> 7;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
internal static int GetBestChunkSize(int group, int totalBytes, int mtu)
|
||||
{
|
||||
int tryNumChunks = (totalBytes / (mtu - 8)) + 1;
|
||||
int tryChunkSize = (totalBytes / tryNumChunks) + 1; // +1 since we immediately decrement it in the loop
|
||||
|
||||
int headerSize = 0;
|
||||
do
|
||||
{
|
||||
tryChunkSize--; // keep reducing chunk size until it fits within MTU including header
|
||||
|
||||
int numChunks = totalBytes / tryChunkSize;
|
||||
if (numChunks * tryChunkSize < totalBytes)
|
||||
numChunks++;
|
||||
|
||||
headerSize = GetFragmentationHeaderSize(group, totalBytes, tryChunkSize, numChunks);
|
||||
|
||||
} while (tryChunkSize + headerSize + 5 + 1 >= mtu);
|
||||
|
||||
return tryChunkSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -235,7 +235,7 @@ namespace Lidgren.Network
|
||||
byte[] bytes = PeekBytes(8);
|
||||
return BitConverter.ToDouble(bytes, 0); // endianness is handled inside BitConverter.ToSingle
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reads a string
|
||||
/// </summary>
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Lidgren.Network
|
||||
Type tp = target.GetType();
|
||||
|
||||
FieldInfo[] fields = tp.GetFields(flags);
|
||||
SortMembersList(fields);
|
||||
NetUtility.SortMembersList(fields);
|
||||
|
||||
foreach (FieldInfo fi in fields)
|
||||
{
|
||||
@@ -81,7 +81,7 @@ namespace Lidgren.Network
|
||||
Type tp = target.GetType();
|
||||
|
||||
PropertyInfo[] fields = tp.GetProperties(flags);
|
||||
SortMembersList(fields);
|
||||
NetUtility.SortMembersList(fields);
|
||||
foreach (PropertyInfo fi in fields)
|
||||
{
|
||||
object value;
|
||||
@@ -99,44 +99,5 @@ namespace Lidgren.Network
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// shell sort
|
||||
internal static void SortMembersList(MemberInfo[] list)
|
||||
{
|
||||
int h;
|
||||
int j;
|
||||
MemberInfo tmp;
|
||||
|
||||
h = 1;
|
||||
while (h * 3 + 1 <= list.Length)
|
||||
h = 3 * h + 1;
|
||||
|
||||
while (h > 0)
|
||||
{
|
||||
for (int i = h - 1; i < list.Length; i++)
|
||||
{
|
||||
tmp = list[i];
|
||||
j = i;
|
||||
while (true)
|
||||
{
|
||||
if (j >= h)
|
||||
{
|
||||
if (string.Compare(list[j - h].Name, tmp.Name, StringComparison.InvariantCulture) > 0)
|
||||
{
|
||||
list[j] = list[j - h];
|
||||
j -= h;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
list[j] = tmp;
|
||||
}
|
||||
h /= 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,12 +30,12 @@ namespace Lidgren.Network
|
||||
|
||||
private static readonly Dictionary<Type, MethodInfo> s_readMethods;
|
||||
|
||||
private int m_readPosition;
|
||||
internal int m_readPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the read position in the buffer, in bits (not bytes)
|
||||
/// </summary>
|
||||
public override long Position // override of Stream property
|
||||
public long Position
|
||||
{
|
||||
get { return (long)m_readPosition; }
|
||||
set { m_readPosition = (int)value; }
|
||||
@@ -62,7 +62,7 @@ namespace Lidgren.Network
|
||||
string n = mi.Name.Substring(4);
|
||||
foreach (Type it in integralTypes)
|
||||
{
|
||||
if (it.Name == n && mi.ReturnType.Name == it.Name)
|
||||
if (it.Name == n)
|
||||
s_readMethods[it] = mi;
|
||||
}
|
||||
}
|
||||
@@ -83,7 +83,7 @@ namespace Lidgren.Network
|
||||
//
|
||||
// 8 bit
|
||||
//
|
||||
public new byte ReadByte()
|
||||
public byte ReadByte()
|
||||
{
|
||||
NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError);
|
||||
byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition);
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetIncomingMessage : Stream
|
||||
{
|
||||
public override bool CanRead { get { return true; } }
|
||||
public override bool CanSeek { get { return true; } }
|
||||
public override bool CanWrite { get { return false; } }
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
// no op
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length in bytes
|
||||
/// </summary>
|
||||
public override long Length
|
||||
{
|
||||
get { return LengthBytes; }
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
// limit amount to remaining
|
||||
int remainingBytes = NetUtility.BytesToHoldBits(m_bitLength - m_readPosition);
|
||||
if (count > remainingBytes)
|
||||
count = remainingBytes;
|
||||
if (count < 1)
|
||||
return 0;
|
||||
|
||||
ReadBytes(buffer, offset, count);
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the position in the stream, in bytes
|
||||
/// </summary>
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
Position = (offset * 8);
|
||||
break;
|
||||
case SeekOrigin.Current:
|
||||
Position = Position + (offset * 8);
|
||||
break;
|
||||
case SeekOrigin.End:
|
||||
Position = (LengthBytes - offset) * 8;
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException("Bad SeekOrigin");
|
||||
}
|
||||
return Position;
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NetException("It's not possible to set the length of the NetIncomingMessage");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,12 +88,7 @@ namespace Lidgren.Network
|
||||
m_bitLength += bits;
|
||||
}
|
||||
|
||||
public override void Write(byte[] source, int offsetInBytes, int numberOfBytes)
|
||||
{
|
||||
throw new NetException("NetIncomingMessage does not support writing!");
|
||||
}
|
||||
|
||||
internal void WriteBytes(byte[] source, int offsetInBytes, int numberOfBytes)
|
||||
internal void Write(byte[] source, int offsetInBytes, int numberOfBytes)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException("source");
|
||||
|
||||
@@ -17,77 +17,40 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR TH
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal enum NetIncomingMessageReleaseStatus
|
||||
{
|
||||
NotReleased = 0,
|
||||
ReleasedToApplication,
|
||||
RecycledByApplication
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{m_readPosition} of {m_bitLength} bits ({LengthBytes} bytes) read")]
|
||||
/// <summary>
|
||||
/// Incoming message either sent from a remote peer or generated within the library
|
||||
/// </summary>
|
||||
[DebuggerDisplay("Type={MessageType} LengthBits={LengthBits}")]
|
||||
public partial class NetIncomingMessage
|
||||
{
|
||||
internal byte[] m_data;
|
||||
internal int m_bitLength;
|
||||
internal NetMessageType m_messageType; // NetDeliveryMethod and sequence channel can be derived from this
|
||||
internal ushort m_sequenceNumber;
|
||||
internal NetIncomingMessageReleaseStatus m_status;
|
||||
|
||||
internal NetIncomingMessageType m_incomingType;
|
||||
internal NetIncomingMessageType m_incomingMessageType;
|
||||
internal IPEndPoint m_senderEndpoint;
|
||||
internal NetConnection m_senderConnection;
|
||||
|
||||
internal NetFragmentationInfo m_fragmentationInfo;
|
||||
internal int m_sequenceNumber;
|
||||
internal NetMessageType m_receivedMessageType;
|
||||
internal bool m_isFragment;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the data in number of bytes
|
||||
/// Gets the type of this incoming message
|
||||
/// </summary>
|
||||
public int LengthBytes
|
||||
{
|
||||
get { return ((m_bitLength + 7) >> 3); }
|
||||
}
|
||||
public NetIncomingMessageType MessageType { get { return m_incomingMessageType; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the data in number of bits
|
||||
/// Gets the delivery method this message was sent with (if user data)
|
||||
/// </summary>
|
||||
public int LengthBits
|
||||
{
|
||||
get { return m_bitLength; }
|
||||
}
|
||||
public NetDeliveryMethod DeliveryMethod { get { return m_receivedMessageType.GetDeliveryMethod(); } }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the internal data buffer, don't modify
|
||||
/// Gets the sequence channel this message was sent with (if user data)
|
||||
/// </summary>
|
||||
public byte[] PeekDataBuffer()
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NetDeliveryMethod used by this message
|
||||
/// </summary>
|
||||
public NetDeliveryMethod DeliveryMethod
|
||||
{
|
||||
get { return NetPeer.GetDeliveryMethod(m_messageType); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets which sequence channel this message was sent in
|
||||
/// </summary>
|
||||
public int SequenceChannel
|
||||
{
|
||||
get { return (int)m_messageType - (int)NetPeer.GetDeliveryMethod(m_messageType); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of data contained in this message
|
||||
/// </summary>
|
||||
public NetIncomingMessageType MessageType { get { return m_incomingType; } }
|
||||
public int SequenceChannel { get { return (int)m_receivedMessageType - (int)m_receivedMessageType.GetDeliveryMethod(); } }
|
||||
|
||||
/// <summary>
|
||||
/// IPEndPoint of sender, if any
|
||||
@@ -99,22 +62,40 @@ namespace Lidgren.Network
|
||||
/// </summary>
|
||||
public NetConnection SenderConnection { get { return m_senderConnection; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the message payload in bytes
|
||||
/// </summary>
|
||||
public int LengthBytes
|
||||
{
|
||||
get { return ((m_bitLength + 7) >> 3); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the message payload in bits
|
||||
/// </summary>
|
||||
public int LengthBits
|
||||
{
|
||||
get { return m_bitLength; }
|
||||
internal set { m_bitLength = value; }
|
||||
}
|
||||
|
||||
internal NetIncomingMessage()
|
||||
{
|
||||
}
|
||||
|
||||
internal NetIncomingMessage(byte[] data, int dataLength)
|
||||
internal NetIncomingMessage(NetIncomingMessageType tp)
|
||||
{
|
||||
m_data = data;
|
||||
m_bitLength = dataLength * 8;
|
||||
m_incomingMessageType = tp;
|
||||
}
|
||||
|
||||
internal void Reset()
|
||||
{
|
||||
m_bitLength = 0;
|
||||
m_incomingMessageType = NetIncomingMessageType.Error;
|
||||
m_readPosition = 0;
|
||||
m_status = NetIncomingMessageReleaseStatus.NotReleased;
|
||||
m_fragmentationInfo = null;
|
||||
m_receivedMessageType = NetMessageType.LibraryError;
|
||||
m_senderConnection = null;
|
||||
m_bitLength = 0;
|
||||
m_isFragment = false;
|
||||
}
|
||||
|
||||
public void Decrypt(NetXtea tea)
|
||||
@@ -130,34 +111,9 @@ namespace Lidgren.Network
|
||||
m_data = result;
|
||||
}
|
||||
|
||||
public NetIncomingMessage Clone()
|
||||
{
|
||||
NetIncomingMessage retval = new NetIncomingMessage();
|
||||
|
||||
// copy content
|
||||
retval.m_data = new byte[LengthBytes];
|
||||
Buffer.BlockCopy(m_data, 0, retval.m_data, 0, LengthBytes);
|
||||
|
||||
retval.m_bitLength = m_bitLength;
|
||||
retval.m_messageType = m_messageType;
|
||||
retval.m_sequenceNumber = m_sequenceNumber;
|
||||
retval.m_status = m_status;
|
||||
retval.m_incomingType = m_incomingType;
|
||||
retval.m_senderEndpoint = m_senderEndpoint;
|
||||
retval.m_senderConnection = m_senderConnection;
|
||||
retval.m_fragmentationInfo = m_fragmentationInfo;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Format("[NetIncomingMessage {0}, {1}|{2}, {3} bits]",
|
||||
m_incomingType,
|
||||
m_messageType,
|
||||
m_sequenceNumber,
|
||||
m_bitLength
|
||||
);
|
||||
return "[NetIncomingMessage #" + m_sequenceNumber + " " + this.LengthBytes + " bytes]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of incoming message
|
||||
/// The type of a NetIncomingMessage
|
||||
/// </summary>
|
||||
[SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")]
|
||||
public enum NetIncomingMessageType
|
||||
|
||||
@@ -20,148 +20,175 @@ using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
// publicly visible subset of NetMessageType
|
||||
|
||||
/// <summary>
|
||||
/// How the library deals with dropped and delayed messages
|
||||
/// </summary>
|
||||
public enum NetDeliveryMethod : byte
|
||||
{
|
||||
Unknown = 0,
|
||||
Unreliable = 2,
|
||||
UnreliableSequenced = 3,
|
||||
ReliableUnordered = 35,
|
||||
ReliableSequenced = 36,
|
||||
ReliableOrdered = 68,
|
||||
}
|
||||
|
||||
internal enum NetMessageLibraryType : byte
|
||||
{
|
||||
Error = 0,
|
||||
KeepAlive = 1, // used for piggybacking acks
|
||||
Ping = 2, // used for RTT calculation
|
||||
Pong = 3, // used for RTT calculation
|
||||
Connect = 4,
|
||||
ConnectResponse = 5,
|
||||
ConnectionEstablished = 6,
|
||||
Acknowledge = 7,
|
||||
Disconnect = 8,
|
||||
Discovery = 9,
|
||||
DiscoveryResponse = 10,
|
||||
NatPunchMessage = 11, // send between peers
|
||||
NatIntroduction = 12, // send to master server
|
||||
}
|
||||
|
||||
internal enum NetMessageType : byte
|
||||
{
|
||||
Error = 0,
|
||||
Unconnected = 0,
|
||||
|
||||
Library = 1, // NetMessageLibraryType byte follows
|
||||
UserUnreliable = 1,
|
||||
|
||||
UserUnreliable = 2,
|
||||
UserSequenced1 = 2,
|
||||
UserSequenced2 = 3,
|
||||
UserSequenced3 = 4,
|
||||
UserSequenced4 = 5,
|
||||
UserSequenced5 = 6,
|
||||
UserSequenced6 = 7,
|
||||
UserSequenced7 = 8,
|
||||
UserSequenced8 = 9,
|
||||
UserSequenced9 = 10,
|
||||
UserSequenced10 = 11,
|
||||
UserSequenced11 = 12,
|
||||
UserSequenced12 = 13,
|
||||
UserSequenced13 = 14,
|
||||
UserSequenced14 = 15,
|
||||
UserSequenced15 = 16,
|
||||
UserSequenced16 = 17,
|
||||
UserSequenced17 = 18,
|
||||
UserSequenced18 = 19,
|
||||
UserSequenced19 = 20,
|
||||
UserSequenced20 = 21,
|
||||
UserSequenced21 = 22,
|
||||
UserSequenced22 = 23,
|
||||
UserSequenced23 = 24,
|
||||
UserSequenced24 = 25,
|
||||
UserSequenced25 = 26,
|
||||
UserSequenced26 = 27,
|
||||
UserSequenced27 = 28,
|
||||
UserSequenced28 = 29,
|
||||
UserSequenced29 = 30,
|
||||
UserSequenced30 = 31,
|
||||
UserSequenced31 = 32,
|
||||
UserSequenced32 = 33,
|
||||
|
||||
// 3 to 34 = UserSequenced 0 to 31
|
||||
UserSequenced = 3,
|
||||
UserSequenced1 = 4,
|
||||
UserSequenced2 = 5,
|
||||
UserSequenced3 = 6,
|
||||
UserSequenced4 = 7,
|
||||
UserSequenced5 = 8,
|
||||
UserSequenced6 = 9,
|
||||
UserSequenced7 = 10,
|
||||
UserSequenced8 = 11,
|
||||
UserSequenced9 = 12,
|
||||
UserSequenced10 = 13,
|
||||
UserSequenced11 = 14,
|
||||
UserSequenced12 = 15,
|
||||
UserSequenced13 = 16,
|
||||
UserSequenced14 = 17,
|
||||
UserSequenced15 = 18,
|
||||
UserSequenced16 = 19,
|
||||
UserSequenced17 = 20,
|
||||
UserSequenced18 = 21,
|
||||
UserSequenced19 = 22,
|
||||
UserSequenced20 = 23,
|
||||
UserSequenced21 = 24,
|
||||
UserSequenced22 = 25,
|
||||
UserSequenced23 = 26,
|
||||
UserSequenced24 = 27,
|
||||
UserSequenced25 = 28,
|
||||
UserSequenced26 = 29,
|
||||
UserSequenced27 = 30,
|
||||
UserSequenced28 = 31,
|
||||
UserSequenced29 = 32,
|
||||
UserSequenced30 = 33,
|
||||
UserSequenced31 = 34,
|
||||
UserReliableUnordered = 34,
|
||||
|
||||
UserReliableUnordered = 35,
|
||||
UserReliableSequenced1 = 35,
|
||||
UserReliableSequenced2 = 36,
|
||||
UserReliableSequenced3 = 37,
|
||||
UserReliableSequenced4 = 38,
|
||||
UserReliableSequenced5 = 39,
|
||||
UserReliableSequenced6 = 40,
|
||||
UserReliableSequenced7 = 41,
|
||||
UserReliableSequenced8 = 42,
|
||||
UserReliableSequenced9 = 43,
|
||||
UserReliableSequenced10 = 44,
|
||||
UserReliableSequenced11 = 45,
|
||||
UserReliableSequenced12 = 46,
|
||||
UserReliableSequenced13 = 47,
|
||||
UserReliableSequenced14 = 48,
|
||||
UserReliableSequenced15 = 49,
|
||||
UserReliableSequenced16 = 50,
|
||||
UserReliableSequenced17 = 51,
|
||||
UserReliableSequenced18 = 52,
|
||||
UserReliableSequenced19 = 53,
|
||||
UserReliableSequenced20 = 54,
|
||||
UserReliableSequenced21 = 55,
|
||||
UserReliableSequenced22 = 56,
|
||||
UserReliableSequenced23 = 57,
|
||||
UserReliableSequenced24 = 58,
|
||||
UserReliableSequenced25 = 59,
|
||||
UserReliableSequenced26 = 60,
|
||||
UserReliableSequenced27 = 61,
|
||||
UserReliableSequenced28 = 62,
|
||||
UserReliableSequenced29 = 63,
|
||||
UserReliableSequenced30 = 64,
|
||||
UserReliableSequenced31 = 65,
|
||||
UserReliableSequenced32 = 66,
|
||||
|
||||
// 36 to 67 = UserReliableSequenced 0 to 31
|
||||
UserReliableSequenced = 36,
|
||||
UserReliableSequenced1 = 37,
|
||||
UserReliableSequenced2 = 38,
|
||||
UserReliableSequenced3 = 39,
|
||||
UserReliableSequenced4 = 40,
|
||||
UserReliableSequenced5 = 41,
|
||||
UserReliableSequenced6 = 42,
|
||||
UserReliableSequenced7 = 43,
|
||||
UserReliableSequenced8 = 44,
|
||||
UserReliableSequenced9 = 45,
|
||||
UserReliableSequenced10 = 46,
|
||||
UserReliableSequenced11 = 47,
|
||||
UserReliableSequenced12 = 48,
|
||||
UserReliableSequenced13 = 49,
|
||||
UserReliableSequenced14 = 50,
|
||||
UserReliableSequenced15 = 51,
|
||||
UserReliableSequenced16 = 52,
|
||||
UserReliableSequenced17 = 53,
|
||||
UserReliableSequenced18 = 54,
|
||||
UserReliableSequenced19 = 55,
|
||||
UserReliableSequenced20 = 56,
|
||||
UserReliableSequenced21 = 57,
|
||||
UserReliableSequenced22 = 58,
|
||||
UserReliableSequenced23 = 59,
|
||||
UserReliableSequenced24 = 60,
|
||||
UserReliableSequenced25 = 61,
|
||||
UserReliableSequenced26 = 62,
|
||||
UserReliableSequenced27 = 63,
|
||||
UserReliableSequenced28 = 64,
|
||||
UserReliableSequenced29 = 65,
|
||||
UserReliableSequenced30 = 66,
|
||||
UserReliableSequenced31 = 67,
|
||||
UserReliableOrdered1 = 67,
|
||||
UserReliableOrdered2 = 68,
|
||||
UserReliableOrdered3 = 69,
|
||||
UserReliableOrdered4 = 70,
|
||||
UserReliableOrdered5 = 71,
|
||||
UserReliableOrdered6 = 72,
|
||||
UserReliableOrdered7 = 73,
|
||||
UserReliableOrdered8 = 74,
|
||||
UserReliableOrdered9 = 75,
|
||||
UserReliableOrdered10 = 76,
|
||||
UserReliableOrdered11 = 77,
|
||||
UserReliableOrdered12 = 78,
|
||||
UserReliableOrdered13 = 79,
|
||||
UserReliableOrdered14 = 80,
|
||||
UserReliableOrdered15 = 81,
|
||||
UserReliableOrdered16 = 82,
|
||||
UserReliableOrdered17 = 83,
|
||||
UserReliableOrdered18 = 84,
|
||||
UserReliableOrdered19 = 85,
|
||||
UserReliableOrdered20 = 86,
|
||||
UserReliableOrdered21 = 87,
|
||||
UserReliableOrdered22 = 88,
|
||||
UserReliableOrdered23 = 89,
|
||||
UserReliableOrdered24 = 90,
|
||||
UserReliableOrdered25 = 91,
|
||||
UserReliableOrdered26 = 92,
|
||||
UserReliableOrdered27 = 93,
|
||||
UserReliableOrdered28 = 94,
|
||||
UserReliableOrdered29 = 95,
|
||||
UserReliableOrdered30 = 96,
|
||||
UserReliableOrdered31 = 97,
|
||||
UserReliableOrdered32 = 98,
|
||||
|
||||
// 68 to 99 = UserReliableOrdered 0 to 31
|
||||
UserReliableOrdered = 68,
|
||||
UserReliableOrdered1 = 69,
|
||||
UserReliableOrdered2 = 70,
|
||||
UserReliableOrdered3 = 71,
|
||||
UserReliableOrdered4 = 72,
|
||||
UserReliableOrdered5 = 73,
|
||||
UserReliableOrdered6 = 74,
|
||||
UserReliableOrdered7 = 75,
|
||||
UserReliableOrdered8 = 76,
|
||||
UserReliableOrdered9 = 77,
|
||||
UserReliableOrdered10 = 78,
|
||||
UserReliableOrdered11 = 79,
|
||||
UserReliableOrdered12 = 80,
|
||||
UserReliableOrdered13 = 81,
|
||||
UserReliableOrdered14 = 82,
|
||||
UserReliableOrdered15 = 83,
|
||||
UserReliableOrdered16 = 84,
|
||||
UserReliableOrdered17 = 85,
|
||||
UserReliableOrdered18 = 86,
|
||||
UserReliableOrdered19 = 87,
|
||||
UserReliableOrdered20 = 88,
|
||||
UserReliableOrdered21 = 89,
|
||||
UserReliableOrdered22 = 90,
|
||||
UserReliableOrdered23 = 91,
|
||||
UserReliableOrdered24 = 92,
|
||||
UserReliableOrdered25 = 93,
|
||||
UserReliableOrdered26 = 94,
|
||||
UserReliableOrdered27 = 95,
|
||||
UserReliableOrdered28 = 96,
|
||||
UserReliableOrdered29 = 97,
|
||||
UserReliableOrdered30 = 98,
|
||||
UserReliableOrdered31 = 99,
|
||||
Unused1 = 99,
|
||||
Unused2 = 100,
|
||||
Unused3 = 101,
|
||||
Unused4 = 102,
|
||||
Unused5 = 103,
|
||||
Unused6 = 104,
|
||||
Unused7 = 105,
|
||||
Unused8 = 106,
|
||||
Unused9 = 107,
|
||||
Unused10 = 108,
|
||||
Unused11 = 109,
|
||||
Unused12 = 110,
|
||||
Unused13 = 111,
|
||||
Unused14 = 112,
|
||||
Unused15 = 113,
|
||||
Unused16 = 114,
|
||||
Unused17 = 115,
|
||||
Unused18 = 116,
|
||||
Unused19 = 117,
|
||||
Unused20 = 118,
|
||||
Unused21 = 119,
|
||||
Unused22 = 120,
|
||||
Unused23 = 121,
|
||||
Unused24 = 122,
|
||||
Unused25 = 123,
|
||||
Unused26 = 124,
|
||||
Unused27 = 125,
|
||||
Unused28 = 126,
|
||||
Unused29 = 127,
|
||||
|
||||
LibraryError = 128,
|
||||
Ping = 129, // used for RTT calculation
|
||||
Pong = 130, // used for RTT calculation
|
||||
Connect = 131,
|
||||
ConnectResponse = 132,
|
||||
ConnectionEstablished = 133,
|
||||
Acknowledge = 134,
|
||||
Disconnect = 135,
|
||||
Discovery = 136,
|
||||
DiscoveryResponse = 137,
|
||||
NatPunchMessage = 138, // send between peers
|
||||
NatIntroduction = 139, // send to master server
|
||||
}
|
||||
|
||||
internal static class NetMessageTypeExtensions
|
||||
{
|
||||
internal static bool IsLibrary(this NetMessageType tp)
|
||||
{
|
||||
return tp >= NetMessageType.LibraryError;
|
||||
}
|
||||
|
||||
internal static NetDeliveryMethod GetDeliveryMethod(this NetMessageType mtp)
|
||||
{
|
||||
if (mtp >= NetMessageType.UserReliableOrdered1)
|
||||
return NetDeliveryMethod.ReliableOrdered;
|
||||
else if (mtp >= NetMessageType.UserReliableSequenced1)
|
||||
return NetDeliveryMethod.ReliableSequenced;
|
||||
else if (mtp >= NetMessageType.UserReliableUnordered)
|
||||
return NetDeliveryMethod.ReliableUnordered;
|
||||
else if (mtp >= NetMessageType.UserSequenced1)
|
||||
return NetDeliveryMethod.UnreliableSequenced;
|
||||
return NetDeliveryMethod.Unreliable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,23 +18,23 @@ namespace Lidgren.Network
|
||||
{
|
||||
// send message to client
|
||||
NetOutgoingMessage msg = CreateMessage(10 + token.Length + 1);
|
||||
msg.m_libType = NetMessageLibraryType.NatIntroduction;
|
||||
msg.m_messageType = NetMessageType.NatIntroduction;
|
||||
msg.Write(false);
|
||||
msg.WritePadBits();
|
||||
msg.Write(hostInternal);
|
||||
msg.Write(hostExternal);
|
||||
msg.Write(token);
|
||||
SendUnconnectedLibrary(msg, clientExternal);
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(clientExternal, msg));
|
||||
|
||||
// send message to host
|
||||
msg = CreateMessage(10 + token.Length + 1);
|
||||
msg.m_libType = NetMessageLibraryType.NatIntroduction;
|
||||
msg.m_messageType = NetMessageType.NatIntroduction;
|
||||
msg.Write(true);
|
||||
msg.WritePadBits();
|
||||
msg.Write(clientInternal);
|
||||
msg.Write(clientExternal);
|
||||
msg.Write(token);
|
||||
SendUnconnectedLibrary(msg, hostExternal);
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(hostExternal, msg));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -45,8 +45,8 @@ namespace Lidgren.Network
|
||||
VerifyNetworkThread();
|
||||
|
||||
// read intro
|
||||
NetIncomingMessage tmp = new NetIncomingMessage(m_receiveBuffer, 1000); // never mind length
|
||||
tmp.Position = (ptr * 8);
|
||||
NetIncomingMessage tmp = SetupReadHelperMessage(ptr, 1000); // never mind length
|
||||
|
||||
byte hostByte = tmp.ReadByte();
|
||||
IPEndPoint remoteInternal = tmp.ReadIPEndpoint();
|
||||
IPEndPoint remoteExternal = tmp.ReadIPEndpoint();
|
||||
@@ -62,17 +62,17 @@ namespace Lidgren.Network
|
||||
|
||||
// send internal punch
|
||||
punch = CreateMessage(1);
|
||||
punch.m_libType = NetMessageLibraryType.NatPunchMessage;
|
||||
punch.m_messageType = NetMessageType.NatPunchMessage;
|
||||
punch.Write(hostByte);
|
||||
punch.Write(token);
|
||||
SendUnconnectedLibrary(punch, remoteInternal);
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(remoteInternal, punch));
|
||||
|
||||
// send external punch
|
||||
punch = CreateMessage(1);
|
||||
punch.m_libType = NetMessageLibraryType.NatPunchMessage;
|
||||
punch.m_messageType = NetMessageType.NatPunchMessage;
|
||||
punch.Write(hostByte);
|
||||
punch.Write(token);
|
||||
SendUnconnectedLibrary(punch, remoteExternal);
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(remoteExternal, punch));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -80,8 +80,7 @@ namespace Lidgren.Network
|
||||
/// </summary>
|
||||
private void HandleNatPunch(int ptr, IPEndPoint senderEndpoint)
|
||||
{
|
||||
NetIncomingMessage tmp = new NetIncomingMessage(m_receiveBuffer, 1000); // never mind length
|
||||
tmp.Position = (ptr * 8);
|
||||
NetIncomingMessage tmp = SetupReadHelperMessage(ptr, 1000); // never mind length
|
||||
|
||||
byte fromHostByte = tmp.ReadByte();
|
||||
if (fromHostByte == 0)
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetOutgoingMessage : Stream
|
||||
{
|
||||
public override bool CanRead { get { return false; } }
|
||||
public override bool CanSeek { get { return false; } }
|
||||
public override bool CanWrite { get { return true; } }
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
// no op
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length of the stream, in bytes
|
||||
/// </summary>
|
||||
public override long Length
|
||||
{
|
||||
get { return (long)LengthBytes; }
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NetException("Position in bytes is not relevant since the bit count can vary");
|
||||
}
|
||||
set
|
||||
{
|
||||
throw new NetException("It's not possible to seek in this message");
|
||||
}
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NetException("It's not possible to read from this message");
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NetException("It's not possible to seek in this message");
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NetException("It's not possible to set the length of this message");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ namespace Lidgren.Network
|
||||
Type tp = ob.GetType();
|
||||
|
||||
FieldInfo[] fields = tp.GetFields(flags);
|
||||
NetIncomingMessage.SortMembersList(fields);
|
||||
NetUtility.SortMembersList(fields);
|
||||
|
||||
foreach (FieldInfo fi in fields)
|
||||
{
|
||||
@@ -74,7 +74,7 @@ namespace Lidgren.Network
|
||||
Type tp = ob.GetType();
|
||||
|
||||
PropertyInfo[] fields = tp.GetProperties(flags);
|
||||
NetIncomingMessage.SortMembersList(fields);
|
||||
NetUtility.SortMembersList(fields);
|
||||
|
||||
foreach (PropertyInfo fi in fields)
|
||||
{
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace Lidgren.Network
|
||||
m_bitLength += bits;
|
||||
}
|
||||
|
||||
public override void Write(byte[] source, int offsetInBytes, int numberOfBytes)
|
||||
public void Write(byte[] source, int offsetInBytes, int numberOfBytes)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException("source");
|
||||
|
||||
@@ -15,182 +15,103 @@ PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Outgoing message used to send data to remote peer(s)
|
||||
/// </summary>
|
||||
[DebuggerDisplay("LengthBits={LengthBits}")]
|
||||
public sealed partial class NetOutgoingMessage
|
||||
{
|
||||
// reference count before message can be recycled
|
||||
internal int m_numUnfinishedSendings;
|
||||
internal NetMessageType m_messageType;
|
||||
internal bool m_isSent;
|
||||
internal int m_recyclingCount;
|
||||
|
||||
internal bool m_wasSent; // true is SendMessage() public method has been called
|
||||
internal NetMessageLibraryType m_libType = NetMessageLibraryType.Error;
|
||||
|
||||
//internal int m_fragmentGroupId;
|
||||
//internal int m_fragmentNumber;
|
||||
//internal int m_fragmentTotalCount;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this message has been passed to SendMessage() already
|
||||
/// </summary>
|
||||
public bool IsSent { get { return m_wasSent; } }
|
||||
internal int m_fragmentGroup; // which group of fragments ths belongs to
|
||||
internal int m_fragmentGroupTotalBits; // total number of bits in this group
|
||||
internal int m_fragmentChunkByteSize; // size, in bytes, of every chunk but the last one
|
||||
internal int m_fragmentChunkNumber; // which number chunk this is, starting with 0
|
||||
|
||||
internal NetOutgoingMessage()
|
||||
{
|
||||
}
|
||||
|
||||
internal void Reset()
|
||||
public void Reset()
|
||||
{
|
||||
NetException.Assert(m_numUnfinishedSendings == 0, "Ouch! Resetting NetOutgoingMessage still in some queue!");
|
||||
NetException.Assert(m_wasSent == true, "Ouch! Resetting unsent message!");
|
||||
|
||||
m_messageType = NetMessageType.LibraryError;
|
||||
m_bitLength = 0;
|
||||
m_libType = NetMessageLibraryType.Error;
|
||||
m_wasSent = false;
|
||||
m_isSent = false;
|
||||
m_recyclingCount = 0;
|
||||
m_fragmentGroup = 0;
|
||||
}
|
||||
|
||||
internal static int EncodeAcksMessage(byte[] buffer, int ptr, NetConnection conn, int maxBytesPayload)
|
||||
internal int Encode(byte[] intoBuffer, int ptr, int sequenceNumber)
|
||||
{
|
||||
// TODO: if appropriate; make bit vector of adjacent acks
|
||||
// 8 bits - NetMessageType
|
||||
// 1 bit - Fragment?
|
||||
// 15 bits - Sequence number
|
||||
// 16 bits - Payload length in bits
|
||||
|
||||
intoBuffer[ptr++] = (byte)m_messageType;
|
||||
|
||||
buffer[ptr++] = (byte)NetMessageType.Library;
|
||||
buffer[ptr++] = (byte)NetMessageLibraryType.Acknowledge;
|
||||
byte low = (byte)((sequenceNumber << 1) | (m_fragmentGroup == 0 ? 0 : 1));
|
||||
intoBuffer[ptr++] = low;
|
||||
intoBuffer[ptr++] = (byte)(sequenceNumber >> 7);
|
||||
|
||||
Queue<int> acks = conn.m_acknowledgesToSend;
|
||||
|
||||
int maxAcks = maxBytesPayload / 3;
|
||||
int acksToEncode = (acks.Count < maxAcks ? acks.Count : maxAcks);
|
||||
|
||||
int payloadBitsLength = acksToEncode * 3 * 8;
|
||||
if (payloadBitsLength < 127)
|
||||
if (m_fragmentGroup == 0)
|
||||
{
|
||||
buffer[ptr++] = (byte)payloadBitsLength;
|
||||
intoBuffer[ptr++] = (byte)m_bitLength;
|
||||
intoBuffer[ptr++] = (byte)(m_bitLength >> 8);
|
||||
|
||||
int byteLen = NetUtility.BytesToHoldBits(m_bitLength);
|
||||
if (byteLen > 0)
|
||||
{
|
||||
Buffer.BlockCopy(m_data, 0, intoBuffer, ptr, byteLen);
|
||||
ptr += byteLen;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer[ptr++] = (byte)((payloadBitsLength & 127) | 128);
|
||||
buffer[ptr++] = (byte)(payloadBitsLength >> 7);
|
||||
}
|
||||
|
||||
for (int i = 0; i < acksToEncode; i++)
|
||||
{
|
||||
int ack = acks.Dequeue();
|
||||
buffer[ptr++] = (byte)ack; // message type
|
||||
buffer[ptr++] = (byte)(ack >> 8); // seqnr low
|
||||
buffer[ptr++] = (byte)(ack >> 16); // seqnr high
|
||||
int wasPtr = ptr;
|
||||
intoBuffer[ptr++] = (byte)m_bitLength;
|
||||
intoBuffer[ptr++] = (byte)(m_bitLength >> 8);
|
||||
|
||||
//
|
||||
// write fragmentation header
|
||||
//
|
||||
ptr = NetFragmentationHelper.WriteHeader(intoBuffer, ptr, m_fragmentGroup, m_fragmentGroupTotalBits, m_fragmentChunkByteSize, m_fragmentChunkNumber);
|
||||
int hdrLen = ptr - wasPtr - 2;
|
||||
|
||||
// update length
|
||||
int realBitLength = m_bitLength + (hdrLen * 8);
|
||||
intoBuffer[wasPtr] = (byte)realBitLength;
|
||||
intoBuffer[wasPtr + 1] = (byte)(realBitLength >> 8);
|
||||
|
||||
int byteLen = NetUtility.BytesToHoldBits(m_bitLength);
|
||||
if (byteLen > 0)
|
||||
{
|
||||
Buffer.BlockCopy(m_data, (int)(m_fragmentChunkNumber * m_fragmentChunkByteSize), intoBuffer, ptr, byteLen);
|
||||
ptr += byteLen;
|
||||
}
|
||||
}
|
||||
|
||||
NetException.Assert(ptr > 0);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
internal int EncodeUnfragmented(byte[] buffer, int ptr, NetMessageType tp, ushort sequenceNumber)
|
||||
internal int GetEncodedSize()
|
||||
{
|
||||
// message type
|
||||
buffer[ptr++] = (byte)tp; // | (m_fragmentGroupId == -1 ? 0 : 128));
|
||||
int retval = 5; // regular headers
|
||||
if (m_fragmentGroup != 0)
|
||||
retval += NetFragmentationHelper.GetFragmentationHeaderSize(m_fragmentGroup, m_fragmentGroupTotalBits / 8, m_fragmentChunkByteSize, m_fragmentChunkNumber);
|
||||
retval += this.LengthBytes;
|
||||
|
||||
if (tp == NetMessageType.Library)
|
||||
buffer[ptr++] = (byte)m_libType;
|
||||
|
||||
// channel sequence number
|
||||
if (tp >= NetMessageType.UserSequenced)
|
||||
{
|
||||
ushort seqNr = (ushort)sequenceNumber;
|
||||
buffer[ptr++] = (byte)seqNr;
|
||||
buffer[ptr++] = (byte)(seqNr >> 8);
|
||||
}
|
||||
|
||||
// payload length
|
||||
int payloadBitsLength = LengthBits;
|
||||
int payloadBytesLength = NetUtility.BytesToHoldBits(payloadBitsLength);
|
||||
if (payloadBitsLength < 127)
|
||||
{
|
||||
buffer[ptr++] = (byte)payloadBitsLength;
|
||||
}
|
||||
else if (payloadBitsLength < 32768)
|
||||
{
|
||||
buffer[ptr++] = (byte)((payloadBitsLength & 127) | 128);
|
||||
buffer[ptr++] = (byte)(payloadBitsLength >> 7);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NetException("Packet content too large; 4095 bytes maximum");
|
||||
}
|
||||
|
||||
// payload
|
||||
if (payloadBitsLength > 0)
|
||||
{
|
||||
// zero out last byte
|
||||
buffer[ptr + payloadBytesLength] = 0;
|
||||
|
||||
Buffer.BlockCopy(m_data, 0, buffer, ptr, payloadBytesLength);
|
||||
ptr += payloadBytesLength;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
internal int EncodeFragmented(byte[] buffer, int ptr, NetSending send, int mtu)
|
||||
{
|
||||
NetException.Assert(send.MessageType != NetMessageType.Library, "Library messages cant be fragmented");
|
||||
|
||||
// message type
|
||||
buffer[ptr++] = (byte)((int)send.MessageType | 128);
|
||||
|
||||
// channel sequence number
|
||||
if (send.MessageType >= NetMessageType.UserSequenced)
|
||||
{
|
||||
ushort seqNr = (ushort)send.SequenceNumber;
|
||||
buffer[ptr++] = (byte)seqNr;
|
||||
buffer[ptr++] = (byte)(seqNr >> 8);
|
||||
}
|
||||
|
||||
// calculate fragment payload length
|
||||
mtu -= NetConstants.FragmentHeaderSize; // size of fragmentation info
|
||||
int thisFragmentLength = (send.FragmentNumber == send.FragmentTotalCount - 1 ? (send.Message.LengthBytes - (mtu * (send.FragmentTotalCount - 1))) : mtu);
|
||||
|
||||
int payloadBitsLength = thisFragmentLength * 8;
|
||||
if (payloadBitsLength < 127)
|
||||
{
|
||||
buffer[ptr++] = (byte)payloadBitsLength;
|
||||
}
|
||||
else if (payloadBitsLength < 32768)
|
||||
{
|
||||
buffer[ptr++] = (byte)((payloadBitsLength & 127) | 128);
|
||||
buffer[ptr++] = (byte)(payloadBitsLength >> 7);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NetException("Packet content too large; 4095 bytes maximum");
|
||||
}
|
||||
|
||||
// fragmentation info
|
||||
buffer[ptr++] = (byte)send.FragmentGroupId;
|
||||
buffer[ptr++] = (byte)(send.FragmentGroupId >> 8);
|
||||
buffer[ptr++] = (byte)send.FragmentTotalCount;
|
||||
buffer[ptr++] = (byte)(send.FragmentTotalCount >> 8);
|
||||
buffer[ptr++] = (byte)send.FragmentNumber;
|
||||
buffer[ptr++] = (byte)(send.FragmentNumber >> 8);
|
||||
|
||||
// payload
|
||||
if (payloadBitsLength > 0)
|
||||
{
|
||||
// zero out last byte
|
||||
buffer[ptr + thisFragmentLength] = 0;
|
||||
|
||||
int offset = (mtu * send.FragmentNumber);
|
||||
|
||||
Buffer.BlockCopy(m_data, offset, buffer, ptr, thisFragmentLength);
|
||||
ptr += thisFragmentLength;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
return retval;
|
||||
}
|
||||
|
||||
public void Encrypt(NetXtea tea)
|
||||
@@ -204,14 +125,14 @@ namespace Lidgren.Network
|
||||
Write((byte)0);
|
||||
|
||||
byte[] result = new byte[m_data.Length];
|
||||
for(int i=0;i<blocksNeeded;i++)
|
||||
for (int i = 0; i < blocksNeeded; i++)
|
||||
tea.EncryptBlock(m_data, (i * 8), result, (i * 8));
|
||||
m_data = result;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "[NetOutgoingMessage " + LengthBytes + " bytes]";
|
||||
return "[NetOutgoingMessage " + m_messageType + " " + this.LengthBytes + " bytes]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ namespace Lidgren.Network
|
||||
public void DiscoverLocalPeers(int serverPort)
|
||||
{
|
||||
NetOutgoingMessage om = CreateMessage(0);
|
||||
om.m_libType = NetMessageLibraryType.Discovery;
|
||||
SendUnconnectedLibrary(om, new IPEndPoint(IPAddress.Broadcast, serverPort));
|
||||
om.m_messageType = NetMessageType.Discovery;
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(new IPEndPoint(IPAddress.Broadcast, serverPort), om));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -23,18 +23,35 @@ namespace Lidgren.Network
|
||||
IPAddress address = NetUtility.Resolve(host);
|
||||
if (address == null)
|
||||
return false;
|
||||
return DiscoverKnownPeer(new IPEndPoint(address, serverPort));
|
||||
DiscoverKnownPeer(new IPEndPoint(address, serverPort));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emit a discovery signal to a single known host
|
||||
/// </summary>
|
||||
public bool DiscoverKnownPeer(IPEndPoint endpoint)
|
||||
public void DiscoverKnownPeer(IPEndPoint endpoint)
|
||||
{
|
||||
NetOutgoingMessage om = CreateMessage(0);
|
||||
om.m_libType = NetMessageLibraryType.Discovery;
|
||||
SendUnconnectedLibrary(om, endpoint);
|
||||
return true;
|
||||
om.m_messageType = NetMessageType.Discovery;
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(endpoint, om));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a discovery response message
|
||||
/// </summary>
|
||||
public void SendDiscoveryResponse(NetOutgoingMessage msg, IPEndPoint recipient)
|
||||
{
|
||||
if (recipient == null)
|
||||
throw new ArgumentNullException("recipient");
|
||||
|
||||
if (msg == null)
|
||||
msg = CreateMessage(0);
|
||||
else if (msg.m_isSent)
|
||||
throw new NetException("Message has already been sent!");
|
||||
|
||||
msg.m_messageType = NetMessageType.DiscoveryResponse;
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(recipient, msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
150
Lidgren.Network/NetPeer.Fragmentation.cs
Normal file
150
Lidgren.Network/NetPeer.Fragmentation.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal class ReceivedFragmentGroup
|
||||
{
|
||||
public float LastReceived;
|
||||
public byte[] Data;
|
||||
public NetBitVector ReceivedChunks;
|
||||
}
|
||||
|
||||
public partial class NetPeer
|
||||
{
|
||||
private int m_lastUsedFragmentGroup;
|
||||
|
||||
private Dictionary<int, ReceivedFragmentGroup> m_receivedFragmentGroups;
|
||||
|
||||
// on user thread
|
||||
private void SendFragmentedMessage(NetOutgoingMessage msg, IList<NetConnection> recipients, NetDeliveryMethod method, int sequenceChannel)
|
||||
{
|
||||
int group = Interlocked.Increment(ref m_lastUsedFragmentGroup);
|
||||
if (group >= NetConstants.MaxFragmentationGroups)
|
||||
{
|
||||
// TODO: not thread safe; but in practice probably not an issue
|
||||
m_lastUsedFragmentGroup = 1;
|
||||
group = 1;
|
||||
}
|
||||
msg.m_fragmentGroup = group;
|
||||
|
||||
// do not send msg; but set fragmentgroup in case user tries to recycle it immediately
|
||||
|
||||
// create fragmentation specifics
|
||||
int totalBytes = msg.LengthBytes;
|
||||
int mtu = m_configuration.MaximumTransmissionUnit;
|
||||
|
||||
int bytesPerChunk = NetFragmentationHelper.GetBestChunkSize(group, totalBytes, mtu);
|
||||
|
||||
int numChunks = totalBytes / bytesPerChunk;
|
||||
if (numChunks * bytesPerChunk < totalBytes)
|
||||
numChunks++;
|
||||
|
||||
int bitsPerChunk = bytesPerChunk * 8;
|
||||
int bitsLeft = msg.LengthBits;
|
||||
for (int i = 0; i < numChunks; i++)
|
||||
{
|
||||
NetOutgoingMessage chunk = CreateMessage(mtu);
|
||||
|
||||
chunk.m_bitLength = (bitsLeft > bitsPerChunk ? bitsPerChunk : bitsLeft);
|
||||
chunk.m_data = msg.m_data;
|
||||
chunk.m_fragmentGroup = group;
|
||||
chunk.m_fragmentGroupTotalBits = totalBytes * 8;
|
||||
chunk.m_fragmentChunkByteSize = bytesPerChunk;
|
||||
chunk.m_fragmentChunkNumber = i;
|
||||
|
||||
NetException.Assert(chunk.m_bitLength != 0);
|
||||
NetException.Assert(chunk.GetEncodedSize() < mtu);
|
||||
|
||||
Interlocked.Add(ref chunk.m_recyclingCount, recipients.Count);
|
||||
|
||||
foreach (NetConnection recipient in recipients)
|
||||
recipient.EnqueueMessage(chunk, method, sequenceChannel);
|
||||
|
||||
bitsLeft -= bitsPerChunk;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void HandleReleasedFragment(NetIncomingMessage im)
|
||||
{
|
||||
//
|
||||
// read fragmentation header and combine fragments
|
||||
//
|
||||
int group;
|
||||
int totalBits;
|
||||
int chunkByteSize;
|
||||
int chunkNumber;
|
||||
int ptr = NetFragmentationHelper.ReadHeader(
|
||||
im.m_data, 0,
|
||||
out group,
|
||||
out totalBits,
|
||||
out chunkByteSize,
|
||||
out chunkNumber
|
||||
);
|
||||
|
||||
NetException.Assert(im.LengthBytes > ptr);
|
||||
|
||||
NetException.Assert(group > 0);
|
||||
NetException.Assert(totalBits > 0);
|
||||
NetException.Assert(chunkByteSize > 0);
|
||||
|
||||
int totalBytes = NetUtility.BytesToHoldBits((int)totalBits);
|
||||
int totalNumChunks = totalBytes / chunkByteSize;
|
||||
if (totalNumChunks * chunkByteSize < totalBytes)
|
||||
totalNumChunks++;
|
||||
|
||||
NetException.Assert(chunkNumber < totalNumChunks);
|
||||
|
||||
if (chunkNumber >= totalNumChunks)
|
||||
{
|
||||
LogWarning("Index out of bounds for chunk " + chunkNumber + " (total chunks " + totalNumChunks + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
ReceivedFragmentGroup info;
|
||||
if (!m_receivedFragmentGroups.TryGetValue(group, out info))
|
||||
{
|
||||
info = new ReceivedFragmentGroup();
|
||||
info.Data = new byte[totalBytes];
|
||||
info.ReceivedChunks = new NetBitVector(totalNumChunks);
|
||||
m_receivedFragmentGroups[group] = info;
|
||||
}
|
||||
|
||||
info.ReceivedChunks[chunkNumber] = true;
|
||||
info.LastReceived = (float)NetTime.Now;
|
||||
|
||||
// copy to data
|
||||
int offset = (chunkNumber * chunkByteSize);
|
||||
Buffer.BlockCopy(im.m_data, ptr, info.Data, offset, im.LengthBytes - ptr);
|
||||
|
||||
int cnt = info.ReceivedChunks.Count();
|
||||
//Console.WriteLine("Found fragment #" + chunkNumber + " in group " + group + " offset " + offset + " of total bits " + totalBits + " (total chunks done " + cnt + ")");
|
||||
|
||||
LogVerbose("Received fragment " + chunkNumber + " of " + totalNumChunks + " (" + cnt + " chunks received)");
|
||||
|
||||
if (info.ReceivedChunks.Count() == totalNumChunks)
|
||||
{
|
||||
// Done! Transform this incoming message
|
||||
im.m_data = info.Data;
|
||||
im.m_bitLength = (int)totalBits;
|
||||
im.m_isFragment = false;
|
||||
|
||||
LogVerbose("Fragment group #" + group + " fully received in " + totalNumChunks + " chunks (" + totalBits + " bits)");
|
||||
|
||||
ReleaseMessage(im);
|
||||
}
|
||||
else
|
||||
{
|
||||
// data has been copied; recycle this incoming message
|
||||
Recycle(im);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +1,66 @@
|
||||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
#define IS_MAC_AVAILABLE
|
||||
#define IS_MAC_AVAILABLE
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Net.Sockets;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetPeer
|
||||
{
|
||||
private EndPoint m_senderRemote;
|
||||
internal byte[] m_receiveBuffer;
|
||||
private NetPeerStatus m_status;
|
||||
private Thread m_networkThread;
|
||||
private Socket m_socket;
|
||||
internal byte[] m_sendBuffer;
|
||||
internal Socket m_socket;
|
||||
internal byte[] m_macAddressBytes;
|
||||
private int m_listenPort;
|
||||
internal byte[] m_receiveBuffer;
|
||||
internal NetIncomingMessage m_readHelperMessage;
|
||||
private EndPoint m_senderRemote;
|
||||
private object m_initializeLock = new object();
|
||||
|
||||
internal readonly NetPeerConfiguration m_configuration;
|
||||
private readonly NetQueue<NetIncomingMessage> m_releasedIncomingMessages;
|
||||
internal readonly NetQueue<NetTuple<IPEndPoint, NetOutgoingMessage>> m_unsentUnconnectedMessages;
|
||||
|
||||
internal Dictionary<IPEndPoint, NetConnection> m_handshakes;
|
||||
|
||||
internal readonly NetPeerStatistics m_statistics;
|
||||
internal long m_uniqueIdentifier;
|
||||
|
||||
private AutoResetEvent m_messageReceivedEvent = new AutoResetEvent(false);
|
||||
|
||||
private readonly NetQueue<NetIncomingMessage> m_releasedIncomingMessages = new NetQueue<NetIncomingMessage>(8);
|
||||
private readonly NetQueue<NetSending> m_unsentUnconnectedMessage = new NetQueue<NetSending>(2);
|
||||
|
||||
/// <summary>
|
||||
/// Signalling event which can be waited on to determine when a message is queued for reading.
|
||||
/// Note that there is no guarantee that after the event is signaled the blocked thread will
|
||||
/// find the message in the queue. Other user created threads could be preempted and dequeue
|
||||
/// the message before the waiting thread wakes up.
|
||||
/// </summary>
|
||||
public AutoResetEvent MessageReceivedEvent { get { return m_messageReceivedEvent; } }
|
||||
|
||||
internal void ReleaseMessage(NetIncomingMessage msg)
|
||||
{
|
||||
NetException.Assert(msg.m_status != NetIncomingMessageReleaseStatus.ReleasedToApplication, "Message released to application twice!");
|
||||
NetException.Assert(msg.m_incomingMessageType != NetIncomingMessageType.Error);
|
||||
|
||||
NetException.Assert(msg.m_fragmentationInfo == null, "Fragment released to application!");
|
||||
|
||||
msg.m_status = NetIncomingMessageReleaseStatus.ReleasedToApplication;
|
||||
if (msg.m_isFragment)
|
||||
{
|
||||
HandleReleasedFragment(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
m_releasedIncomingMessages.Enqueue(msg);
|
||||
if (m_messageReceivedEvent != null)
|
||||
m_messageReceivedEvent.Set();
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("DEBUG")]
|
||||
internal void VerifyNetworkThread()
|
||||
{
|
||||
Thread ct = System.Threading.Thread.CurrentThread;
|
||||
if (ct != m_networkThread)
|
||||
throw new NetException("Executing on wrong thread! Should be library system thread (is " + ct.Name + " mId " + ct.ManagedThreadId + ")");
|
||||
}
|
||||
|
||||
private void InitializeNetwork()
|
||||
{
|
||||
//
|
||||
// Initialize
|
||||
//
|
||||
|
||||
InitializeRecycling();
|
||||
|
||||
System.Net.NetworkInformation.PhysicalAddress pa = null;
|
||||
#if IS_MAC_AVAILABLE
|
||||
pa = NetUtility.GetMacAddress();
|
||||
if (pa != null)
|
||||
{
|
||||
m_macAddressBytes = pa.GetAddressBytes();
|
||||
LogVerbose("Mac address is " + NetUtility.ToHexString(m_macAddressBytes));
|
||||
}
|
||||
else
|
||||
{
|
||||
LogWarning("Failed to get Mac address");
|
||||
}
|
||||
#else
|
||||
// random bytes is better than nothing
|
||||
m_macAddressBytes = new byte[6];
|
||||
NetRandom.Instance.NextBytes(m_macAddressBytes);
|
||||
#endif
|
||||
LogDebug("Initializing Network");
|
||||
|
||||
lock (m_initializeLock)
|
||||
{
|
||||
m_configuration.Lock();
|
||||
|
||||
if (m_status == NetPeerStatus.Running)
|
||||
return;
|
||||
|
||||
m_statistics.Reset();
|
||||
InitializePools();
|
||||
|
||||
m_releasedIncomingMessages.Clear();
|
||||
m_unsentUnconnectedMessages.Clear();
|
||||
m_handshakes.Clear();
|
||||
|
||||
// bind to socket
|
||||
IPEndPoint iep = null;
|
||||
@@ -115,36 +76,38 @@ namespace Lidgren.Network
|
||||
|
||||
IPEndPoint boundEp = m_socket.LocalEndPoint as IPEndPoint;
|
||||
LogDebug("Socket bound to " + boundEp + ": " + m_socket.IsBound);
|
||||
|
||||
m_listenPort = boundEp.Port;
|
||||
|
||||
int first = (pa == null ? this.GetHashCode() : pa.GetHashCode());
|
||||
int second = boundEp.GetHashCode();
|
||||
|
||||
byte[] raw = new byte[8];
|
||||
raw[0] = (byte)first;
|
||||
raw[1] = (byte)(first << 8);
|
||||
raw[2] = (byte)(first << 16);
|
||||
raw[3] = (byte)(first << 24);
|
||||
raw[4] = (byte)second;
|
||||
raw[5] = (byte)(second << 8);
|
||||
raw[6] = (byte)(second << 16);
|
||||
raw[7] = (byte)(second << 24);
|
||||
m_uniqueIdentifier = BitConverter.ToInt64(NetSha.Hash(raw), 0);
|
||||
|
||||
m_receiveBuffer = new byte[m_configuration.ReceiveBufferSize];
|
||||
m_sendBuffer = new byte[m_configuration.SendBufferSize];
|
||||
m_readHelperMessage = new NetIncomingMessage(NetIncomingMessageType.Error);
|
||||
m_readHelperMessage.m_data = m_receiveBuffer;
|
||||
|
||||
LogVerbose("Initialization done");
|
||||
byte[] macBytes = new byte[8];
|
||||
NetRandom.Instance.NextBytes(macBytes);
|
||||
|
||||
#if IS_MAC_AVAILABLE
|
||||
System.Net.NetworkInformation.PhysicalAddress pa = NetUtility.GetMacAddress();
|
||||
if (pa != null)
|
||||
{
|
||||
macBytes = pa.GetAddressBytes();
|
||||
LogVerbose("Mac address is " + NetUtility.ToHexString(macBytes));
|
||||
}
|
||||
else
|
||||
{
|
||||
LogWarning("Failed to get Mac address");
|
||||
}
|
||||
#endif
|
||||
byte[] epBytes = BitConverter.GetBytes(boundEp.GetHashCode());
|
||||
byte[] combined = new byte[epBytes.Length + macBytes.Length];
|
||||
Array.Copy(epBytes, 0, combined, 0, epBytes.Length);
|
||||
Array.Copy(macBytes, 0, combined, epBytes.Length, macBytes.Length);
|
||||
m_uniqueIdentifier = BitConverter.ToInt64(SHA1.Create().ComputeHash(combined), 0);
|
||||
|
||||
// only set Running if everything succeeds
|
||||
m_status = NetPeerStatus.Running;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Network loop
|
||||
//
|
||||
private void NetworkLoop()
|
||||
{
|
||||
VerifyNetworkThread();
|
||||
@@ -169,14 +132,26 @@ namespace Lidgren.Network
|
||||
//
|
||||
// perform shutdown
|
||||
//
|
||||
ExecutePeerShutdown();
|
||||
}
|
||||
|
||||
private void ExecutePeerShutdown()
|
||||
{
|
||||
VerifyNetworkThread();
|
||||
|
||||
LogDebug("Shutting down...");
|
||||
|
||||
// disconnect and make one final heartbeat
|
||||
lock (m_connections)
|
||||
{
|
||||
foreach (NetConnection conn in m_connections)
|
||||
if (conn.m_status == NetConnectionStatus.Connected || conn.m_status == NetConnectionStatus.Connecting)
|
||||
conn.Disconnect(m_shutdownReason);
|
||||
conn.Shutdown(m_shutdownReason);
|
||||
}
|
||||
|
||||
lock (m_handshakes)
|
||||
{
|
||||
foreach (NetConnection conn in m_handshakes.Values)
|
||||
conn.Shutdown(m_shutdownReason);
|
||||
}
|
||||
|
||||
// one final heartbeat, will send stuff and do disconnect
|
||||
@@ -203,6 +178,12 @@ namespace Lidgren.Network
|
||||
m_status = NetPeerStatus.NotRunning;
|
||||
LogDebug("Shutdown complete");
|
||||
}
|
||||
|
||||
m_receiveBuffer = null;
|
||||
m_sendBuffer = null;
|
||||
m_unsentUnconnectedMessages.Clear();
|
||||
m_connections.Clear();
|
||||
m_handshakes.Clear();
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -212,449 +193,300 @@ namespace Lidgren.Network
|
||||
{
|
||||
VerifyNetworkThread();
|
||||
|
||||
float now = (float)NetTime.Now;
|
||||
|
||||
// do handshake heartbeats
|
||||
foreach (NetConnection conn in m_handshakes.Values)
|
||||
{
|
||||
conn.UnconnectedHeartbeat(now);
|
||||
if (conn.m_status == NetConnectionStatus.Connected || conn.m_status == NetConnectionStatus.Disconnected)
|
||||
break; // collection is modified
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
// send delayed packets
|
||||
SendDelayedPackets();
|
||||
#endif
|
||||
|
||||
// connection approval
|
||||
CheckPendingConnections();
|
||||
|
||||
double now = NetTime.Now;
|
||||
|
||||
// do connection heartbeats
|
||||
foreach (NetConnection conn in m_connections)
|
||||
lock (m_connections)
|
||||
{
|
||||
conn.Heartbeat(now);
|
||||
if (conn.m_status == NetConnectionStatus.Disconnected)
|
||||
foreach (NetConnection conn in m_connections)
|
||||
{
|
||||
RemoveConnection(conn);
|
||||
break; // can't continue iteration here
|
||||
conn.Heartbeat(now);
|
||||
if (conn.m_status == NetConnectionStatus.Disconnected)
|
||||
{
|
||||
//
|
||||
// remove connection
|
||||
//
|
||||
m_connections.Remove(conn);
|
||||
m_connectionLookup.Remove(conn.RemoteEndpoint);
|
||||
break; // can't continue iteration here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// send unconnected sends
|
||||
NetSending uncSend;
|
||||
while (m_unsentUnconnectedMessage.TryDequeue(out uncSend))
|
||||
// send unsent unconnected messages
|
||||
NetTuple<IPEndPoint, NetOutgoingMessage> unsent;
|
||||
while (m_unsentUnconnectedMessages.TryDequeue(out unsent))
|
||||
{
|
||||
//
|
||||
// TODO: use throttling here
|
||||
//
|
||||
NetOutgoingMessage om = unsent.Item2;
|
||||
|
||||
int ptr = uncSend.Message.EncodeUnfragmented(m_sendBuffer, 0, uncSend.MessageType, uncSend.SequenceNumber);
|
||||
bool connectionReset = false;
|
||||
bool connReset;
|
||||
int len = om.Encode(m_sendBuffer, 0, 0);
|
||||
SendPacket(len, unsent.Item1, 1, out connReset);
|
||||
|
||||
if (uncSend.Recipient.Address.Equals(IPAddress.Broadcast))
|
||||
{
|
||||
// send using broadcast
|
||||
try
|
||||
{
|
||||
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
|
||||
SendPacket(ptr, uncSend.Recipient, 1, out connectionReset);
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// send normally
|
||||
SendPacket(ptr, uncSend.Recipient, 1, out connectionReset);
|
||||
}
|
||||
|
||||
if (connectionReset)
|
||||
LogWarning(NetConstants.ConnResetMessage);
|
||||
|
||||
int unfin = uncSend.Message.m_numUnfinishedSendings;
|
||||
uncSend.Message.m_numUnfinishedSendings = unfin - 1;
|
||||
if (unfin <= 1)
|
||||
Recycle(uncSend.Message);
|
||||
Interlocked.Decrement(ref om.m_recyclingCount);
|
||||
if (om.m_recyclingCount <= 0)
|
||||
Recycle(om);
|
||||
}
|
||||
|
||||
// check if we need to reduce the recycled pool
|
||||
ReduceStoragePool();
|
||||
|
||||
//
|
||||
// read from socket
|
||||
//
|
||||
do
|
||||
if (m_socket == null)
|
||||
return;
|
||||
|
||||
if (!m_socket.Poll(500, SelectMode.SelectRead)) // wait up to 1/2 ms for data to arrive
|
||||
return;
|
||||
|
||||
//if (m_socket == null || m_socket.Available < 1)
|
||||
// return;
|
||||
|
||||
int bytesReceived = 0;
|
||||
try
|
||||
{
|
||||
if (m_socket == null)
|
||||
bytesReceived = m_socket.ReceiveFrom(m_receiveBuffer, 0, m_receiveBuffer.Length, SocketFlags.None, ref m_senderRemote);
|
||||
}
|
||||
catch (SocketException sx)
|
||||
{
|
||||
// no good response to this yet
|
||||
if (sx.SocketErrorCode == SocketError.ConnectionReset)
|
||||
{
|
||||
// connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable"
|
||||
// we should shut down the connection; but m_senderRemote seemingly cannot be trusted, so which connection should we shut down?!
|
||||
//LogWarning("Connection reset by peer, seemingly from " + m_senderRemote);
|
||||
lock (m_connections)
|
||||
{
|
||||
if (m_connections.Count + m_handshakes.Count == 1)
|
||||
{
|
||||
foreach (var kvp in m_handshakes)
|
||||
kvp.Value.ExecuteDisconnect("Connection forcibly closed", true);
|
||||
foreach (var conn in m_connections)
|
||||
conn.ExecuteDisconnect("Connection forcibly closed", true);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_socket.Poll(1000, SelectMode.SelectRead)) // wait up to 1 ms for data to arrive
|
||||
LogWarning(sx.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (bytesReceived < NetConstants.HeaderByteSize)
|
||||
return;
|
||||
|
||||
//LogVerbose("Received " + bytesReceived + " bytes");
|
||||
|
||||
IPEndPoint ipsender = (IPEndPoint)m_senderRemote;
|
||||
|
||||
NetConnection sender = null;
|
||||
m_connectionLookup.TryGetValue(ipsender, out sender);
|
||||
|
||||
//
|
||||
// parse packet into messages
|
||||
//
|
||||
int ptr = 0;
|
||||
while ((bytesReceived - ptr) >= NetConstants.HeaderByteSize)
|
||||
{
|
||||
// decode header
|
||||
// 8 bits - NetMessageType
|
||||
// 1 bit - Fragment?
|
||||
// 15 bits - Sequence number
|
||||
// 16 bits - Payload length in bits
|
||||
|
||||
NetMessageType tp = (NetMessageType)m_receiveBuffer[ptr++];
|
||||
|
||||
byte low = m_receiveBuffer[ptr++];
|
||||
byte high = m_receiveBuffer[ptr++];
|
||||
|
||||
bool isFragment = ((low & 1) == 1);
|
||||
ushort sequenceNumber = (ushort)((low >> 1) | (((int)high) << 7));
|
||||
|
||||
ushort payloadBitLength = (ushort)(m_receiveBuffer[ptr++] | (m_receiveBuffer[ptr++] << 8));
|
||||
int payloadByteLength = NetUtility.BytesToHoldBits(payloadBitLength);
|
||||
|
||||
if (bytesReceived - ptr < payloadByteLength)
|
||||
{
|
||||
LogWarning("Malformed packet; stated payload length " + payloadByteLength + ", remaining bytes " + (bytesReceived - ptr));
|
||||
return;
|
||||
}
|
||||
|
||||
//if (m_socket == null || m_socket.Available < 1)
|
||||
// return;
|
||||
|
||||
int bytesReceived = 0;
|
||||
try
|
||||
{
|
||||
bytesReceived = m_socket.ReceiveFrom(m_receiveBuffer, 0, m_receiveBuffer.Length, SocketFlags.None, ref m_senderRemote);
|
||||
}
|
||||
catch (SocketException sx)
|
||||
{
|
||||
// no good response to this yet
|
||||
if (sx.SocketErrorCode == SocketError.ConnectionReset)
|
||||
NetException.Assert(tp < NetMessageType.Unused1 || tp > NetMessageType.Unused29);
|
||||
|
||||
if (tp >= NetMessageType.LibraryError)
|
||||
{
|
||||
// connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable"
|
||||
// we should shut down the connection; but m_senderRemote seemingly cannot be trusted, so which connection should we shut down?!
|
||||
//LogWarning("Connection reset by peer, seemingly from " + m_senderRemote);
|
||||
lock (m_connections)
|
||||
if (sender != null)
|
||||
sender.ReceivedLibraryMessage(tp, ptr, payloadByteLength);
|
||||
else
|
||||
ReceivedUnconnectedLibraryMessage(ipsender, tp, ptr, payloadByteLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sender == null && !m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.UnconnectedData))
|
||||
return; // dropping unconnected message since it's not enabled
|
||||
|
||||
NetIncomingMessage msg = CreateIncomingMessage(NetIncomingMessageType.Data, payloadByteLength);
|
||||
msg.m_isFragment = isFragment;
|
||||
msg.m_sequenceNumber = sequenceNumber;
|
||||
msg.m_receivedMessageType = tp;
|
||||
msg.m_senderConnection = sender;
|
||||
msg.m_senderEndpoint = ipsender;
|
||||
msg.m_bitLength = payloadBitLength;
|
||||
Buffer.BlockCopy(m_receiveBuffer, ptr, msg.m_data, 0, payloadByteLength);
|
||||
if (sender != null)
|
||||
{
|
||||
if (m_connections.Count == 1)
|
||||
if (tp == NetMessageType.Unconnected)
|
||||
{
|
||||
// only one connection; let's shut it down, unless already in progress
|
||||
m_connections[0].Disconnect("Connection forcibly closed");
|
||||
m_connections[0].ExecuteDisconnect(false);
|
||||
m_connections[0].FinishDisconnect();
|
||||
// We're connected; but we can still send unconnected messages to this peer
|
||||
msg.m_incomingMessageType = NetIncomingMessageType.UnconnectedData;
|
||||
ReleaseMessage(msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
// connected application (non-library) message
|
||||
sender.ReceivedMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
LogWarning(sx.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (bytesReceived < NetPeer.kMinPacketHeaderSize)
|
||||
return;
|
||||
|
||||
// renew current time; we might have waited in Poll
|
||||
now = NetTime.Now;
|
||||
|
||||
//LogVerbose("Received " + bytesReceived + " bytes");
|
||||
|
||||
IPEndPoint ipsender = (IPEndPoint)m_senderRemote;
|
||||
|
||||
NetConnection sender = null;
|
||||
m_connectionLookup.TryGetValue(ipsender, out sender);
|
||||
|
||||
int ptr = 0;
|
||||
NetMessageType msgType;
|
||||
NetMessageLibraryType libType = NetMessageLibraryType.Error;
|
||||
|
||||
//
|
||||
// parse packet into messages
|
||||
//
|
||||
int numMessagesReceived = 0;
|
||||
while ((bytesReceived - ptr) >= NetPeer.kMinPacketHeaderSize)
|
||||
{
|
||||
// get NetMessageType
|
||||
byte top = m_receiveBuffer[ptr++];
|
||||
bool isFragment = (top & 128) == 128;
|
||||
msgType = (NetMessageType)(top & 127);
|
||||
|
||||
// get NetmessageLibraryType?
|
||||
if (msgType == NetMessageType.Library)
|
||||
libType = (NetMessageLibraryType)m_receiveBuffer[ptr++];
|
||||
|
||||
// get sequence number?
|
||||
ushort sequenceNumber;
|
||||
if (msgType >= NetMessageType.UserSequenced)
|
||||
sequenceNumber = (ushort)(m_receiveBuffer[ptr++] | (m_receiveBuffer[ptr++] << 8));
|
||||
else
|
||||
sequenceNumber = 0;
|
||||
|
||||
// get payload length
|
||||
int payloadLengthBits = (int)m_receiveBuffer[ptr++];
|
||||
if ((payloadLengthBits & 128) == 128) // large payload
|
||||
payloadLengthBits = (payloadLengthBits & 127) | (m_receiveBuffer[ptr++] << 7);
|
||||
|
||||
int payloadLengthBytes = NetUtility.BytesToHoldBits(payloadLengthBits);
|
||||
|
||||
if ((ptr + payloadLengthBytes) > bytesReceived)
|
||||
{
|
||||
LogWarning("Malformed message from " + ipsender.ToString() + "; not enough bytes");
|
||||
break;
|
||||
}
|
||||
|
||||
//
|
||||
// handle incoming message
|
||||
//
|
||||
|
||||
if (msgType == NetMessageType.Error)
|
||||
{
|
||||
LogError("Malformed message; no message type!");
|
||||
continue;
|
||||
}
|
||||
|
||||
numMessagesReceived++;
|
||||
|
||||
if (msgType == NetMessageType.Library)
|
||||
{
|
||||
if (sender == null)
|
||||
HandleUnconnectedLibraryMessage(libType, ptr, payloadLengthBits, ipsender);
|
||||
else
|
||||
sender.HandleLibraryMessage(now, libType, ptr, payloadLengthBits);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sender == null)
|
||||
{
|
||||
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.UnconnectedData))
|
||||
HandleUnconnectedUserMessage(ptr, payloadLengthBits, ipsender);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.Data))
|
||||
sender.HandleUserMessage(now, msgType, isFragment, sequenceNumber, ptr, payloadLengthBits);
|
||||
// at this point we know the message type is enabled
|
||||
// unconnected application (non-library) message
|
||||
msg.m_incomingMessageType = NetIncomingMessageType.UnconnectedData;
|
||||
ReleaseMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
if (isFragment)
|
||||
ptr += NetConstants.FragmentHeaderSize;
|
||||
|
||||
ptr += payloadLengthBytes;
|
||||
}
|
||||
|
||||
m_statistics.PacketReceived(bytesReceived, numMessagesReceived);
|
||||
|
||||
if (sender != null)
|
||||
catch (Exception ex)
|
||||
{
|
||||
sender.m_lastHeardFrom = now;
|
||||
sender.m_statistics.PacketReceived(bytesReceived, numMessagesReceived);
|
||||
LogError("Packet parsing error: " + ex.Message);
|
||||
}
|
||||
|
||||
if (ptr < bytesReceived)
|
||||
{
|
||||
// malformed packet
|
||||
LogWarning("Malformed packet from " + sender + " (" + ipsender + "); " + (ptr - bytesReceived) + " stray bytes");
|
||||
continue;
|
||||
}
|
||||
} while (true);
|
||||
ptr += payloadByteLength;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleUnconnectedLibraryMessage(NetMessageLibraryType libType, int ptr, int payloadLengthBits, IPEndPoint senderEndpoint)
|
||||
private void ReceivedUnconnectedLibraryMessage(IPEndPoint senderEndpoint, NetMessageType tp, int ptr, int payloadByteLength)
|
||||
{
|
||||
VerifyNetworkThread();
|
||||
|
||||
int payloadLengthBytes = NetUtility.BytesToHoldBits(payloadLengthBits);
|
||||
|
||||
switch (libType)
|
||||
NetConnection shake;
|
||||
if (m_handshakes.TryGetValue(senderEndpoint, out shake))
|
||||
{
|
||||
case NetMessageLibraryType.NatPunchMessage:
|
||||
HandleNatPunch(ptr, senderEndpoint);
|
||||
break;
|
||||
case NetMessageLibraryType.NatIntroduction:
|
||||
HandleNatIntroduction(ptr);
|
||||
break;
|
||||
case NetMessageLibraryType.Discovery:
|
||||
shake.ReceivedHandshake(tp, ptr, payloadByteLength);
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Library message from a completely unknown sender; lets just accept Connect
|
||||
//
|
||||
switch (tp)
|
||||
{
|
||||
case NetMessageType.Discovery:
|
||||
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.DiscoveryRequest))
|
||||
{
|
||||
NetIncomingMessage dm = CreateIncomingMessage(NetIncomingMessageType.DiscoveryRequest, payloadLengthBytes);
|
||||
if (payloadLengthBytes > 0)
|
||||
Buffer.BlockCopy(m_receiveBuffer, ptr, dm.m_data, 0, payloadLengthBytes);
|
||||
dm.m_bitLength = payloadLengthBits;
|
||||
NetIncomingMessage dm = CreateIncomingMessage(NetIncomingMessageType.DiscoveryRequest, payloadByteLength);
|
||||
if (payloadByteLength > 0)
|
||||
Buffer.BlockCopy(m_receiveBuffer, ptr, dm.m_data, 0, payloadByteLength);
|
||||
dm.m_bitLength = payloadByteLength * 8;
|
||||
dm.m_senderEndpoint = senderEndpoint;
|
||||
ReleaseMessage(dm);
|
||||
}
|
||||
return;
|
||||
|
||||
break;
|
||||
case NetMessageLibraryType.DiscoveryResponse:
|
||||
case NetMessageType.DiscoveryResponse:
|
||||
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.DiscoveryResponse))
|
||||
{
|
||||
NetIncomingMessage dr = CreateIncomingMessage(NetIncomingMessageType.DiscoveryResponse, payloadLengthBytes);
|
||||
if (payloadLengthBytes > 0)
|
||||
Buffer.BlockCopy(m_receiveBuffer, ptr, dr.m_data, 0, payloadLengthBytes);
|
||||
dr.m_bitLength = payloadLengthBits;
|
||||
NetIncomingMessage dr = CreateIncomingMessage(NetIncomingMessageType.DiscoveryResponse, payloadByteLength);
|
||||
if (payloadByteLength > 0)
|
||||
Buffer.BlockCopy(m_receiveBuffer, ptr, dr.m_data, 0, payloadByteLength);
|
||||
dr.m_bitLength = payloadByteLength * 8;
|
||||
dr.m_senderEndpoint = senderEndpoint;
|
||||
ReleaseMessage(dr);
|
||||
}
|
||||
return;
|
||||
case NetMessageType.NatIntroduction:
|
||||
HandleNatIntroduction(ptr);
|
||||
return;
|
||||
case NetMessageType.NatPunchMessage:
|
||||
HandleNatPunch(ptr, senderEndpoint);
|
||||
return;
|
||||
case NetMessageType.Connect:
|
||||
// proceed
|
||||
break;
|
||||
|
||||
case NetMessageLibraryType.Connect:
|
||||
|
||||
if (!m_configuration.m_acceptIncomingConnections)
|
||||
{
|
||||
LogWarning("Connect received; but we're not accepting incoming connections!");
|
||||
break;
|
||||
}
|
||||
|
||||
string appIdent;
|
||||
long remoteUniqueIdentifier = 0;
|
||||
NetIncomingMessage approval = null;
|
||||
try
|
||||
{
|
||||
NetIncomingMessage reader = new NetIncomingMessage();
|
||||
|
||||
reader.m_data = GetStorage(payloadLengthBytes);
|
||||
Buffer.BlockCopy(m_receiveBuffer, ptr, reader.m_data, 0, payloadLengthBytes);
|
||||
ptr += payloadLengthBytes;
|
||||
reader.m_bitLength = payloadLengthBits;
|
||||
appIdent = reader.ReadString();
|
||||
remoteUniqueIdentifier = reader.ReadInt64();
|
||||
|
||||
int approvalBitLength = (int)reader.ReadVariableUInt32();
|
||||
if (approvalBitLength > 0)
|
||||
{
|
||||
int approvalByteLength = NetUtility.BytesToHoldBits(approvalBitLength);
|
||||
if (approvalByteLength < m_configuration.MaximumTransmissionUnit)
|
||||
{
|
||||
approval = CreateIncomingMessage(NetIncomingMessageType.ConnectionApproval, approvalByteLength);
|
||||
reader.ReadBits(approval.m_data, 0, approvalBitLength);
|
||||
approval.m_bitLength = approvalBitLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// malformed connect packet
|
||||
LogWarning("Malformed connect packet from " + senderEndpoint + " - " + ex.ToString());
|
||||
break;
|
||||
}
|
||||
|
||||
if (appIdent.Equals(m_configuration.AppIdentifier, StringComparison.InvariantCulture) == false)
|
||||
{
|
||||
// wrong app ident
|
||||
LogWarning("Connect received with wrong appidentifier (need '" + m_configuration.AppIdentifier + "' found '" + appIdent + "') from " + senderEndpoint);
|
||||
|
||||
NetOutgoingMessage bye = CreateLibraryMessage(NetMessageLibraryType.Disconnect, "Wrong app identifier!");
|
||||
SendUnconnectedLibrary(bye, senderEndpoint);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// ok, someone wants to connect to us, and we're accepting connections!
|
||||
int reservedSlots = m_connections.Count;
|
||||
if (m_pendingConnections != null)
|
||||
reservedSlots += m_pendingConnections.Count;
|
||||
if (reservedSlots >= m_configuration.MaximumConnections)
|
||||
{
|
||||
HandleServerFull(senderEndpoint);
|
||||
break;
|
||||
}
|
||||
|
||||
bool isAlreadyPending = false;
|
||||
if (m_pendingConnections != null)
|
||||
{
|
||||
// check so we don't already have a pending connection to this endpoint
|
||||
foreach (NetConnection conn in m_pendingConnections)
|
||||
{
|
||||
if (conn.RemoteEndpoint.Equals(senderEndpoint))
|
||||
{
|
||||
// Yes, we do.
|
||||
isAlreadyPending = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAlreadyPending)
|
||||
{
|
||||
NetConnection conn = new NetConnection(this, senderEndpoint);
|
||||
conn.m_connectionInitiator = false;
|
||||
conn.m_connectInitationTime = NetTime.Now;
|
||||
conn.m_remoteUniqueIdentifier = remoteUniqueIdentifier;
|
||||
|
||||
if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.ConnectionApproval))
|
||||
{
|
||||
// do connection approval before accepting this connection
|
||||
AddPendingConnection(conn, approval);
|
||||
break;
|
||||
}
|
||||
|
||||
AcceptConnection(conn);
|
||||
}
|
||||
break;
|
||||
case NetMessageType.Disconnect:
|
||||
// this is probably ok
|
||||
LogVerbose("Received Disconnect from unconnected source: " + senderEndpoint);
|
||||
return;
|
||||
default:
|
||||
LogWarning("Received unconnected library message of type " + libType);
|
||||
break;
|
||||
LogWarning("Received unhandled library message " + tp + " from " + senderEndpoint);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleUnconnectedUserMessage(int ptr, int payloadLengthBits, IPEndPoint senderEndpoint)
|
||||
{
|
||||
VerifyNetworkThread();
|
||||
// It's someone wanting to shake hands with us!
|
||||
|
||||
NetIncomingMessage ium = CreateIncomingMessage(NetIncomingMessageType.UnconnectedData, m_receiveBuffer, ptr, NetUtility.BytesToHoldBits(payloadLengthBits));
|
||||
ium.m_bitLength = payloadLengthBits;
|
||||
ium.m_senderEndpoint = senderEndpoint;
|
||||
ReleaseMessage(ium);
|
||||
}
|
||||
|
||||
private void AcceptConnection(NetConnection conn)
|
||||
{
|
||||
lock (m_connections)
|
||||
int reservedSlots = m_handshakes.Count + m_connections.Count;
|
||||
if (reservedSlots >= m_configuration.m_maximumConnections)
|
||||
{
|
||||
m_connections.Add(conn);
|
||||
m_connectionLookup[conn.m_remoteEndpoint] = conn;
|
||||
// server full
|
||||
NetOutgoingMessage full = CreateMessage("Server full");
|
||||
full.m_messageType = NetMessageType.Disconnect;
|
||||
SendLibrary(full, senderEndpoint);
|
||||
return;
|
||||
}
|
||||
conn.SetStatus(NetConnectionStatus.Connecting, "Connecting");
|
||||
|
||||
// send connection response
|
||||
conn.SendConnectResponse();
|
||||
|
||||
conn.m_connectInitationTime = NetTime.Now;
|
||||
// Ok, start handshake!
|
||||
NetConnection conn = new NetConnection(this, senderEndpoint);
|
||||
m_handshakes.Add(senderEndpoint, conn);
|
||||
conn.ReceivedHandshake(tp, ptr, payloadByteLength);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
internal void RemoveConnection(NetConnection conn)
|
||||
internal void AcceptConnection(NetConnection conn)
|
||||
{
|
||||
// LogDebug("Accepted connection " + conn);
|
||||
|
||||
if (m_handshakes.Remove(conn.m_remoteEndpoint) == false)
|
||||
LogWarning("AcceptConnection called but m_handshakes did not contain it!");
|
||||
|
||||
lock (m_connections)
|
||||
{
|
||||
m_connections.Remove(conn);
|
||||
m_connectionLookup.Remove(conn.m_remoteEndpoint);
|
||||
if (m_connections.Contains(conn))
|
||||
{
|
||||
LogWarning("AcceptConnection called but m_connection already contains it!");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_connections.Add(conn);
|
||||
m_connectionLookup.Add(conn.m_remoteEndpoint, conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleServerFull(IPEndPoint connecter)
|
||||
[Conditional("DEBUG")]
|
||||
internal void VerifyNetworkThread()
|
||||
{
|
||||
const string rejectMessage = "Server is full!"; // TODO: put in configuration
|
||||
NetOutgoingMessage reply = CreateLibraryMessage(NetMessageLibraryType.Disconnect, rejectMessage);
|
||||
SendLibraryImmediately(reply, connecter);
|
||||
Thread ct = Thread.CurrentThread;
|
||||
if (Thread.CurrentThread != m_networkThread)
|
||||
throw new NetException("Executing on wrong thread! Should be library system thread (is " + ct.Name + " mId " + ct.ManagedThreadId + ")");
|
||||
}
|
||||
|
||||
// called by user and network thread
|
||||
private void EnqueueUnconnectedMessage(NetOutgoingMessage msg, IPEndPoint recipient)
|
||||
internal NetIncomingMessage SetupReadHelperMessage(int ptr, int payloadLength)
|
||||
{
|
||||
NetSending send = new NetSending(msg, NetMessageType.UserUnreliable, 0);
|
||||
send.Recipient = recipient;
|
||||
VerifyNetworkThread();
|
||||
|
||||
msg.m_numUnfinishedSendings++;
|
||||
m_unsentUnconnectedMessage.Enqueue(send);
|
||||
m_readHelperMessage.m_bitLength = (ptr + payloadLength) * 8;
|
||||
m_readHelperMessage.m_readPosition = (ptr * 8);
|
||||
return m_readHelperMessage;
|
||||
}
|
||||
|
||||
// called by user and network thread
|
||||
private void SendUnconnectedLibrary(NetOutgoingMessage msg, IPEndPoint recipient)
|
||||
{
|
||||
msg.m_wasSent = true;
|
||||
NetSending send = new NetSending(msg, NetMessageType.Library, 0);
|
||||
send.Recipient = recipient;
|
||||
|
||||
msg.m_numUnfinishedSendings++;
|
||||
m_unsentUnconnectedMessage.Enqueue(send);
|
||||
}
|
||||
|
||||
internal static NetDeliveryMethod GetDeliveryMethod(NetMessageType mtp)
|
||||
{
|
||||
if (mtp >= NetMessageType.UserReliableOrdered)
|
||||
return NetDeliveryMethod.ReliableOrdered;
|
||||
else if (mtp >= NetMessageType.UserReliableSequenced)
|
||||
return NetDeliveryMethod.ReliableSequenced;
|
||||
else if (mtp >= NetMessageType.UserReliableUnordered)
|
||||
return NetDeliveryMethod.ReliableUnordered;
|
||||
else if (mtp >= NetMessageType.UserSequenced)
|
||||
return NetDeliveryMethod.UnreliableSequenced;
|
||||
return NetDeliveryMethod.Unreliable;
|
||||
}
|
||||
|
||||
internal void SendLibraryImmediately(NetOutgoingMessage msg, IPEndPoint destination)
|
||||
{
|
||||
msg.m_wasSent = true;
|
||||
int len = msg.EncodeUnfragmented(m_sendBuffer, 0, NetMessageType.Library, 0);
|
||||
|
||||
bool connectionReset;
|
||||
SendPacket(len, destination, 1, out connectionReset);
|
||||
|
||||
// TODO: handle connectionReset
|
||||
|
||||
Recycle(msg);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
@@ -45,34 +46,35 @@ namespace Lidgren.Network
|
||||
float loss = m_configuration.m_loss;
|
||||
if (loss > 0.0f)
|
||||
{
|
||||
if (NetRandom.Instance.Chance(m_configuration.m_loss))
|
||||
if ((float)NetRandom.Instance.NextDouble() < loss)
|
||||
{
|
||||
//LogVerbose("Sending packet " + numBytes + " bytes - SIMULATED LOST!");
|
||||
LogVerbose("Sending packet " + numBytes + " bytes - SIMULATED LOST!");
|
||||
return; // packet "lost"
|
||||
}
|
||||
}
|
||||
|
||||
m_statistics.PacketSent(numBytes, numMessages);
|
||||
|
||||
|
||||
// simulate latency
|
||||
float m = m_configuration.m_minimumOneWayLatency;
|
||||
float r = m_configuration.m_randomOneWayLatency;
|
||||
if (m == 0.0f && r == 0.0f)
|
||||
{
|
||||
// no latency simulation
|
||||
//LogVerbose("Sending packet " + numBytes + " bytes");
|
||||
ActuallySendPacket(m_sendBuffer, numBytes, target, out connectionReset);
|
||||
// LogVerbose("Sending packet " + numBytes + " bytes");
|
||||
bool wasSent = ActuallySendPacket(m_sendBuffer, numBytes, target, out connectionReset);
|
||||
// TODO: handle wasSent == false?
|
||||
return;
|
||||
}
|
||||
|
||||
int num = 1;
|
||||
if (m_configuration.m_duplicates > 0.0f && NetRandom.Instance.Chance(m_configuration.m_duplicates))
|
||||
if (m_configuration.m_duplicates > 0.0f && NetRandom.Instance.NextSingle() < m_configuration.m_duplicates)
|
||||
num++;
|
||||
|
||||
float delay = 0;
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
delay = m_configuration.m_minimumOneWayLatency + (NetRandom.Instance.NextFloat() * m_configuration.m_randomOneWayLatency);
|
||||
delay = m_configuration.m_minimumOneWayLatency + (NetRandom.Instance.NextSingle() * m_configuration.m_randomOneWayLatency);
|
||||
|
||||
// Enqueue delayed packet
|
||||
DelayedPacket p = new DelayedPacket();
|
||||
@@ -108,7 +110,7 @@ namespace Lidgren.Network
|
||||
}
|
||||
}
|
||||
|
||||
internal void ActuallySendPacket(byte[] data, int numBytes, IPEndPoint target, out bool connectionReset)
|
||||
internal bool ActuallySendPacket(byte[] data, int numBytes, IPEndPoint target, out bool connectionReset)
|
||||
{
|
||||
connectionReset = false;
|
||||
try
|
||||
@@ -122,6 +124,56 @@ namespace Lidgren.Network
|
||||
}
|
||||
catch (SocketException sx)
|
||||
{
|
||||
if (sx.SocketErrorCode == SocketError.WouldBlock)
|
||||
{
|
||||
// send buffer full?
|
||||
LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration");
|
||||
return false;
|
||||
}
|
||||
if (sx.SocketErrorCode == SocketError.ConnectionReset)
|
||||
{
|
||||
// connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable"
|
||||
connectionReset = true;
|
||||
return false;
|
||||
}
|
||||
LogError("Failed to send packet: " + sx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError("Failed to send packet: " + ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (target.Address == IPAddress.Broadcast)
|
||||
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#else
|
||||
//
|
||||
// Release - just send the packet straight away
|
||||
//
|
||||
internal void SendPacket(int numBytes, IPEndPoint target, int numMessages, out bool connectionReset)
|
||||
{
|
||||
connectionReset = false;
|
||||
try
|
||||
{
|
||||
if (target.Address == IPAddress.Broadcast)
|
||||
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 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.WouldBlock)
|
||||
{
|
||||
// send buffer full?
|
||||
LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration");
|
||||
return;
|
||||
}
|
||||
if (sx.SocketErrorCode == SocketError.ConnectionReset)
|
||||
{
|
||||
// connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable"
|
||||
@@ -139,35 +191,13 @@ namespace Lidgren.Network
|
||||
if (target.Address == IPAddress.Broadcast)
|
||||
m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#else
|
||||
//
|
||||
// Release - just send the packet straight away
|
||||
//
|
||||
internal void SendPacket(int numBytes, IPEndPoint target, int numMessages, out bool connectionReset)
|
||||
private void SendCallBack(IAsyncResult res)
|
||||
{
|
||||
connectionReset = false;
|
||||
try
|
||||
{
|
||||
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.ConnectionReset)
|
||||
{
|
||||
// connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable"
|
||||
connectionReset = true;
|
||||
return;
|
||||
}
|
||||
LogError("Failed to send packet: " + sx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError("Failed to send packet: " + ex);
|
||||
}
|
||||
NetException.Assert(res.IsCompleted == true);
|
||||
m_socket.EndSendTo(res);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
194
Lidgren.Network/NetPeer.MessagePools.cs
Normal file
194
Lidgren.Network/NetPeer.MessagePools.cs
Normal file
@@ -0,0 +1,194 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetPeer
|
||||
{
|
||||
private List<byte[]> m_storagePool; // sorted smallest to largest
|
||||
private NetQueue<NetOutgoingMessage> m_outgoingMessagesPool;
|
||||
private NetQueue<NetIncomingMessage> m_incomingMessagesPool;
|
||||
|
||||
internal int m_storagePoolBytes;
|
||||
|
||||
private void InitializePools()
|
||||
{
|
||||
if (m_configuration.UseMessageRecycling)
|
||||
{
|
||||
m_storagePool = new List<byte[]>(16);
|
||||
m_outgoingMessagesPool = new NetQueue<NetOutgoingMessage>(4);
|
||||
m_incomingMessagesPool = new NetQueue<NetIncomingMessage>(4);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_storagePool = null;
|
||||
m_outgoingMessagesPool = null;
|
||||
m_incomingMessagesPool = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal byte[] GetStorage(int minimumCapacity)
|
||||
{
|
||||
if (m_storagePool == null)
|
||||
return new byte[minimumCapacity];
|
||||
|
||||
lock (m_storagePool)
|
||||
{
|
||||
for (int i = 0; i < m_storagePool.Count; i++)
|
||||
{
|
||||
byte[] retval = m_storagePool[i];
|
||||
if (retval != null && retval.Length >= minimumCapacity)
|
||||
{
|
||||
m_storagePool[i] = null;
|
||||
m_storagePoolBytes -= retval.Length;
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
}
|
||||
m_statistics.m_bytesAllocated += minimumCapacity;
|
||||
return new byte[minimumCapacity];
|
||||
}
|
||||
|
||||
internal void Recycle(byte[] storage)
|
||||
{
|
||||
if (m_storagePool == null)
|
||||
return;
|
||||
|
||||
int len = storage.Length;
|
||||
lock (m_storagePool)
|
||||
{
|
||||
for (int i = 0; i < m_storagePool.Count; i++)
|
||||
{
|
||||
if (m_storagePool[i] == null)
|
||||
{
|
||||
m_storagePoolBytes += storage.Length;
|
||||
m_storagePool[i] = storage;
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_storagePoolBytes += storage.Length;
|
||||
m_storagePool.Add(storage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new message for sending
|
||||
/// </summary>
|
||||
/// <param name="initialCapacity">initial capacity in bytes</param>
|
||||
public NetOutgoingMessage CreateMessage()
|
||||
{
|
||||
return CreateMessage(m_configuration.m_defaultOutgoingMessageCapacity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new message for sending and writes the provided string to it
|
||||
/// </summary>
|
||||
/// <param name="initialCapacity">initial capacity in bytes</param>
|
||||
public NetOutgoingMessage CreateMessage(string content)
|
||||
{
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(content);
|
||||
NetOutgoingMessage om = CreateMessage(2 + bytes.Length);
|
||||
om.WriteVariableUInt32((uint)bytes.Length);
|
||||
om.Write(bytes);
|
||||
return om;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new message for sending
|
||||
/// </summary>
|
||||
/// <param name="initialCapacity">initial capacity in bytes</param>
|
||||
public NetOutgoingMessage CreateMessage(int initialCapacity)
|
||||
{
|
||||
NetOutgoingMessage retval;
|
||||
if (m_outgoingMessagesPool == null || !m_outgoingMessagesPool.TryDequeue(out retval))
|
||||
retval = new NetOutgoingMessage();
|
||||
|
||||
byte[] storage = GetStorage(initialCapacity);
|
||||
retval.m_data = storage;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, byte[] useStorageData)
|
||||
{
|
||||
NetIncomingMessage retval;
|
||||
if (m_incomingMessagesPool == null || !m_incomingMessagesPool.TryDequeue(out retval))
|
||||
retval = new NetIncomingMessage(tp);
|
||||
else
|
||||
retval.m_incomingMessageType = tp;
|
||||
retval.m_data = useStorageData;
|
||||
return retval;
|
||||
}
|
||||
|
||||
internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, int minimumByteSize)
|
||||
{
|
||||
NetIncomingMessage retval;
|
||||
if (m_incomingMessagesPool == null || !m_incomingMessagesPool.TryDequeue(out retval))
|
||||
retval = new NetIncomingMessage(tp);
|
||||
else
|
||||
retval.m_incomingMessageType = tp;
|
||||
retval.m_data = GetStorage(minimumByteSize);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recycles a NetIncomingMessage instance for reuse; taking pressure off the garbage collector
|
||||
/// </summary>
|
||||
public void Recycle(NetIncomingMessage msg)
|
||||
{
|
||||
if (m_incomingMessagesPool == null)
|
||||
return;
|
||||
#if DEBUG
|
||||
if (m_incomingMessagesPool.Contains(msg))
|
||||
throw new NetException("Recyling already recycled message! Thread race?");
|
||||
#endif
|
||||
byte[] storage = msg.m_data;
|
||||
msg.m_data = null;
|
||||
Recycle(storage);
|
||||
msg.Reset();
|
||||
m_incomingMessagesPool.Enqueue(msg);
|
||||
}
|
||||
|
||||
internal void Recycle(NetOutgoingMessage msg)
|
||||
{
|
||||
if (m_outgoingMessagesPool == null)
|
||||
return;
|
||||
#if DEBUG
|
||||
if (m_outgoingMessagesPool.Contains(msg))
|
||||
throw new NetException("Recyling already recycled message! Thread race?");
|
||||
#endif
|
||||
|
||||
byte[] storage = msg.m_data;
|
||||
msg.m_data = null;
|
||||
|
||||
// message fragments cannot be recycled
|
||||
// TODO: find a way to recycle large message after all fragments has been acknowledged
|
||||
if (msg.m_fragmentGroup == 0)
|
||||
Recycle(storage);
|
||||
|
||||
msg.Reset();
|
||||
m_outgoingMessagesPool.Enqueue(msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an incoming message with the required capacity for releasing to the application
|
||||
/// </summary>
|
||||
internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, string text)
|
||||
{
|
||||
NetIncomingMessage retval;
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
retval = CreateIncomingMessage(tp, 1);
|
||||
retval.Write("");
|
||||
return retval;
|
||||
}
|
||||
|
||||
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(text);
|
||||
retval = CreateIncomingMessage(tp, bytes.Length + (bytes.Length > 127 ? 2 : 1));
|
||||
retval.Write(text);
|
||||
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
}
|
||||
154
Lidgren.Network/NetPeer.Send.cs
Normal file
154
Lidgren.Network/NetPeer.Send.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Net;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
public partial class NetPeer
|
||||
{
|
||||
/// <summary>
|
||||
/// Send a message to a specific connection
|
||||
/// </summary>
|
||||
/// <param name="msg">The message to send</param>
|
||||
/// <param name="recipient">The recipient connection</param>
|
||||
/// <param name="method">How to deliver the message</param>
|
||||
public NetSendResult SendMessage(NetOutgoingMessage msg, NetConnection recipient, NetDeliveryMethod method)
|
||||
{
|
||||
return SendMessage(msg, recipient, method, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to a specific connection
|
||||
/// </summary>
|
||||
/// <param name="msg">The message to send</param>
|
||||
/// <param name="recipient">The recipient connection</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, NetConnection recipient, NetDeliveryMethod method, int sequenceChannel)
|
||||
{
|
||||
if (msg == null)
|
||||
throw new ArgumentNullException("msg");
|
||||
if (recipient == null)
|
||||
throw new ArgumentNullException("recipient");
|
||||
if (method == NetDeliveryMethod.Unreliable || method == NetDeliveryMethod.ReliableUnordered)
|
||||
NetException.Assert(sequenceChannel == 0, "Delivery method " + method + " cannot use sequence channels other than 0!");
|
||||
if (msg.m_isSent)
|
||||
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)
|
||||
{
|
||||
Interlocked.Increment(ref msg.m_recyclingCount);
|
||||
return recipient.EnqueueMessage(msg, method, sequenceChannel);
|
||||
}
|
||||
else
|
||||
{
|
||||
// message must be fragmented!
|
||||
SendFragmentedMessage(msg, new NetConnection[] { recipient }, method, sequenceChannel);
|
||||
return NetSendResult.Queued; // could be different for each connection; Queued is "most true"
|
||||
}
|
||||
}
|
||||
|
||||
public void SendMessage(NetOutgoingMessage msg, IList<NetConnection> recipients, NetDeliveryMethod method, int sequenceChannel)
|
||||
{
|
||||
if (msg == null)
|
||||
throw new ArgumentNullException("msg");
|
||||
if (recipients == null)
|
||||
throw new ArgumentNullException("recipients");
|
||||
if (method == NetDeliveryMethod.Unreliable || method == NetDeliveryMethod.ReliableUnordered)
|
||||
NetException.Assert(sequenceChannel == 0, "Delivery method " + method + " cannot use sequence channels other than 0!");
|
||||
if (msg.m_isSent)
|
||||
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)
|
||||
{
|
||||
Interlocked.Add(ref msg.m_recyclingCount, recipients.Count);
|
||||
foreach (NetConnection conn in recipients)
|
||||
{
|
||||
if (conn == null)
|
||||
{
|
||||
Interlocked.Decrement(ref msg.m_recyclingCount);
|
||||
continue;
|
||||
}
|
||||
NetSendResult res = conn.EnqueueMessage(msg, method, sequenceChannel);
|
||||
if (res == NetSendResult.Dropped)
|
||||
Interlocked.Decrement(ref msg.m_recyclingCount);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// message must be fragmented!
|
||||
SendFragmentedMessage(msg, recipients, method, sequenceChannel);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to an unconnected host
|
||||
/// </summary>
|
||||
public void SendUnconnectedMessage(NetOutgoingMessage msg, string host, int port)
|
||||
{
|
||||
if (msg == null)
|
||||
throw new ArgumentNullException("msg");
|
||||
if (host == null)
|
||||
throw new ArgumentNullException("host");
|
||||
if (msg.m_isSent)
|
||||
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
|
||||
if (msg.LengthBytes > m_configuration.MaximumTransmissionUnit)
|
||||
throw new NetException("Unconnected messages too long! Must be shorter than NetConfiguration.MaximumTransmissionUnit (currently " + m_configuration.MaximumTransmissionUnit + ")");
|
||||
|
||||
IPAddress adr = NetUtility.Resolve(host);
|
||||
if (adr == null)
|
||||
throw new NetException("Failed to resolve " + host);
|
||||
|
||||
msg.m_messageType = NetMessageType.Unconnected;
|
||||
|
||||
// TODO: Interlocked.Add(ref msg.m_recyclingCount, recipients.Count); ?
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(new IPEndPoint(adr, port), msg));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to an unconnected host
|
||||
/// </summary>
|
||||
public void SendUnconnectedMessage(NetOutgoingMessage msg, IPEndPoint recipient)
|
||||
{
|
||||
if (msg == null)
|
||||
throw new ArgumentNullException("msg");
|
||||
if (recipient == null)
|
||||
throw new ArgumentNullException("recipient");
|
||||
if (msg.m_isSent)
|
||||
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
|
||||
if (msg.LengthBytes > m_configuration.MaximumTransmissionUnit)
|
||||
throw new NetException("Unconnected messages too long! Must be shorter than NetConfiguration.MaximumTransmissionUnit (currently " + m_configuration.MaximumTransmissionUnit + ")");
|
||||
|
||||
msg.m_messageType = NetMessageType.Unconnected;
|
||||
|
||||
// TODO: Interlocked.Add(ref msg.m_recyclingCount, recipients.Count); ?
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(recipient, msg));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to an unconnected host
|
||||
/// </summary>
|
||||
public void SendUnconnectedMessage(NetOutgoingMessage msg, IList<IPEndPoint> recipients)
|
||||
{
|
||||
if (msg == null)
|
||||
throw new ArgumentNullException("msg");
|
||||
if (recipients == null)
|
||||
throw new ArgumentNullException("recipients");
|
||||
if (msg.m_isSent)
|
||||
throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently");
|
||||
if (msg.LengthBytes > m_configuration.MaximumTransmissionUnit)
|
||||
throw new NetException("Unconnected messages too long! Must be shorter than NetConfiguration.MaximumTransmissionUnit (currently " + m_configuration.MaximumTransmissionUnit + ")");
|
||||
|
||||
msg.m_messageType = NetMessageType.Unconnected;
|
||||
|
||||
Interlocked.Add(ref msg.m_recyclingCount, recipients.Count);
|
||||
foreach(IPEndPoint ep in recipients)
|
||||
m_unsentUnconnectedMessages.Enqueue(new NetTuple<IPEndPoint, NetOutgoingMessage>(ep, msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,85 +1,56 @@
|
||||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
//
|
||||
// This partial file holds public netpeer methods accessible to the application
|
||||
//
|
||||
[DebuggerDisplay("Status={m_status}")]
|
||||
/// <summary>
|
||||
/// Represents a local peer capable of holding zero, one or more connections to remote peers
|
||||
/// </summary>
|
||||
public partial class NetPeer
|
||||
{
|
||||
private static int s_peerCount = 0;
|
||||
private static int s_initializedPeersCount;
|
||||
|
||||
internal const int kMinPacketHeaderSize = 2;
|
||||
internal const int kMaxPacketHeaderSize = 5;
|
||||
|
||||
private NetPeerStatus m_status;
|
||||
private readonly object m_initializeLock = new object();
|
||||
internal long m_uniqueIdentifier;
|
||||
|
||||
internal NetPeerConfiguration m_configuration;
|
||||
internal readonly NetPeerStatistics m_statistics;
|
||||
private Thread m_networkThread;
|
||||
private string m_shutdownReason;
|
||||
private string m_networkThreadName;
|
||||
private int m_listenPort;
|
||||
|
||||
internal readonly List<NetConnection> m_connections;
|
||||
private readonly Dictionary<IPEndPoint, NetConnection> m_connectionLookup;
|
||||
|
||||
private string m_shutdownReason;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status of the NetPeer
|
||||
/// </summary>
|
||||
public NetPeerStatus Status { get { return m_status; } }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the network thread for this NetPeer
|
||||
/// Signalling event which can be waited on to determine when a message is queued for reading.
|
||||
/// Note that there is no guarantee that after the event is signaled the blocked thread will
|
||||
/// find the message in the queue. Other user created threads could be preempted and dequeue
|
||||
/// the message before the waiting thread wakes up.
|
||||
/// </summary>
|
||||
public string NetworkThreadName
|
||||
{
|
||||
get { return m_networkThreadName; }
|
||||
set
|
||||
{
|
||||
if (m_networkThreadName != value)
|
||||
{
|
||||
m_networkThreadName = value;
|
||||
if (m_networkThread != null)
|
||||
m_networkThread.Name = m_networkThreadName;
|
||||
}
|
||||
}
|
||||
}
|
||||
public AutoResetEvent MessageReceivedEvent { get { return m_messageReceivedEvent; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a unique identifier for this NetPeer based on Mac address and ip/port. Note! Not available until Start has been called!
|
||||
/// </summary>
|
||||
public long UniqueIdentifier { get { return m_uniqueIdentifier; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the port number this NetPeer is listening and sending on
|
||||
/// </summary>
|
||||
public int Port { get { return m_listenPort; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a copy of the list of connections
|
||||
/// </summary>
|
||||
public NetConnection[] Connections
|
||||
public List<NetConnection> Connections
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (m_connections)
|
||||
return m_connections.ToArray();
|
||||
return new List<NetConnection>(m_connections);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,48 +75,22 @@ namespace Lidgren.Network
|
||||
/// </summary>
|
||||
public NetPeerConfiguration Configuration { get { return m_configuration; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the port number this NetPeer is listening and sending on
|
||||
/// </summary>
|
||||
public int Port { get { return m_listenPort; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a semi-unique identifier based on Mac address and ip/port. Note! Not available until Start has been called!
|
||||
/// </summary>
|
||||
public long UniqueIdentifier { get { return m_uniqueIdentifier; } }
|
||||
|
||||
public NetPeer(NetPeerConfiguration configuration)
|
||||
public NetPeer(NetPeerConfiguration config)
|
||||
{
|
||||
if (configuration == null)
|
||||
throw new ArgumentNullException("configuration");
|
||||
|
||||
m_status = NetPeerStatus.NotRunning;
|
||||
m_configuration = configuration;
|
||||
m_connections = new List<NetConnection>(m_configuration.MaximumConnections);
|
||||
m_connectionLookup = new Dictionary<IPEndPoint, NetConnection>(m_configuration.MaximumConnections);
|
||||
m_senderRemote = (EndPoint)new IPEndPoint(IPAddress.Any, 0);
|
||||
m_configuration = config;
|
||||
m_statistics = new NetPeerStatistics(this);
|
||||
|
||||
int pc = Interlocked.Increment(ref s_peerCount);
|
||||
m_networkThreadName = "Lidgren network thread " + pc.ToString();
|
||||
m_releasedIncomingMessages = new NetQueue<NetIncomingMessage>(4);
|
||||
m_unsentUnconnectedMessages = new NetQueue<NetTuple<IPEndPoint, NetOutgoingMessage>>(2);
|
||||
m_connections = new List<NetConnection>();
|
||||
m_connectionLookup = new Dictionary<IPEndPoint, NetConnection>();
|
||||
m_handshakes = new Dictionary<IPEndPoint, NetConnection>();
|
||||
m_senderRemote = (EndPoint)new IPEndPoint(IPAddress.Any, 0);
|
||||
m_status = NetPeerStatus.NotRunning;
|
||||
m_receivedFragmentGroups = new Dictionary<int, ReceivedFragmentGroup>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the connection to a remote endpoint; if it exists
|
||||
/// </summary>
|
||||
public NetConnection GetConnection(IPEndPoint remoteEndPoint)
|
||||
{
|
||||
if (remoteEndPoint == null)
|
||||
throw new ArgumentNullException("remoteEndPoint");
|
||||
|
||||
NetConnection retval;
|
||||
if (m_connectionLookup.TryGetValue(remoteEndPoint, out retval))
|
||||
return retval;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binds to socket
|
||||
/// Binds to socket and spawns networking thread
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
@@ -158,16 +103,18 @@ namespace Lidgren.Network
|
||||
|
||||
m_status = NetPeerStatus.Starting;
|
||||
|
||||
m_releasedIncomingMessages.Clear();
|
||||
m_unsentUnconnectedMessage.Clear();
|
||||
|
||||
m_configuration.VerifyAndLock();
|
||||
// fix network thread name
|
||||
if (m_configuration.NetworkThreadName == "Lidgren network thread")
|
||||
{
|
||||
int pc = Interlocked.Increment(ref s_initializedPeersCount);
|
||||
m_configuration.NetworkThreadName = "Lidgren network thread " + pc.ToString();
|
||||
}
|
||||
|
||||
InitializeNetwork();
|
||||
|
||||
|
||||
// start network thread
|
||||
m_networkThread = new Thread(new ThreadStart(NetworkLoop));
|
||||
m_networkThread.Name = m_networkThreadName;
|
||||
m_networkThread.Name = m_configuration.NetworkThreadName;
|
||||
m_networkThread.IsBackground = true;
|
||||
m_networkThread.Start();
|
||||
|
||||
@@ -175,17 +122,11 @@ namespace Lidgren.Network
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if there is a queued message available to read using ReadMessage()
|
||||
/// </summary>
|
||||
public bool MessageAvailable
|
||||
internal NetConnection GetConnection(IPEndPoint ep)
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_status == NetPeerStatus.NotRunning)
|
||||
return false;
|
||||
return (m_releasedIncomingMessages.Count > 0);
|
||||
}
|
||||
NetConnection retval;
|
||||
m_connectionLookup.TryGetValue(ep, out retval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -193,9 +134,6 @@ namespace Lidgren.Network
|
||||
/// </summary>
|
||||
public NetIncomingMessage ReadMessage()
|
||||
{
|
||||
if (m_status == NetPeerStatus.NotRunning)
|
||||
return null;
|
||||
|
||||
NetIncomingMessage retval;
|
||||
if (m_releasedIncomingMessages.TryDequeue(out retval))
|
||||
{
|
||||
@@ -207,14 +145,16 @@ namespace Lidgren.Network
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
public NetIncomingMessage WaitMessage(int maxMillis)
|
||||
|
||||
// send message immediately
|
||||
internal void SendLibrary(NetOutgoingMessage msg, IPEndPoint recipient)
|
||||
{
|
||||
if (m_messageReceivedEvent != null)
|
||||
m_messageReceivedEvent.WaitOne(maxMillis);
|
||||
NetIncomingMessage retval;
|
||||
m_releasedIncomingMessages.TryDequeue(out retval);
|
||||
return retval;
|
||||
VerifyNetworkThread();
|
||||
NetException.Assert(msg.m_isSent == false);
|
||||
|
||||
bool connReset;
|
||||
int len = msg.Encode(m_sendBuffer, 0, 0);
|
||||
SendPacket(len, recipient, 1, out connReset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -225,6 +165,14 @@ namespace Lidgren.Network
|
||||
return Connect(new IPEndPoint(NetUtility.Resolve(host), port), null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a connection to a remote endpoint
|
||||
/// </summary>
|
||||
public NetConnection Connect(string host, int port, NetOutgoingMessage hailMessage)
|
||||
{
|
||||
return Connect(new IPEndPoint(NetUtility.Resolve(host), port), hailMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a connection to a remote endpoint
|
||||
/// </summary>
|
||||
@@ -236,34 +184,7 @@ namespace Lidgren.Network
|
||||
/// <summary>
|
||||
/// Create a connection to a remote endpoint
|
||||
/// </summary>
|
||||
public NetConnection Connect(string host, int port, NetOutgoingMessage approvalMessage)
|
||||
{
|
||||
return Connect(new IPEndPoint(NetUtility.Resolve(host), port), approvalMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to create a connection to a remote endpoint; returns true on success
|
||||
/// Note that the connection attempt may still fail; the returning value only indicates that the connection procedure initiated successfully
|
||||
/// </summary>
|
||||
public bool TryConnect(IPEndPoint remoteEndpoint, NetOutgoingMessage approvalMessage, out NetConnection connection)
|
||||
{
|
||||
lock (m_connections)
|
||||
{
|
||||
if (m_status == NetPeerStatus.NotRunning || m_connectionLookup.ContainsKey(remoteEndpoint))
|
||||
{
|
||||
connection = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
connection = Connect(remoteEndpoint, approvalMessage);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a connection to a remote endpoint
|
||||
/// </summary>
|
||||
public virtual NetConnection Connect(IPEndPoint remoteEndpoint, NetOutgoingMessage approvalMessage)
|
||||
public virtual NetConnection Connect(IPEndPoint remoteEndpoint, NetOutgoingMessage hailMessage)
|
||||
{
|
||||
if (remoteEndpoint == null)
|
||||
throw new ArgumentNullException("remoteEndpoint");
|
||||
@@ -276,158 +197,49 @@ namespace Lidgren.Network
|
||||
if (m_connectionLookup.ContainsKey(remoteEndpoint))
|
||||
throw new NetException("Already connected to that endpoint!");
|
||||
|
||||
NetConnection hs;
|
||||
if (m_handshakes.TryGetValue(remoteEndpoint, out hs))
|
||||
{
|
||||
// already trying to connect to that endpoint; make another try
|
||||
switch (hs.Status)
|
||||
{
|
||||
case NetConnectionStatus.InitiatedConnect:
|
||||
// send another connect
|
||||
hs.m_connectRequested = true;
|
||||
break;
|
||||
case NetConnectionStatus.RespondedConnect:
|
||||
// send another response
|
||||
hs.SendConnectResponse(false);
|
||||
break;
|
||||
default:
|
||||
// weird
|
||||
LogWarning("Weird situation; Connect() already in progress to remote endpoint; but hs status is " + hs.Status);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
NetConnection conn = new NetConnection(this, remoteEndpoint);
|
||||
conn.m_approvalMessage = approvalMessage;
|
||||
conn.m_localHailMessage = hailMessage;
|
||||
|
||||
// handle on network thread
|
||||
conn.m_connectRequested = true;
|
||||
conn.m_connectionInitiator = true;
|
||||
|
||||
m_connections.Add(conn);
|
||||
m_connectionLookup[remoteEndpoint] = conn;
|
||||
m_handshakes.Add(remoteEndpoint, conn);
|
||||
|
||||
return conn;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to an existing connection
|
||||
/// </summary>
|
||||
public bool SendMessage(NetOutgoingMessage msg, NetConnection recipient, NetDeliveryMethod deliveryMethod)
|
||||
#if DEBUG
|
||||
public void RawSend(byte[] arr, int offset, int length, IPEndPoint destination)
|
||||
{
|
||||
return SendMessage(msg, recipient, deliveryMethod, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to an existing connection
|
||||
/// </summary>
|
||||
/// <param name="msg">The NetOutgoingMessage to send</param>
|
||||
/// <param name="recipient">The recipient connection</param>
|
||||
/// <param name="deliveryMethod">How to deliver the message</param>
|
||||
/// <param name="channel">Delivery channel (0-31)</param>
|
||||
/// <returns>True if the message was queued for delivery, else false</returns>
|
||||
public bool SendMessage(NetOutgoingMessage msg, NetConnection recipient, NetDeliveryMethod deliveryMethod, int channel)
|
||||
{
|
||||
if (msg == null)
|
||||
throw new ArgumentNullException("msg");
|
||||
if (recipient == null)
|
||||
throw new ArgumentNullException("recipient");
|
||||
|
||||
if (msg.IsSent)
|
||||
throw new NetException("Message has already been sent! To send to multiple recipients use SendMessage(... IEnumerable<NetConnection...)");
|
||||
if (channel < 0 || channel > 63)
|
||||
throw new NetException("Channel must be between 0 and 63");
|
||||
if (channel != 0 && (deliveryMethod == NetDeliveryMethod.Unreliable || deliveryMethod == NetDeliveryMethod.ReliableUnordered))
|
||||
throw new NetException("Channel must be 0 for Unreliable and ReliableUnordered");
|
||||
|
||||
if (m_status != NetPeerStatus.Running)
|
||||
return false;
|
||||
|
||||
return recipient.SendMessage(msg, deliveryMethod, channel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to a number of existing connections; returns true if all recipients were sent the message
|
||||
/// </summary>
|
||||
/// <param name="channel">Delivery channel (0-31)</param>
|
||||
public bool SendMessage(NetOutgoingMessage msg, IEnumerable<NetConnection> recipients, NetDeliveryMethod deliveryMethod, int sequenceChannel)
|
||||
{
|
||||
if (msg == null)
|
||||
throw new ArgumentNullException("msg");
|
||||
if (recipients == null)
|
||||
throw new ArgumentNullException("recipients");
|
||||
|
||||
if (msg.IsSent)
|
||||
throw new NetException("Message has already been sent!");
|
||||
if (sequenceChannel < 0 || sequenceChannel > NetConstants.NetChannelsPerDeliveryMethod)
|
||||
throw new NetException("Channel must be between 0 and " + (NetConstants.NetChannelsPerDeliveryMethod - 1));
|
||||
if (sequenceChannel != 0 && (deliveryMethod == NetDeliveryMethod.Unreliable || deliveryMethod == NetDeliveryMethod.ReliableUnordered))
|
||||
throw new NetException("Channel must be 0 for Unreliable and ReliableUnordered");
|
||||
|
||||
if (m_status != NetPeerStatus.Running)
|
||||
return false;
|
||||
|
||||
msg.m_wasSent = true;
|
||||
|
||||
NetMessageType tp = (NetMessageType)((int)deliveryMethod + sequenceChannel);
|
||||
|
||||
bool all = true;
|
||||
foreach (NetConnection conn in recipients)
|
||||
{
|
||||
if (!conn.EnqueueSendMessage(msg, tp))
|
||||
all = false;
|
||||
}
|
||||
|
||||
return all;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to an unconnected host
|
||||
/// </summary>
|
||||
public void SendUnconnectedMessage(NetOutgoingMessage msg, string host, int port)
|
||||
{
|
||||
if (msg == null)
|
||||
throw new ArgumentNullException("msg");
|
||||
if (host == null)
|
||||
throw new ArgumentNullException("host");
|
||||
|
||||
IPAddress adr = NetUtility.Resolve(host);
|
||||
if (adr == null)
|
||||
throw new NetException("Failed to resolve " + host);
|
||||
|
||||
SendUnconnectedMessage(msg, new IPEndPoint(adr, port));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to an unconnected host
|
||||
/// </summary>
|
||||
public void SendUnconnectedMessage(NetOutgoingMessage msg, IPEndPoint recipient)
|
||||
{
|
||||
if (msg == null)
|
||||
throw new ArgumentNullException("msg");
|
||||
if (recipient == null)
|
||||
throw new ArgumentNullException("recipient");
|
||||
|
||||
if (msg.IsSent)
|
||||
throw new NetException("Message has already been sent!");
|
||||
msg.m_wasSent = true;
|
||||
|
||||
EnqueueUnconnectedMessage(msg, recipient);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to a number of unconnected hosts
|
||||
/// </summary>
|
||||
public void SendUnconnectedMessage(NetOutgoingMessage msg, IEnumerable<IPEndPoint> recipients)
|
||||
{
|
||||
if (msg == null)
|
||||
throw new ArgumentNullException("msg");
|
||||
if (recipients == null)
|
||||
throw new ArgumentNullException("recipients");
|
||||
if (msg.IsSent)
|
||||
throw new NetException("Message has already been sent!");
|
||||
msg.m_wasSent = true;
|
||||
|
||||
foreach (IPEndPoint rec in recipients)
|
||||
EnqueueUnconnectedMessage(msg, rec);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a discovery response message
|
||||
/// </summary>
|
||||
public void SendDiscoveryResponse(NetOutgoingMessage msg, IPEndPoint recipient)
|
||||
{
|
||||
if (recipient == null)
|
||||
throw new ArgumentNullException("recipient");
|
||||
|
||||
if (msg == null)
|
||||
msg = CreateMessage(0);
|
||||
else if (msg.IsSent)
|
||||
throw new NetException("Message has already been sent!");
|
||||
|
||||
msg.m_libType = NetMessageLibraryType.DiscoveryResponse;
|
||||
SendUnconnectedLibrary(msg, recipient);
|
||||
// wrong thread - this miiiight crash with network thread... but what's a boy to do.
|
||||
Array.Copy(arr, offset, m_sendBuffer, 0, length);
|
||||
bool unused;
|
||||
SendPacket(length, destination, 1, out unused);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects all active connections and closes the socket
|
||||
@@ -435,7 +247,6 @@ namespace Lidgren.Network
|
||||
public void Shutdown(string bye)
|
||||
{
|
||||
// called on user thread
|
||||
|
||||
if (m_socket == null)
|
||||
return; // already shut down
|
||||
|
||||
@@ -443,12 +254,5 @@ namespace Lidgren.Network
|
||||
m_shutdownReason = bye;
|
||||
m_status = NetPeerStatus.ShutdownRequested;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (m_socket == null)
|
||||
return "[NetPeer unbound]";
|
||||
return "[NetPeer bound to " + m_socket.LocalEndPoint + " " + ConnectionsCount + " connections]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,28 +30,21 @@ namespace Lidgren.Network
|
||||
private const string c_isLockedMessage = "You may not modify the NetPeerConfiguration after it has been used to initialize a NetPeer";
|
||||
|
||||
private bool m_isLocked;
|
||||
private readonly string m_appIdentifier;
|
||||
private string m_networkThreadName;
|
||||
private IPAddress m_localAddress;
|
||||
internal bool m_acceptIncomingConnections;
|
||||
internal string m_appIdentifier;
|
||||
internal IPAddress m_localAddress;
|
||||
internal int m_port;
|
||||
internal int m_receiveBufferSize, m_sendBufferSize;
|
||||
internal int m_defaultOutgoingMessageCapacity;
|
||||
internal int m_maximumTransmissionUnit;
|
||||
internal bool m_useMessageCoalescing;
|
||||
internal int m_maximumConnections;
|
||||
internal NetIncomingMessageType m_disabledTypes;
|
||||
internal int m_throttleBytesPerSecond;
|
||||
internal int m_throttlePeakBytes;
|
||||
internal int m_maxRecycledBytesKept;
|
||||
|
||||
// handshake, timeout and keepalive
|
||||
internal float m_handshakeAttemptDelay;
|
||||
internal int m_handshakeMaxAttempts;
|
||||
internal int m_maximumTransmissionUnit;
|
||||
internal int m_defaultOutgoingMessageCapacity;
|
||||
internal float m_pingInterval;
|
||||
internal bool m_useMessageRecycling;
|
||||
internal float m_connectionTimeout;
|
||||
internal float m_pingFrequency;
|
||||
|
||||
// reliability
|
||||
internal float m_maxAckDelayTime;
|
||||
internal NetIncomingMessageType m_disabledTypes;
|
||||
internal int m_port;
|
||||
internal int m_receiveBufferSize;
|
||||
internal int m_sendBufferSize;
|
||||
|
||||
// bad network simulation
|
||||
internal float m_loss;
|
||||
@@ -65,108 +58,47 @@ namespace Lidgren.Network
|
||||
throw new NetException("App identifier must be at least one character long");
|
||||
m_appIdentifier = appIdentifier.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||
|
||||
// defaults
|
||||
m_isLocked = false;
|
||||
m_acceptIncomingConnections = true;
|
||||
//
|
||||
// default values
|
||||
//
|
||||
m_disabledTypes = NetIncomingMessageType.ConnectionApproval | NetIncomingMessageType.UnconnectedData | NetIncomingMessageType.VerboseDebugMessage;
|
||||
m_networkThreadName = "Lidgren network thread";
|
||||
m_localAddress = IPAddress.Any;
|
||||
m_port = 0;
|
||||
m_receiveBufferSize = 131071;
|
||||
m_sendBufferSize = 131071;
|
||||
m_connectionTimeout = 25;
|
||||
m_maximumConnections = 16;
|
||||
m_defaultOutgoingMessageCapacity = 8;
|
||||
m_pingFrequency = 6.0f;
|
||||
m_throttleBytesPerSecond = 1024 * 256;
|
||||
m_throttlePeakBytes = 8192;
|
||||
m_maxAckDelayTime = 0.01f;
|
||||
m_handshakeAttemptDelay = 1.0f;
|
||||
m_handshakeMaxAttempts = 7;
|
||||
m_maxRecycledBytesKept = 128 * 1024;
|
||||
m_useMessageCoalescing = true;
|
||||
m_acceptIncomingConnections = false;
|
||||
m_maximumConnections = 32;
|
||||
m_defaultOutgoingMessageCapacity = 16;
|
||||
m_pingInterval = 3.0f;
|
||||
m_connectionTimeout = 25.0f;
|
||||
m_useMessageRecycling = true;
|
||||
|
||||
// Maximum transmission unit
|
||||
// Ethernet can take 1500 bytes of payload, so lets stay below that.
|
||||
// The aim is for a max full packet to be 1440 bytes (30 x 48 bytes, lower than 1468)
|
||||
// -20 bytes IP header
|
||||
// -8 bytes UDP header
|
||||
// -4 bytes to be on the safe side and align to 8-byte boundary
|
||||
// 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_loss = 0.0f;
|
||||
m_minimumOneWayLatency = 0.0f;
|
||||
m_randomOneWayLatency = 0.0f;
|
||||
m_duplicates = 0.0f;
|
||||
|
||||
// default disabled types
|
||||
m_disabledTypes = NetIncomingMessageType.ConnectionApproval | NetIncomingMessageType.UnconnectedData | NetIncomingMessageType.VerboseDebugMessage;
|
||||
|
||||
// Maximum transmission unit
|
||||
// Ethernet can take 1500 bytes of payload, so lets stay below that.
|
||||
// The aim is for a max full packet to be 1440 bytes (30 x 48 bytes, lower than 1468)
|
||||
// 20 bytes IP header
|
||||
// 8 bytes UDP header
|
||||
// 5 bytes lidgren header for one message
|
||||
// 1 byte just to be on the safe side
|
||||
// Totals 1440 minus 34 = 1406 bytes free for payload
|
||||
m_maximumTransmissionUnit = 1406;
|
||||
m_isLocked = false;
|
||||
}
|
||||
|
||||
public NetPeerConfiguration Clone()
|
||||
internal void Lock()
|
||||
{
|
||||
NetPeerConfiguration retval = this.MemberwiseClone() as NetPeerConfiguration;
|
||||
retval.m_isLocked = false;
|
||||
return retval;
|
||||
}
|
||||
|
||||
internal void VerifyAndLock()
|
||||
{
|
||||
if (m_throttleBytesPerSecond != 0 && m_throttleBytesPerSecond < m_maximumTransmissionUnit)
|
||||
m_throttleBytesPerSecond = m_maximumTransmissionUnit;
|
||||
|
||||
m_isLocked = true;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
/// <summary>
|
||||
/// Gets or sets the simulated amount of sent packets lost from 0.0f to 1.0f
|
||||
/// </summary>
|
||||
public float SimulatedLoss
|
||||
{
|
||||
get { return m_loss; }
|
||||
set { m_loss = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum simulated amount of one way latency for sent packets in seconds
|
||||
/// </summary>
|
||||
public float SimulatedMinimumLatency
|
||||
{
|
||||
get { return m_minimumOneWayLatency; }
|
||||
set { m_minimumOneWayLatency = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the simulated added random amount of one way latency for sent packets in seconds
|
||||
/// </summary>
|
||||
public float SimulatedRandomLatency
|
||||
{
|
||||
get { return m_randomOneWayLatency; }
|
||||
set { m_randomOneWayLatency = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the average simulated one way latency in seconds
|
||||
/// </summary>
|
||||
public float SimulatedAverageLatency
|
||||
{
|
||||
get { return m_minimumOneWayLatency + (m_randomOneWayLatency * 0.5f); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the simulated amount of duplicated packets from 0.0f to 1.0f
|
||||
/// </summary>
|
||||
public float SimulatedDuplicatesChance
|
||||
{
|
||||
get { return m_duplicates; }
|
||||
set { m_duplicates = value; }
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of this application; the library can only connect to matching app identifier peers
|
||||
/// Gets the identifier of this application; the library can only connect to matching app identifier peers
|
||||
/// </summary>
|
||||
public string AppIdentifier
|
||||
{
|
||||
@@ -209,28 +141,19 @@ namespace Lidgren.Network
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum amount of bytes to send in a single packet, excluding ip, udp and lidgren headers
|
||||
/// Gets or sets the name of the library network thread. Cannot be changed once NetPeer is initialized.
|
||||
/// </summary>
|
||||
public int MaximumTransmissionUnit
|
||||
public string NetworkThreadName
|
||||
{
|
||||
get { return m_maximumTransmissionUnit; }
|
||||
get { return m_networkThreadName; }
|
||||
set
|
||||
{
|
||||
if (value < 1 || value >= 4096)
|
||||
throw new NetException("MaximumTransmissionUnit must be between 1 and 4095 bytes");
|
||||
m_maximumTransmissionUnit = value;
|
||||
if (m_isLocked)
|
||||
throw new NetException("NetworkThreadName may not be set after the NetPeer which uses the configuration has been started");
|
||||
m_networkThreadName = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if message coalescing (sending multiple messages in a single packet) should be used. Normally this should be true.
|
||||
/// </summary>
|
||||
public bool UseMessageCoalescing
|
||||
{
|
||||
get { return m_useMessageCoalescing; }
|
||||
set { m_useMessageCoalescing = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum amount of connections this peer can hold. Cannot be changed once NetPeer is initialized.
|
||||
/// </summary>
|
||||
@@ -246,12 +169,17 @@ namespace Lidgren.Network
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if the NetPeer should accept incoming connections. This is automatically set to true in NetServer and false in NetClient.
|
||||
/// Gets or sets the maximum amount of bytes to send in a single packet, excluding ip, udp and lidgren headers
|
||||
/// </summary>
|
||||
public bool AcceptIncomingConnections
|
||||
public int MaximumTransmissionUnit
|
||||
{
|
||||
get { return m_acceptIncomingConnections; }
|
||||
set { m_acceptIncomingConnections = value; }
|
||||
get { return m_maximumTransmissionUnit; }
|
||||
set
|
||||
{
|
||||
if (value < 1 || value >= ((ushort.MaxValue + 1) / 8))
|
||||
throw new NetException("MaximumTransmissionUnit must be between 1 and " + (((ushort.MaxValue + 1) / 8) - 1) + " bytes");
|
||||
m_maximumTransmissionUnit = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -263,6 +191,43 @@ namespace Lidgren.Network
|
||||
set { m_defaultOutgoingMessageCapacity = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the time between latency calculating pings
|
||||
/// </summary>
|
||||
public float PingInterval
|
||||
{
|
||||
get { return m_pingInterval; }
|
||||
set { m_pingInterval = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if the library should recycling messages to avoid excessive garbage collection. Cannot be changed once NetPeer is initialized.
|
||||
/// </summary>
|
||||
public bool UseMessageRecycling
|
||||
{
|
||||
get { return m_useMessageRecycling; }
|
||||
set
|
||||
{
|
||||
if (m_isLocked)
|
||||
throw new NetException(c_isLockedMessage);
|
||||
m_useMessageRecycling = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of seconds timeout will be postponed on a successful ping/pong
|
||||
/// </summary>
|
||||
public float ConnectionTimeout
|
||||
{
|
||||
get { return m_connectionTimeout; }
|
||||
set
|
||||
{
|
||||
if (value < m_pingInterval)
|
||||
throw new NetException("Connection timeout cannot be lower than ping interval!");
|
||||
m_connectionTimeout = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the local ip address to bind to. Defaults to IPAddress.Any. Cannot be changed once NetPeer is initialized.
|
||||
/// </summary>
|
||||
@@ -320,86 +285,65 @@ namespace Lidgren.Network
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of seconds of non-response before disconnecting because of time out. Cannot be changed once NetPeer is initialized.
|
||||
/// Gets or sets if the NetPeer should accept incoming connections. This is automatically set to true in NetServer and false in NetClient.
|
||||
/// </summary>
|
||||
public float ConnectionTimeout
|
||||
public bool AcceptIncomingConnections
|
||||
{
|
||||
get { return m_connectionTimeout; }
|
||||
set
|
||||
{
|
||||
if (m_isLocked)
|
||||
throw new NetException(c_isLockedMessage);
|
||||
m_connectionTimeout = value;
|
||||
}
|
||||
get { return m_acceptIncomingConnections; }
|
||||
set { m_acceptIncomingConnections = value; }
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
/// <summary>
|
||||
/// Gets or sets the simulated amount of sent packets lost from 0.0f to 1.0f
|
||||
/// </summary>
|
||||
public float SimulatedLoss
|
||||
{
|
||||
get { return m_loss; }
|
||||
set { m_loss = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of seconds between latency calculation (rtt) pings
|
||||
/// Gets or sets the minimum simulated amount of one way latency for sent packets in seconds
|
||||
/// </summary>
|
||||
public float PingFrequency
|
||||
public float SimulatedMinimumLatency
|
||||
{
|
||||
get { return m_pingFrequency; }
|
||||
set { m_pingFrequency = value; }
|
||||
get { return m_minimumOneWayLatency; }
|
||||
set { m_minimumOneWayLatency = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number of allowed bytes to be sent per second per connection; 0 means unlimited (throttling disabled)
|
||||
/// Gets or sets the simulated added random amount of one way latency for sent packets in seconds
|
||||
/// </summary>
|
||||
public int ThrottleBytesPerSecond
|
||||
public float SimulatedRandomLatency
|
||||
{
|
||||
get { return m_throttleBytesPerSecond; }
|
||||
set
|
||||
{
|
||||
if (m_throttleBytesPerSecond != 0 && m_throttleBytesPerSecond < m_maximumTransmissionUnit)
|
||||
throw new NetException("ThrottleBytesPerSecond can not be lower than MaximumTransmissionUnit");
|
||||
m_throttleBytesPerSecond = value;
|
||||
}
|
||||
get { return m_randomOneWayLatency; }
|
||||
set { m_randomOneWayLatency = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the peak number of bytes sent before throttling kicks in, if enabled
|
||||
/// Gets the average simulated one way latency in seconds
|
||||
/// </summary>
|
||||
public int ThrottlePeakBytes
|
||||
public float SimulatedAverageLatency
|
||||
{
|
||||
get { return m_throttlePeakBytes; }
|
||||
set
|
||||
{
|
||||
if (m_throttlePeakBytes < m_maximumTransmissionUnit)
|
||||
throw new NetException("ThrottlePeakBytes can not be lower than MaximumTransmissionUnit");
|
||||
m_throttlePeakBytes = value;
|
||||
}
|
||||
get { return m_minimumOneWayLatency + (m_randomOneWayLatency * 0.5f); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the number between handshake attempts in seconds
|
||||
/// Gets or sets the simulated amount of duplicated packets from 0.0f to 1.0f
|
||||
/// </summary>
|
||||
public float HandshakeAttemptDelay
|
||||
public float SimulatedDuplicatesChance
|
||||
{
|
||||
get { return m_handshakeAttemptDelay; }
|
||||
set { m_handshakeAttemptDelay = value; }
|
||||
get { return m_duplicates; }
|
||||
set { m_duplicates = value; }
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of handshake attempts before declaring failure to shake hands
|
||||
/// </summary>
|
||||
public int HandshakeMaxAttempts
|
||||
public NetPeerConfiguration Clone()
|
||||
{
|
||||
get { return m_handshakeMaxAttempts; }
|
||||
set { m_handshakeMaxAttempts = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of bytes kept in the recycle pool. Cannot be changed once NetPeer is initialized.
|
||||
/// </summary>
|
||||
public int MaxRecycledBytesKept
|
||||
{
|
||||
get { return m_maxRecycledBytesKept; }
|
||||
set
|
||||
{
|
||||
if (m_isLocked)
|
||||
throw new NetException(c_isLockedMessage);
|
||||
m_maxRecycledBytesKept = value;
|
||||
}
|
||||
NetPeerConfiguration retval = this.MemberwiseClone() as NetPeerConfiguration;
|
||||
retval.m_isLocked = false;
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ using System.Diagnostics;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Statistics for a NetPeer instance
|
||||
/// </summary>
|
||||
public sealed class NetPeerStatistics
|
||||
{
|
||||
private readonly NetPeer m_peer;
|
||||
@@ -96,7 +99,7 @@ namespace Lidgren.Network
|
||||
/// <summary>
|
||||
/// Gets the number of bytes in the recycled pool
|
||||
/// </summary>
|
||||
public int BytesInRecyclePool { get { return m_peer.m_storedBytes; } }
|
||||
public int BytesInRecyclePool { get { return m_peer.m_storagePoolBytes; } }
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
internal void PacketSent(int numBytes, int numMessages)
|
||||
@@ -113,7 +116,7 @@ namespace Lidgren.Network
|
||||
m_receivedBytes += numBytes;
|
||||
m_receivedMessages += numMessages;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder bdr = new StringBuilder();
|
||||
@@ -121,7 +124,7 @@ namespace Lidgren.Network
|
||||
bdr.AppendLine("Sent " + m_sentBytes + " bytes in " + m_sentMessages + " messages in " + m_sentPackets + " packets");
|
||||
bdr.AppendLine("Received " + m_receivedBytes + " bytes in " + m_receivedMessages + " messages in " + m_receivedPackets + " packets");
|
||||
bdr.AppendLine("Allocated " + m_bytesAllocated + " bytes");
|
||||
bdr.AppendLine("Recycled pool " + m_peer.m_storedBytes + " bytes");
|
||||
bdr.AppendLine("Recycled pool " + m_peer.m_storagePoolBytes + " bytes");
|
||||
return bdr.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Status for a NetPeer instance
|
||||
/// </summary>
|
||||
public enum NetPeerStatus
|
||||
{
|
||||
NotRunning = 0,
|
||||
|
||||
@@ -62,20 +62,11 @@ namespace Lidgren.Network
|
||||
/// </summary>
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
#if DEBUG
|
||||
if (typeof(T) == typeof(NetSending))
|
||||
{
|
||||
NetSending om = item as NetSending;
|
||||
if (om != null)
|
||||
if (om.MessageType == NetMessageType.Error)
|
||||
throw new NetException("Enqueuing NetSending with MessageType.Error!");
|
||||
}
|
||||
#endif
|
||||
lock (m_lock)
|
||||
{
|
||||
if (m_size == m_items.Length)
|
||||
SetCapacity(m_items.Length + 8);
|
||||
|
||||
|
||||
int slot = (m_head + m_size) % m_items.Length;
|
||||
m_items[slot] = item;
|
||||
m_size++;
|
||||
@@ -91,7 +82,7 @@ namespace Lidgren.Network
|
||||
{
|
||||
if (m_size >= m_items.Length)
|
||||
SetCapacity(m_items.Length + 8);
|
||||
|
||||
|
||||
m_head--;
|
||||
if (m_head < 0)
|
||||
m_head = m_items.Length - 1;
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// A fast random number generator for .NET
|
||||
/// Colin Green, January 2005
|
||||
///
|
||||
/// </summary>
|
||||
/// September 4th 2005
|
||||
/// Added NextBytesUnsafe() - commented out by default.
|
||||
/// Fixed bug in Reinitialise() - y,z and w variables were not being reset.
|
||||
@@ -21,7 +19,7 @@ namespace Lidgren.Network
|
||||
/// how this can be easily extened if you need a longer period. At the time of writing I could find no
|
||||
/// information on the period of System.Random for comparison.
|
||||
///
|
||||
/// 2) Faster than System.Random. Up to 15x faster, depending on which methods are called.
|
||||
/// 2) Faster than System.Random. Up to 8x faster, depending on which methods are called.
|
||||
///
|
||||
/// 3) Direct replacement for System.Random. This class implements all of the methods that System.Random
|
||||
/// does plus some additional methods. The like named methods are functionally equivalent.
|
||||
@@ -37,49 +35,28 @@ namespace Lidgren.Network
|
||||
/// A further performance improvement can be obtained by declaring local variables as static, thus avoiding
|
||||
/// re-allocation of variables on each call. However care should be taken if multiple instances of
|
||||
/// FastRandom are in use or if being used in a multi-threaded environment.
|
||||
///
|
||||
/// </summary>
|
||||
public sealed class NetRandom : Random
|
||||
public class NetRandom
|
||||
{
|
||||
public static NetRandom Instance = new NetRandom();
|
||||
|
||||
protected override double Sample()
|
||||
{
|
||||
return NextDouble();
|
||||
}
|
||||
public static readonly NetRandom Instance = new NetRandom();
|
||||
|
||||
// The +1 ensures NextDouble doesn't generate 1.0
|
||||
private const double c_realUnitInt = 1.0 / ((double)int.MaxValue + 1.0);
|
||||
private const double c_realUnitUint = 1.0 / ((double)uint.MaxValue + 1.0);
|
||||
private const uint c_y = 842502087, c_z = 3579807591, c_w = 273326509;
|
||||
const double REAL_UNIT_INT = 1.0 / ((double)int.MaxValue + 1.0);
|
||||
const double REAL_UNIT_UINT = 1.0 / ((double)uint.MaxValue + 1.0);
|
||||
const uint Y = 842502087, Z = 3579807591, W = 273326509;
|
||||
|
||||
private static int s_extraSeed = 42;
|
||||
|
||||
uint m_x, m_y, m_z, m_w;
|
||||
uint x, y, z, w;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random seed based on time and working set
|
||||
/// </summary>
|
||||
public static int GetRandomSeed(object forObject)
|
||||
{
|
||||
// mix some semi-random properties
|
||||
int seed = (int)Environment.TickCount;
|
||||
seed ^= forObject.GetHashCode();
|
||||
seed ^= (int)(Stopwatch.GetTimestamp());
|
||||
seed ^= (int)(Environment.WorkingSet); // will return 0 on mono
|
||||
|
||||
int extraSeed = Interlocked.Increment(ref s_extraSeed);
|
||||
|
||||
return seed + extraSeed;
|
||||
}
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Initialises a new instance using time dependent seed.
|
||||
/// </summary>
|
||||
public NetRandom()
|
||||
{
|
||||
// Initialise using the system tick count
|
||||
Reinitialise(GetRandomSeed(this));
|
||||
// Initialise using the system tick count.
|
||||
Reinitialise(GetSeed(this));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -92,6 +69,23 @@ namespace Lidgren.Network
|
||||
Reinitialise(seed);
|
||||
}
|
||||
|
||||
public int GetSeed(object forObject)
|
||||
{
|
||||
// mix some semi-random properties
|
||||
int seed = (int)Environment.TickCount;
|
||||
seed ^= forObject.GetHashCode();
|
||||
//seed ^= (int)(Stopwatch.GetTimestamp());
|
||||
//seed ^= (int)(Environment.WorkingSet); // will return 0 on mono
|
||||
|
||||
int extraSeed = System.Threading.Interlocked.Increment(ref s_extraSeed);
|
||||
|
||||
return seed + extraSeed;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods [Reinitialisation]
|
||||
|
||||
/// <summary>
|
||||
/// Reinitialises using an int value as a seed.
|
||||
/// </summary>
|
||||
@@ -101,88 +95,97 @@ namespace Lidgren.Network
|
||||
// The only stipulation stated for the xorshift RNG is that at least one of
|
||||
// the seeds x,y,z,w is non-zero. We fulfill that requirement by only allowing
|
||||
// resetting of the x seed
|
||||
m_x = (uint)seed;
|
||||
m_y = c_y;
|
||||
m_z = c_z;
|
||||
m_w = c_w;
|
||||
x = (uint)seed;
|
||||
y = Y;
|
||||
z = Z;
|
||||
w = W;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a uint. Values returned are over the full range of a uint,
|
||||
/// uint.MinValue to uint.MaxValue, including the min and max values.
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public uint NextUInt()
|
||||
{
|
||||
uint t = (m_x ^ (m_x << 11));
|
||||
m_x = m_y; m_y = m_z; m_z = m_w;
|
||||
return (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8)));
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Methods [System.Random functionally equivalent methods]
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random int. Values returned are over the range 0 to int.MaxValue-1.
|
||||
/// MaxValue is not generated to remain functionally equivalent to System.Random.Next().
|
||||
/// If you require an int from the full range, including negative values then call
|
||||
/// NextUint() and cast the value to an int.
|
||||
/// Generates a random int over the range 0 to int.MaxValue-1.
|
||||
/// MaxValue is not generated in order to remain functionally equivalent to System.Random.Next().
|
||||
/// This does slightly eat into some of the performance gain over System.Random, but not much.
|
||||
/// For better performance see:
|
||||
///
|
||||
/// Call NextInt() for an int over the range 0 to int.MaxValue.
|
||||
///
|
||||
/// Call NextUInt() and cast the result to an int to generate an int over the full Int32 value range
|
||||
/// including negative values.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int Next()
|
||||
public int Next()
|
||||
{
|
||||
uint t = (m_x ^ (m_x << 11));
|
||||
m_x = m_y; m_y = m_z; m_z = m_w;
|
||||
return (int)(0x7FFFFFFF & (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8))));
|
||||
uint t = (x ^ (x << 11));
|
||||
x = y; y = z; z = w;
|
||||
w = (w ^ (w >> 19)) ^ (t ^ (t >> 8));
|
||||
|
||||
// Handle the special case where the value int.MaxValue is generated. This is outside of
|
||||
// the range of permitted values, so we therefore call Next() to try again.
|
||||
uint rtn = w & 0x7FFFFFFF;
|
||||
if (rtn == 0x7FFFFFFF)
|
||||
return Next();
|
||||
return (int)rtn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random int over the range 0 to upperBound-1, and not including upperBound.
|
||||
/// </summary>
|
||||
public override int Next(int maxValue)
|
||||
/// <param name="upperBound"></param>
|
||||
/// <returns></returns>
|
||||
public int Next(int upperBound)
|
||||
{
|
||||
if (maxValue < 0)
|
||||
throw new ArgumentOutOfRangeException("maxValue", maxValue, "maxValue must be >=0");
|
||||
if (upperBound < 0)
|
||||
throw new ArgumentOutOfRangeException("upperBound", upperBound, "upperBound must be >=0");
|
||||
|
||||
uint t = (m_x ^ (m_x << 11));
|
||||
m_x = m_y; m_y = m_z; m_z = m_w;
|
||||
uint t = (x ^ (x << 11));
|
||||
x = y; y = z; z = w;
|
||||
|
||||
// The explicit int cast before the first multiplication gives better performance.
|
||||
// See comments in NextDouble.
|
||||
return (int)((c_realUnitInt * (int)(0x7FFFFFFF & (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8))))) * maxValue);
|
||||
return (int)((REAL_UNIT_INT * (int)(0x7FFFFFFF & (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8))))) * upperBound);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random int over the range minValue to maxValue-1, and not including maxValue.
|
||||
/// maxValue must be >= minValue. minValue may be negative.
|
||||
/// Generates a random int over the range lowerBound to upperBound-1, and not including upperBound.
|
||||
/// upperBound must be >= lowerBound. lowerBound may be negative.
|
||||
/// </summary>
|
||||
public override int Next(int minValue, int maxValue)
|
||||
/// <param name="lowerBound"></param>
|
||||
/// <param name="upperBound"></param>
|
||||
/// <returns></returns>
|
||||
public int Next(int lowerBound, int upperBound)
|
||||
{
|
||||
if (minValue > maxValue)
|
||||
throw new ArgumentOutOfRangeException("maxValue", maxValue, "maxValue must be >=minValue");
|
||||
if (lowerBound > upperBound)
|
||||
throw new ArgumentOutOfRangeException("upperBound", upperBound, "upperBound must be >=lowerBound");
|
||||
|
||||
uint t = (m_x ^ (m_x << 11));
|
||||
m_x = m_y; m_y = m_z; m_z = m_w;
|
||||
uint t = (x ^ (x << 11));
|
||||
x = y; y = z; z = w;
|
||||
|
||||
// The explicit int cast before the first multiplication gives better performance.
|
||||
// See comments in NextDouble.
|
||||
int range = maxValue - minValue;
|
||||
int range = upperBound - lowerBound;
|
||||
if (range < 0)
|
||||
{ // If range is <0 then an overflow has occured and must resort to using long integer arithmetic instead (slower).
|
||||
// We also must use all 32 bits of precision, instead of the normal 31, which again is slower.
|
||||
return minValue + (int)((c_realUnitUint * (double)(m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8)))) * (double)((long)maxValue - (long)minValue));
|
||||
return lowerBound + (int)((REAL_UNIT_UINT * (double)(w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)))) * (double)((long)upperBound - (long)lowerBound));
|
||||
}
|
||||
|
||||
// 31 bits of precision will suffice if range<=int.MaxValue. This allows us to cast to an int anf gain
|
||||
// 31 bits of precision will suffice if range<=int.MaxValue. This allows us to cast to an int and gain
|
||||
// a little more performance.
|
||||
return minValue + (int)((c_realUnitInt * (double)(int)(0x7FFFFFFF & (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8))))) * (double)range);
|
||||
return lowerBound + (int)((REAL_UNIT_INT * (double)(int)(0x7FFFFFFF & (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8))))) * (double)range);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random double. Values returned are from 0.0 up to but not including 1.0.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override double NextDouble()
|
||||
public double NextDouble()
|
||||
{
|
||||
uint t = (m_x ^ (m_x << 11));
|
||||
m_x = m_y; m_y = m_z; m_z = m_w;
|
||||
uint t = (x ^ (x << 11));
|
||||
x = y; y = z; z = w;
|
||||
|
||||
// Here we can gain a 2x speed improvement by generating a value that can be cast to
|
||||
// an int instead of the more easily available uint. If we then explicitly cast to an
|
||||
@@ -190,117 +193,45 @@ namespace Lidgren.Network
|
||||
// this final cast is a lot faster than casting from a uint to a double. The extra cast
|
||||
// to an int is very fast (the allocated bits remain the same) and so the overall effect
|
||||
// of the extra cast is a significant performance improvement.
|
||||
return (c_realUnitInt * (int)(0x7FFFFFFF & (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8)))));
|
||||
//
|
||||
// Also note that the loss of one bit of precision is equivalent to what occurs within
|
||||
// System.Random.
|
||||
return (REAL_UNIT_INT * (int)(0x7FFFFFFF & (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)))));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random double. Values returned are from 0.0 up to but not including 1.0.
|
||||
/// Generates a random single. Values returned are from 0.0 up to but not including 1.0.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public float NextFloat()
|
||||
public float NextSingle()
|
||||
{
|
||||
uint t = (m_x ^ (m_x << 11));
|
||||
m_x = m_y; m_y = m_z; m_z = m_w;
|
||||
|
||||
// Here we can gain a 2x speed improvement by generating a value that can be cast to
|
||||
// an int instead of the more easily available uint. If we then explicitly cast to an
|
||||
// int the compiler will then cast the int to a double to perform the multiplication,
|
||||
// this final cast is a lot faster than casting from a uint to a double. The extra cast
|
||||
// to an int is very fast (the allocated bits remain the same) and so the overall effect
|
||||
// of the extra cast is a significant performance improvement.
|
||||
return (float)(c_realUnitInt * (int)(0x7FFFFFFF & (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8)))));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random double. Values returned are from 0.0 up to but not including roof
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public float NextFloat(float roof)
|
||||
{
|
||||
uint t = (m_x ^ (m_x << 11));
|
||||
m_x = m_y; m_y = m_z; m_z = m_w;
|
||||
|
||||
// Here we can gain a 2x speed improvement by generating a value that can be cast to
|
||||
// an int instead of the more easily available uint. If we then explicitly cast to an
|
||||
// int the compiler will then cast the int to a double to perform the multiplication,
|
||||
// this final cast is a lot faster than casting from a uint to a double. The extra cast
|
||||
// to an int is very fast (the allocated bits remain the same) and so the overall effect
|
||||
// of the extra cast is a significant performance improvement.
|
||||
float f = (float)(c_realUnitInt * (int)(0x7FFFFFFF & (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8)))));
|
||||
|
||||
return f * roof;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random double. Values returned are from min up to but not including min + variance
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public float NextFloat(float min, float variance)
|
||||
{
|
||||
uint t = (m_x ^ (m_x << 11));
|
||||
m_x = m_y; m_y = m_z; m_z = m_w;
|
||||
|
||||
// Here we can gain a 2x speed improvement by generating a value that can be cast to
|
||||
// an int instead of the more easily available uint. If we then explicitly cast to an
|
||||
// int the compiler will then cast the int to a double to perform the multiplication,
|
||||
// this final cast is a lot faster than casting from a uint to a double. The extra cast
|
||||
// to an int is very fast (the allocated bits remain the same) and so the overall effect
|
||||
// of the extra cast is a significant performance improvement.
|
||||
float f = (float)(c_realUnitInt * (int)(0x7FFFFFFF & (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8)))));
|
||||
|
||||
return min + f * variance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If passed 0.7f it will return true 7 times out of 10
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool Chance(float percentChance)
|
||||
{
|
||||
uint t = (m_x ^ (m_x << 11));
|
||||
m_x = m_y; m_y = m_z; m_z = m_w;
|
||||
|
||||
// Here we can gain a 2x speed improvement by generating a value that can be cast to
|
||||
// an int instead of the more easily available uint. If we then explicitly cast to an
|
||||
// int the compiler will then cast the int to a double to perform the multiplication,
|
||||
// this final cast is a lot faster than casting from a uint to a double. The extra cast
|
||||
// to an int is very fast (the allocated bits remain the same) and so the overall effect
|
||||
// of the extra cast is a significant performance improvement.
|
||||
double hit = (c_realUnitInt * (int)(0x7FFFFFFF & (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8)))));
|
||||
return (hit < percentChance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a System.Single larger or equal to 0 and smaller than 1.0f - gaussian distributed!
|
||||
/// </summary>
|
||||
public float NextGaussian()
|
||||
{
|
||||
return (float)((NextDouble() + NextDouble() + NextDouble()) / 3.0);
|
||||
return (float)NextDouble();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills the provided byte array with random bytes.
|
||||
/// Increased performance is achieved by dividing and packaging bits directly from the
|
||||
/// random number generator and storing them in 4 byte 'chunks'.
|
||||
/// This method is functionally equivalent to System.Random.NextBytes().
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
public override void NextBytes(byte[] buffer)
|
||||
public void NextBytes(byte[] buffer)
|
||||
{
|
||||
// Fill up the bulk of the buffer in chunks of 4 bytes at a time.
|
||||
uint x = this.m_x, y = this.m_y, z = this.m_z, w = this.m_w;
|
||||
uint x = this.x, y = this.y, z = this.z, w = this.w;
|
||||
int i = 0;
|
||||
uint t;
|
||||
for (; i < buffer.Length - 3; )
|
||||
for (int bound = buffer.Length - 3; i < bound; )
|
||||
{
|
||||
// Generate 4 bytes.
|
||||
// Generate 4 bytes.
|
||||
// Increased performance is achieved by generating 4 random bytes per loop.
|
||||
// Also note that no mask needs to be applied to zero out the higher order bytes before
|
||||
// casting because the cast ignores thos bytes. Thanks to Stefan Troschütz for pointing this out.
|
||||
t = (x ^ (x << 11));
|
||||
x = y; y = z; z = w;
|
||||
w = (w ^ (w >> 19)) ^ (t ^ (t >> 8));
|
||||
|
||||
buffer[i++] = (byte)(w & 0x000000FF);
|
||||
buffer[i++] = (byte)((w & 0x0000FF00) >> 8);
|
||||
buffer[i++] = (byte)((w & 0x00FF0000) >> 16);
|
||||
buffer[i++] = (byte)((w & 0xFF000000) >> 24);
|
||||
buffer[i++] = (byte)w;
|
||||
buffer[i++] = (byte)(w >> 8);
|
||||
buffer[i++] = (byte)(w >> 16);
|
||||
buffer[i++] = (byte)(w >> 24);
|
||||
}
|
||||
|
||||
// Fill up any remaining bytes in the buffer.
|
||||
@@ -311,80 +242,126 @@ namespace Lidgren.Network
|
||||
x = y; y = z; z = w;
|
||||
w = (w ^ (w >> 19)) ^ (t ^ (t >> 8));
|
||||
|
||||
buffer[i++] = (byte)(w & 0x000000FF);
|
||||
buffer[i++] = (byte)w;
|
||||
if (i < buffer.Length)
|
||||
{
|
||||
buffer[i++] = (byte)((w & 0x0000FF00) >> 8);
|
||||
buffer[i++] = (byte)(w >> 8);
|
||||
if (i < buffer.Length)
|
||||
{
|
||||
buffer[i++] = (byte)((w & 0x00FF0000) >> 16);
|
||||
buffer[i++] = (byte)(w >> 16);
|
||||
if (i < buffer.Length)
|
||||
{
|
||||
buffer[i] = (byte)((w & 0xFF000000) >> 24);
|
||||
buffer[i] = (byte)(w >> 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.m_x = x; this.m_y = y; this.m_z = z; this.m_w = w;
|
||||
this.x = x; this.y = y; this.z = z; this.w = w;
|
||||
}
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// /// A version of NextBytes that uses a pointer to set 4 bytes of the byte buffer in one operation
|
||||
// /// thus providing a nice speedup. Note that this requires the unsafe compilation flag to be specified
|
||||
// /// and so is commented out by default.
|
||||
// /// thus providing a nice speedup. The loop is also partially unrolled to allow out-of-order-execution,
|
||||
// /// this results in about a x2 speedup on an AMD Athlon. Thus performance may vary wildly on different CPUs
|
||||
// /// depending on the number of execution units available.
|
||||
// ///
|
||||
// /// Another significant speedup is obtained by setting the 4 bytes by indexing pDWord (e.g. pDWord[i++]=w)
|
||||
// /// instead of adjusting it dereferencing it (e.g. *pDWord++=w).
|
||||
// ///
|
||||
// /// Note that this routine requires the unsafe compilation flag to be specified and so is commented out by default.
|
||||
// /// </summary>
|
||||
// /// <param name="buffer"></param>
|
||||
// public unsafe void NextBytesUnsafe(byte[] buffer)
|
||||
// {
|
||||
// if(buffer.Length % 4 != 0)
|
||||
// throw new ArgumentException("Buffer length must be divisible by 4", "buffer");
|
||||
// if(buffer.Length % 8 != 0)
|
||||
// throw new ArgumentException("Buffer length must be divisible by 8", "buffer");
|
||||
//
|
||||
// uint x=this.x, y=this.y, z=this.z, w=this.w;
|
||||
// uint t;
|
||||
//
|
||||
//
|
||||
// fixed(byte* pByte0 = buffer)
|
||||
// {
|
||||
// uint* pDWord = (uint*)pByte0;
|
||||
// for(int i = 0, len = buffer.Length>>2; i < len; i++)
|
||||
// for(int i=0, len=buffer.Length>>2; i < len; i+=2)
|
||||
// {
|
||||
// uint t=(x^(x<<11));
|
||||
// x=y; y=z; z=w;
|
||||
// pDWord[i] = w = (w^(w>>19))^(t^(t>>8));
|
||||
//
|
||||
// t=(x^(x<<11));
|
||||
// x=y; y=z; z=w;
|
||||
// *pDWord++ = w = (w^(w>>19))^(t^(t>>8));
|
||||
// pDWord[i+1] = w = (w^(w>>19))^(t^(t>>8));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// this.x=x; this.y=y; this.z=z; this.w=w;
|
||||
// }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods [Methods not present on System.Random]
|
||||
|
||||
/// <summary>
|
||||
/// Generates a uint. Values returned are over the full range of a uint,
|
||||
/// uint.MinValue to uint.MaxValue, inclusive.
|
||||
///
|
||||
/// This is the fastest method for generating a single random number because the underlying
|
||||
/// random number generator algorithm generates 32 random bits that can be cast directly to
|
||||
/// a uint.
|
||||
/// </summary>
|
||||
[CLSCompliant(false)]
|
||||
public uint NextUInt()
|
||||
{
|
||||
uint t = (x ^ (x << 11));
|
||||
x = y; y = z; z = w;
|
||||
return (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a random int over the range 0 to int.MaxValue, inclusive.
|
||||
/// This method differs from Next() only in that the range is 0 to int.MaxValue
|
||||
/// and not 0 to int.MaxValue-1.
|
||||
///
|
||||
/// The slight difference in range means this method is slightly faster than Next()
|
||||
/// but is not functionally equivalent to System.Random.Next().
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int NextInt()
|
||||
{
|
||||
uint t = (x ^ (x << 11));
|
||||
x = y; y = z; z = w;
|
||||
return (int)(0x7FFFFFFF & (w = (w ^ (w >> 19)) ^ (t ^ (t >> 8))));
|
||||
}
|
||||
|
||||
|
||||
// Buffer 32 bits in bitBuffer, return 1 at a time, keep track of how many have been returned
|
||||
// with bitBufferIdx.
|
||||
uint bitBuffer;
|
||||
int bitBufferIdx = 32;
|
||||
uint bitMask = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Generates random bool.
|
||||
/// Increased performance is achieved by buffering 32 random bits for
|
||||
/// future calls. Thus the random number generator is only invoked once
|
||||
/// in every 32 calls.
|
||||
/// Generates a single random bit.
|
||||
/// This method's performance is improved by generating 32 bits in one operation and storing them
|
||||
/// ready for future calls.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool NextBool()
|
||||
{
|
||||
if (bitBufferIdx == 32)
|
||||
if (bitMask == 1)
|
||||
{
|
||||
// Generate 32 more bits.
|
||||
uint t = (m_x ^ (m_x << 11));
|
||||
m_x = m_y; m_y = m_z; m_z = m_w;
|
||||
bitBuffer = m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8));
|
||||
uint t = (x ^ (x << 11));
|
||||
x = y; y = z; z = w;
|
||||
bitBuffer = w = (w ^ (w >> 19)) ^ (t ^ (t >> 8));
|
||||
|
||||
// Reset the idx that tells us which bit to read next.
|
||||
bitBufferIdx = 1;
|
||||
return (bitBuffer & 0x1) == 1;
|
||||
// Reset the bitMask that tells us which bit to read next.
|
||||
bitMask = 0x80000000;
|
||||
return (bitBuffer & bitMask) == 0;
|
||||
}
|
||||
|
||||
bitBufferIdx++;
|
||||
return ((bitBuffer >>= 1) & 0x1) == 1;
|
||||
return (bitBuffer & (bitMask >>= 1)) == 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
18
Lidgren.Network/NetReceiverChannelBase.cs
Normal file
18
Lidgren.Network/NetReceiverChannelBase.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal abstract class NetReceiverChannelBase
|
||||
{
|
||||
internal NetPeer m_peer;
|
||||
internal NetConnection m_connection;
|
||||
|
||||
public NetReceiverChannelBase(NetConnection connection)
|
||||
{
|
||||
m_connection = connection;
|
||||
m_peer = connection.m_peer;
|
||||
}
|
||||
|
||||
internal abstract void ReceiveMessage(NetIncomingMessage msg);
|
||||
}
|
||||
}
|
||||
87
Lidgren.Network/NetReliableOrderedReceiver.cs
Normal file
87
Lidgren.Network/NetReliableOrderedReceiver.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal sealed class NetReliableOrderedReceiver : NetReceiverChannelBase
|
||||
{
|
||||
private int m_windowStart;
|
||||
private int m_windowSize;
|
||||
private NetBitVector m_earlyReceived;
|
||||
internal NetIncomingMessage[] m_withheldMessages;
|
||||
|
||||
public NetReliableOrderedReceiver(NetConnection connection, int windowSize)
|
||||
: base(connection)
|
||||
{
|
||||
m_windowSize = windowSize;
|
||||
m_withheldMessages = new NetIncomingMessage[windowSize];
|
||||
m_earlyReceived = new NetBitVector(windowSize);
|
||||
}
|
||||
|
||||
private void AdvanceWindow()
|
||||
{
|
||||
m_earlyReceived.Set(m_windowStart % m_windowSize, false);
|
||||
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
|
||||
}
|
||||
|
||||
internal override void ReceiveMessage(NetIncomingMessage message)
|
||||
{
|
||||
int relate = NetUtility.RelativeSequenceNumber(message.m_sequenceNumber, m_windowStart);
|
||||
|
||||
// ack no matter what
|
||||
m_connection.QueueAck(message.m_receivedMessageType, message.m_sequenceNumber);
|
||||
|
||||
if (relate == 0)
|
||||
{
|
||||
// Log("Received message #" + message.SequenceNumber + " right on time");
|
||||
|
||||
//
|
||||
// excellent, right on time
|
||||
//
|
||||
m_peer.LogVerbose("Received RIGHT-ON-TIME " + message);
|
||||
|
||||
AdvanceWindow();
|
||||
m_peer.ReleaseMessage(message);
|
||||
|
||||
// release withheld messages
|
||||
int nextSeqNr = (message.m_sequenceNumber + 1) % NetConstants.NumSequenceNumbers;
|
||||
|
||||
while (m_earlyReceived[nextSeqNr % m_windowSize])
|
||||
{
|
||||
message = m_withheldMessages[nextSeqNr % m_windowSize];
|
||||
NetException.Assert(message != null);
|
||||
|
||||
// remove it from withheld messages
|
||||
m_withheldMessages[nextSeqNr % m_windowSize] = null;
|
||||
|
||||
m_peer.LogVerbose("Releasing withheld message #" + message);
|
||||
|
||||
m_peer.ReleaseMessage(message);
|
||||
|
||||
AdvanceWindow();
|
||||
nextSeqNr++;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (relate < 0)
|
||||
{
|
||||
m_peer.LogVerbose("Received message #" + message.m_sequenceNumber + " DROPPING DUPLICATE");
|
||||
// duplicate
|
||||
return;
|
||||
}
|
||||
|
||||
// relate > 0 = early message
|
||||
if (relate > m_windowSize)
|
||||
{
|
||||
// too early message!
|
||||
m_peer.LogDebug("Received " + message + " TOO EARLY! Expected " + m_windowStart);
|
||||
return;
|
||||
}
|
||||
|
||||
m_earlyReceived.Set(message.m_sequenceNumber % m_windowSize, true);
|
||||
m_peer.LogVerbose("Received " + message + " WITHHOLDING, waiting for " + m_windowStart);
|
||||
m_withheldMessages[message.m_sequenceNumber % m_windowSize] = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
236
Lidgren.Network/NetReliableSenderChannel.cs
Normal file
236
Lidgren.Network/NetReliableSenderChannel.cs
Normal file
@@ -0,0 +1,236 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Sender part of Selective repeat ARQ for a particular NetChannel
|
||||
/// </summary>
|
||||
internal sealed class NetReliableSenderChannel : NetSenderChannelBase
|
||||
{
|
||||
private NetConnection m_connection;
|
||||
private int m_windowStart;
|
||||
private int m_windowSize;
|
||||
private int m_sendStart;
|
||||
|
||||
private NetBitVector m_receivedAcks;
|
||||
internal NetStoredReliableMessage[] m_storedMessages;
|
||||
|
||||
internal override int WindowSize { get { return m_windowSize; } }
|
||||
|
||||
internal NetReliableSenderChannel(NetConnection connection, int windowSize)
|
||||
{
|
||||
m_connection = connection;
|
||||
m_windowSize = windowSize;
|
||||
m_windowStart = 0;
|
||||
m_sendStart = 0;
|
||||
m_receivedAcks = new NetBitVector(NetConstants.NumSequenceNumbers);
|
||||
m_storedMessages = new NetStoredReliableMessage[m_windowSize];
|
||||
m_queuedSends = new NetQueue<NetOutgoingMessage>(8);
|
||||
}
|
||||
|
||||
internal override int GetAllowedSends()
|
||||
{
|
||||
int retval = m_windowSize - ((m_sendStart + NetConstants.NumSequenceNumbers) - m_windowStart) % NetConstants.NumSequenceNumbers;
|
||||
NetException.Assert(retval >= 0 && retval <= m_windowSize);
|
||||
return retval;
|
||||
}
|
||||
|
||||
internal override void Reset()
|
||||
{
|
||||
m_receivedAcks.Clear();
|
||||
for (int i = 0; i < m_storedMessages.Length; i++)
|
||||
m_storedMessages[i].Reset();
|
||||
m_queuedSends.Clear();
|
||||
m_windowStart = 0;
|
||||
m_sendStart = 0;
|
||||
}
|
||||
|
||||
internal override NetSendResult Enqueue(NetOutgoingMessage message)
|
||||
{
|
||||
m_queuedSends.Enqueue(message);
|
||||
|
||||
int queueLen = m_queuedSends.Count;
|
||||
int left = m_windowSize - ((m_sendStart + NetConstants.NumSequenceNumbers) - m_windowStart) % NetConstants.NumSequenceNumbers;
|
||||
if (queueLen <= left)
|
||||
return NetSendResult.Sent;
|
||||
return NetSendResult.Queued;
|
||||
}
|
||||
|
||||
// call this regularely
|
||||
internal override void SendQueuedMessages(float now)
|
||||
{
|
||||
// resends
|
||||
float resendDelay = m_connection.GetResendDelay();
|
||||
for (int i = 0; i < m_storedMessages.Length; i++)
|
||||
{
|
||||
NetOutgoingMessage om = m_storedMessages[i].Message;
|
||||
if (om == null)
|
||||
continue;
|
||||
|
||||
float t = m_storedMessages[i].LastSent;
|
||||
if (t > 0 && (now - t) > resendDelay)
|
||||
{
|
||||
// deduce sequence number
|
||||
int startSlot = m_windowStart % m_windowSize;
|
||||
int seqNr = m_windowStart;
|
||||
while (startSlot != i)
|
||||
{
|
||||
startSlot--;
|
||||
if (startSlot < 0)
|
||||
startSlot = m_windowSize - 1;
|
||||
seqNr--;
|
||||
}
|
||||
|
||||
m_connection.m_peer.LogVerbose("Resending due to delay #" + seqNr + " " + om.ToString());
|
||||
m_connection.m_statistics.MessageResent();
|
||||
|
||||
m_connection.QueueSendMessage(om, seqNr);
|
||||
|
||||
m_storedMessages[i].LastSent = now;
|
||||
m_storedMessages[i].NumSent++;
|
||||
}
|
||||
}
|
||||
|
||||
int num = GetAllowedSends();
|
||||
if (num < 1)
|
||||
return;
|
||||
|
||||
// queued sends
|
||||
while (m_queuedSends.Count > 0 && num > 0)
|
||||
{
|
||||
NetOutgoingMessage om;
|
||||
if (m_queuedSends.TryDequeue(out om))
|
||||
ExecuteSend(now, om);
|
||||
num--;
|
||||
NetException.Assert(num == GetAllowedSends());
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteSend(float now, NetOutgoingMessage message)
|
||||
{
|
||||
int seqNr = m_sendStart;
|
||||
m_sendStart = (m_sendStart + 1) % NetConstants.NumSequenceNumbers;
|
||||
|
||||
m_connection.QueueSendMessage(message, seqNr);
|
||||
|
||||
int storeIndex = seqNr % m_windowSize;
|
||||
NetException.Assert(m_storedMessages[storeIndex].Message == null);
|
||||
|
||||
m_storedMessages[storeIndex].NumSent++;
|
||||
m_storedMessages[storeIndex].Message = message;
|
||||
m_storedMessages[storeIndex].LastSent = now;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private void DestoreMessage(int storeIndex)
|
||||
{
|
||||
NetOutgoingMessage storedMessage = m_storedMessages[storeIndex].Message;
|
||||
NetException.Assert(storedMessage != null);
|
||||
|
||||
Interlocked.Decrement(ref storedMessage.m_recyclingCount);
|
||||
if (storedMessage.m_recyclingCount <= 0)
|
||||
m_connection.m_peer.Recycle(storedMessage);
|
||||
|
||||
m_storedMessages[storeIndex] = new NetStoredReliableMessage();
|
||||
}
|
||||
|
||||
// remoteWindowStart is remote expected sequence number; everything below this has arrived properly
|
||||
// seqNr is the actual nr received
|
||||
internal override void ReceiveAcknowledge(float now, int seqNr)
|
||||
{
|
||||
// late (dupe), on time or early ack?
|
||||
int relate = NetUtility.RelativeSequenceNumber(seqNr, m_windowStart);
|
||||
|
||||
if (relate < 0)
|
||||
{
|
||||
//m_connection.m_peer.LogDebug("Received late/dupe ack for #" + seqNr);
|
||||
return; // late/duplicate ack
|
||||
}
|
||||
|
||||
if (relate == 0)
|
||||
{
|
||||
//m_connection.m_peer.LogDebug("Received right-on-time ack for #" + seqNr);
|
||||
|
||||
// ack arrived right on time
|
||||
NetException.Assert(seqNr == m_windowStart);
|
||||
|
||||
m_receivedAcks[m_windowStart] = false;
|
||||
DestoreMessage(m_windowStart % m_windowSize);
|
||||
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
|
||||
|
||||
// advance window if we already have early acks
|
||||
while (m_receivedAcks[m_windowStart])
|
||||
{
|
||||
//m_connection.m_peer.LogDebug("Using early ack for #" + m_windowStart + "...");
|
||||
m_receivedAcks[m_windowStart] = false;
|
||||
NetException.Assert(m_storedMessages[m_windowStart % m_windowSize].Message == null); // should already be destored
|
||||
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
|
||||
//m_connection.m_peer.LogDebug("Advancing window to #" + m_windowStart);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// early ack... (if it has been sent!)
|
||||
//
|
||||
// If it has been sent either the m_windowStart message was lost
|
||||
// ... or the ack for that message was lost
|
||||
//
|
||||
|
||||
//m_connection.m_peer.LogDebug("Received early ack for #" + seqNr);
|
||||
|
||||
int sendRelate = NetUtility.RelativeSequenceNumber(seqNr, m_sendStart);
|
||||
if (sendRelate <= 0)
|
||||
{
|
||||
// yes, we've sent this message - it's an early (but valid) ack
|
||||
if (m_receivedAcks[seqNr])
|
||||
{
|
||||
// we've already destored/been acked for this message
|
||||
}
|
||||
else
|
||||
{
|
||||
DestoreMessage(seqNr % m_windowSize);
|
||||
m_receivedAcks[seqNr] = true;
|
||||
}
|
||||
}
|
||||
else if (sendRelate > 0)
|
||||
{
|
||||
// uh... we haven't sent this message yet? Weird, dupe or error...
|
||||
return;
|
||||
}
|
||||
|
||||
// Ok, lets resend all missing acks
|
||||
int rnr = seqNr;
|
||||
do
|
||||
{
|
||||
rnr--;
|
||||
if (rnr < 0)
|
||||
rnr = NetConstants.NumSequenceNumbers - 1;
|
||||
|
||||
if (m_receivedAcks[rnr])
|
||||
{
|
||||
// m_connection.m_peer.LogDebug("Not resending #" + rnr + " (since we got ack)");
|
||||
}
|
||||
else
|
||||
{
|
||||
int slot = rnr % m_windowSize;
|
||||
NetException.Assert(m_storedMessages[slot].Message != null);
|
||||
if (m_storedMessages[slot].NumSent == 1)
|
||||
{
|
||||
// just sent once; resend immediately since we found gap in ack sequence
|
||||
NetOutgoingMessage rmsg = m_storedMessages[slot].Message;
|
||||
m_connection.m_peer.LogVerbose("Resending #" + rnr + " (" + rmsg + ")");
|
||||
m_storedMessages[slot].LastSent = now;
|
||||
m_storedMessages[slot].NumSent++;
|
||||
m_connection.m_statistics.MessageResent();
|
||||
m_connection.QueueSendMessage(rmsg, rnr);
|
||||
}
|
||||
}
|
||||
|
||||
} while (rnr != m_windowStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
63
Lidgren.Network/NetReliableSequencedReceiver.cs
Normal file
63
Lidgren.Network/NetReliableSequencedReceiver.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal sealed class NetReliableSequencedReceiver : NetReceiverChannelBase
|
||||
{
|
||||
private int m_windowStart;
|
||||
private int m_windowSize;
|
||||
|
||||
public NetReliableSequencedReceiver(NetConnection connection, int windowSize)
|
||||
: base(connection)
|
||||
{
|
||||
m_windowSize = windowSize;
|
||||
}
|
||||
|
||||
private void AdvanceWindow()
|
||||
{
|
||||
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
|
||||
}
|
||||
|
||||
internal override void ReceiveMessage(NetIncomingMessage message)
|
||||
{
|
||||
int nr = message.m_sequenceNumber;
|
||||
|
||||
int relate = NetUtility.RelativeSequenceNumber(nr, m_windowStart);
|
||||
|
||||
// ack no matter what
|
||||
m_connection.QueueAck(message.m_receivedMessageType, nr);
|
||||
|
||||
if (relate == 0)
|
||||
{
|
||||
// Log("Received message #" + message.SequenceNumber + " right on time");
|
||||
|
||||
//
|
||||
// excellent, right on time
|
||||
//
|
||||
|
||||
AdvanceWindow();
|
||||
m_peer.ReleaseMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (relate < 0)
|
||||
{
|
||||
m_peer.LogVerbose("Received message #" + message.m_sequenceNumber + " DROPPING LATE or DUPE");
|
||||
return;
|
||||
}
|
||||
|
||||
// relate > 0 = early message
|
||||
if (relate > m_windowSize)
|
||||
{
|
||||
// too early message!
|
||||
m_peer.LogDebug("Received " + message + " TOO EARLY! Expected " + m_windowStart);
|
||||
return;
|
||||
}
|
||||
|
||||
// ok
|
||||
m_windowStart = (m_windowStart + relate) % NetConstants.NumSequenceNumbers;
|
||||
m_peer.ReleaseMessage(message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
87
Lidgren.Network/NetReliableUnorderedReceiver.cs
Normal file
87
Lidgren.Network/NetReliableUnorderedReceiver.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal sealed class NetReliableUnorderedReceiver : NetReceiverChannelBase
|
||||
{
|
||||
private int m_windowStart;
|
||||
private int m_windowSize;
|
||||
private NetBitVector m_earlyReceived;
|
||||
|
||||
public NetReliableUnorderedReceiver(NetConnection connection, int windowSize)
|
||||
: base(connection)
|
||||
{
|
||||
m_windowSize = windowSize;
|
||||
m_earlyReceived = new NetBitVector(windowSize);
|
||||
}
|
||||
|
||||
private void AdvanceWindow()
|
||||
{
|
||||
m_earlyReceived.Set(m_windowStart % m_windowSize, false);
|
||||
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
|
||||
}
|
||||
|
||||
internal override void ReceiveMessage(NetIncomingMessage message)
|
||||
{
|
||||
int relate = NetUtility.RelativeSequenceNumber(message.m_sequenceNumber, m_windowStart);
|
||||
|
||||
// ack no matter what
|
||||
m_connection.QueueAck(message.m_receivedMessageType, message.m_sequenceNumber);
|
||||
|
||||
if (relate == 0)
|
||||
{
|
||||
// Log("Received message #" + message.SequenceNumber + " right on time");
|
||||
|
||||
//
|
||||
// excellent, right on time
|
||||
//
|
||||
m_peer.LogVerbose("Received RIGHT-ON-TIME " + message);
|
||||
|
||||
AdvanceWindow();
|
||||
m_peer.ReleaseMessage(message);
|
||||
|
||||
// release withheld messages
|
||||
int nextSeqNr = (message.m_sequenceNumber + 1) % NetConstants.NumSequenceNumbers;
|
||||
|
||||
while (m_earlyReceived[nextSeqNr % m_windowSize])
|
||||
{
|
||||
//message = m_withheldMessages[nextSeqNr % m_windowSize];
|
||||
//NetException.Assert(message != null);
|
||||
|
||||
// remove it from withheld messages
|
||||
//m_withheldMessages[nextSeqNr % m_windowSize] = null;
|
||||
|
||||
//m_peer.LogVerbose("Releasing withheld message #" + message);
|
||||
|
||||
//m_peer.ReleaseMessage(message);
|
||||
|
||||
AdvanceWindow();
|
||||
nextSeqNr++;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (relate < 0)
|
||||
{
|
||||
// duplicate
|
||||
m_peer.LogVerbose("Received message #" + message.m_sequenceNumber + " DROPPING DUPLICATE");
|
||||
return;
|
||||
}
|
||||
|
||||
// relate > 0 = early message
|
||||
if (relate > m_windowSize)
|
||||
{
|
||||
// too early message!
|
||||
m_peer.LogDebug("Received " + message + " TOO EARLY! Expected " + m_windowStart);
|
||||
return;
|
||||
}
|
||||
|
||||
m_earlyReceived.Set(message.m_sequenceNumber % m_windowSize, true);
|
||||
//m_peer.LogVerbose("Received " + message + " WITHHOLDING, waiting for " + m_windowStart);
|
||||
//m_withheldMessages[message.m_sequenceNumber % m_windowSize] = message;
|
||||
|
||||
m_peer.ReleaseMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Lidgren.Network/NetSendResult.cs
Normal file
30
Lidgren.Network/NetSendResult.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Result of a SendMessage call
|
||||
/// </summary>
|
||||
public enum NetSendResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Message failed to enqueue; for example if there's no connection in place
|
||||
/// </summary>
|
||||
Failed = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Message was immediately sent
|
||||
/// </summary>
|
||||
Sent = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Message was queued for delivery
|
||||
/// </summary>
|
||||
Queued = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Message was dropped immediately since too many message were queued
|
||||
/// </summary>
|
||||
Dropped = 3
|
||||
}
|
||||
}
|
||||
19
Lidgren.Network/NetSenderChannelBase.cs
Normal file
19
Lidgren.Network/NetSenderChannelBase.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal abstract class NetSenderChannelBase
|
||||
{
|
||||
// access this directly to queue things in this channel
|
||||
internal NetQueue<NetOutgoingMessage> m_queuedSends;
|
||||
|
||||
internal abstract int WindowSize { get; }
|
||||
|
||||
internal abstract int GetAllowedSends();
|
||||
|
||||
internal abstract NetSendResult Enqueue(NetOutgoingMessage message);
|
||||
internal abstract void SendQueuedMessages(float now);
|
||||
internal abstract void Reset();
|
||||
internal abstract void ReceiveAcknowledge(float now, int sequenceNumber);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
[DebuggerDisplay("MessageType={MessageType} SequenceNumber={SequenceNumber} NumSends={NumSends}")]
|
||||
internal sealed class NetSending
|
||||
{
|
||||
public NetOutgoingMessage Message;
|
||||
public IPEndPoint Recipient;
|
||||
public NetMessageType MessageType;
|
||||
public ushort SequenceNumber;
|
||||
public double NextResend;
|
||||
public int NumSends; // how many times has this sending been sent
|
||||
|
||||
public int FragmentGroupId;
|
||||
public int FragmentNumber;
|
||||
public int FragmentTotalCount;
|
||||
|
||||
public NetSending(NetOutgoingMessage msg, NetMessageType tp, ushort sequenceNumber)
|
||||
{
|
||||
Message = msg;
|
||||
MessageType = tp;
|
||||
SequenceNumber = sequenceNumber;
|
||||
}
|
||||
|
||||
internal void SetNextResend(NetConnection conn)
|
||||
{
|
||||
float baseDelay;
|
||||
switch(NumSends)
|
||||
{
|
||||
case 0: baseDelay = 0.025f; break;
|
||||
case 1: baseDelay = 0.05f; break;
|
||||
case 2: baseDelay = 0.15f; break;
|
||||
case 3: baseDelay = 0.3f; break;
|
||||
default:
|
||||
baseDelay = (float)(NumSends - 3); // 4: 1 second, 5: 2 seconds, 6: 3 seconds etc
|
||||
break;
|
||||
}
|
||||
|
||||
float rttMultiplier = 1.15f + (0.15f * NumSends);
|
||||
|
||||
float totalDelay = baseDelay + (conn.AverageRoundtripTime * rttMultiplier);
|
||||
|
||||
NextResend = NetTime.Now + totalDelay;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "[NetSending " + MessageType + "#" + SequenceNumber + " NumSends: " + NumSends + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +1,16 @@
|
||||
/* Copyright (c) 2010 Michael Lidgren
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
*/
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Specialized version of NetPeer used for "server" peers
|
||||
/// </summary>
|
||||
public class NetServer : NetPeer
|
||||
{
|
||||
public NetServer(NetPeerConfiguration config)
|
||||
: base(config)
|
||||
{
|
||||
// force this to true
|
||||
config.AcceptIncomingConnections = true;
|
||||
}
|
||||
|
||||
public void Disconnect(NetConnection connection, string byeMessage)
|
||||
{
|
||||
if (connection == null)
|
||||
throw new ArgumentNullException("connection");
|
||||
|
||||
connection.Disconnect(byeMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
18
Lidgren.Network/NetStoredReliableMessage.cs
Normal file
18
Lidgren.Network/NetStoredReliableMessage.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal struct NetStoredReliableMessage
|
||||
{
|
||||
public int NumSent;
|
||||
public float LastSent;
|
||||
public NetOutgoingMessage Message;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
NumSent = 0;
|
||||
LastSent = 0;
|
||||
Message = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,10 +45,6 @@ namespace Lidgren.Network
|
||||
/// </summary>
|
||||
public static double Now { get { return (double)Environment.TickCount / 1000.0; } }
|
||||
#endif
|
||||
public static double GetRemoteNow(NetConnection conn)
|
||||
{
|
||||
return Now - conn.m_remoteToLocalNetTime;
|
||||
}
|
||||
|
||||
public static string ToReadable(double seconds)
|
||||
{
|
||||
|
||||
20
Lidgren.Network/NetTuple.cs
Normal file
20
Lidgren.Network/NetTuple.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
// replace with BCL 4.0 Tuple<> when appropriate
|
||||
internal struct NetTuple<A, B>
|
||||
{
|
||||
public A Item1;
|
||||
public B Item2;
|
||||
|
||||
public NetTuple(A item1, B item2)
|
||||
{
|
||||
Item1 = item1;
|
||||
Item2 = item2;
|
||||
}
|
||||
}
|
||||
}
|
||||
125
Lidgren.Network/NetUnreliableSenderChannel.cs
Normal file
125
Lidgren.Network/NetUnreliableSenderChannel.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
/// <summary>
|
||||
/// Sender part of Selective repeat ARQ for a particular NetChannel
|
||||
/// </summary>
|
||||
internal sealed class NetUnreliableSenderChannel : NetSenderChannelBase
|
||||
{
|
||||
private NetConnection m_connection;
|
||||
private int m_windowStart;
|
||||
private int m_windowSize;
|
||||
private int m_sendStart;
|
||||
|
||||
private NetBitVector m_receivedAcks;
|
||||
|
||||
internal override int WindowSize { get { return m_windowSize; } }
|
||||
|
||||
internal NetUnreliableSenderChannel(NetConnection connection, int windowSize)
|
||||
{
|
||||
m_connection = connection;
|
||||
m_windowSize = windowSize;
|
||||
m_windowStart = 0;
|
||||
m_sendStart = 0;
|
||||
m_receivedAcks = new NetBitVector(NetConstants.NumSequenceNumbers);
|
||||
m_queuedSends = new NetQueue<NetOutgoingMessage>(8);
|
||||
}
|
||||
|
||||
internal override int GetAllowedSends()
|
||||
{
|
||||
int retval = m_windowSize - ((m_sendStart + NetConstants.NumSequenceNumbers) - m_windowStart) % m_windowSize;
|
||||
NetException.Assert(retval >= 0 && retval <= m_windowSize);
|
||||
return retval;
|
||||
}
|
||||
|
||||
internal override void Reset()
|
||||
{
|
||||
m_receivedAcks.Clear();
|
||||
m_queuedSends.Clear();
|
||||
m_windowStart = 0;
|
||||
m_sendStart = 0;
|
||||
}
|
||||
|
||||
internal override NetSendResult Enqueue(NetOutgoingMessage message)
|
||||
{
|
||||
int queueLen = m_queuedSends.Count + 1;
|
||||
int left = m_windowSize - ((m_sendStart + NetConstants.NumSequenceNumbers) - m_windowStart) % NetConstants.NumSequenceNumbers;
|
||||
if (queueLen > left)
|
||||
return NetSendResult.Dropped;
|
||||
|
||||
m_queuedSends.Enqueue(message);
|
||||
return NetSendResult.Sent;
|
||||
}
|
||||
|
||||
// call this regularely
|
||||
internal override void SendQueuedMessages(float now)
|
||||
{
|
||||
int num = GetAllowedSends();
|
||||
if (num < 1)
|
||||
return;
|
||||
|
||||
// queued sends
|
||||
while (m_queuedSends.Count > 0 && num > 0)
|
||||
{
|
||||
NetOutgoingMessage om;
|
||||
if (m_queuedSends.TryDequeue(out om))
|
||||
ExecuteSend(now, om);
|
||||
num--;
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteSend(float now, NetOutgoingMessage message)
|
||||
{
|
||||
m_connection.m_peer.VerifyNetworkThread();
|
||||
|
||||
int seqNr = m_sendStart;
|
||||
m_sendStart = (m_sendStart + 1) % NetConstants.NumSequenceNumbers;
|
||||
|
||||
m_connection.QueueSendMessage(message, seqNr);
|
||||
|
||||
Interlocked.Decrement(ref message.m_recyclingCount);
|
||||
if (message.m_recyclingCount <= 0)
|
||||
m_connection.m_peer.Recycle(message);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// remoteWindowStart is remote expected sequence number; everything below this has arrived properly
|
||||
// seqNr is the actual nr received
|
||||
internal override void ReceiveAcknowledge(float now, int seqNr)
|
||||
{
|
||||
// late (dupe), on time or early ack?
|
||||
int relate = NetUtility.RelativeSequenceNumber(seqNr, m_windowStart);
|
||||
|
||||
if (relate < 0)
|
||||
{
|
||||
//m_connection.m_peer.LogDebug("Received late/dupe ack for #" + seqNr);
|
||||
return; // late/duplicate ack
|
||||
}
|
||||
|
||||
if (relate == 0)
|
||||
{
|
||||
//m_connection.m_peer.LogDebug("Received right-on-time ack for #" + seqNr);
|
||||
|
||||
// ack arrived right on time
|
||||
NetException.Assert(seqNr == m_windowStart);
|
||||
|
||||
m_receivedAcks[m_windowStart] = false;
|
||||
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Advance window to this position
|
||||
m_receivedAcks[seqNr] = true;
|
||||
|
||||
while (m_windowStart != seqNr)
|
||||
{
|
||||
m_receivedAcks[m_windowStart] = false;
|
||||
m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Lidgren.Network/NetUnreliableSequencedReceiver.cs
Normal file
29
Lidgren.Network/NetUnreliableSequencedReceiver.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal sealed class NetUnreliableSequencedReceiver : NetReceiverChannelBase
|
||||
{
|
||||
private int m_lastReceivedSequenceNumber;
|
||||
|
||||
public NetUnreliableSequencedReceiver(NetConnection connection)
|
||||
: base(connection)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void ReceiveMessage(NetIncomingMessage msg)
|
||||
{
|
||||
int nr = msg.m_sequenceNumber;
|
||||
|
||||
// ack no matter what
|
||||
m_connection.QueueAck(msg.m_receivedMessageType, nr);
|
||||
|
||||
int relate = NetUtility.RelativeSequenceNumber(nr, m_lastReceivedSequenceNumber);
|
||||
if (relate < 0)
|
||||
return; // drop if late
|
||||
|
||||
m_lastReceivedSequenceNumber = nr;
|
||||
m_peer.ReleaseMessage(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Lidgren.Network/NetUnreliableUnorderedReceiver.cs
Normal file
20
Lidgren.Network/NetUnreliableUnorderedReceiver.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal sealed class NetUnreliableUnorderedReceiver : NetReceiverChannelBase
|
||||
{
|
||||
public NetUnreliableUnorderedReceiver(NetConnection connection)
|
||||
: base(connection)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void ReceiveMessage(NetIncomingMessage msg)
|
||||
{
|
||||
// ack no matter what
|
||||
m_connection.QueueAck(msg.m_receivedMessageType, msg.m_sequenceNumber);
|
||||
|
||||
m_peer.ReleaseMessage(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,7 +144,7 @@ namespace Lidgren.Network
|
||||
}
|
||||
return new string(c);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets my local IP address (not necessarily external) and subnet mask
|
||||
/// </summary>
|
||||
@@ -261,5 +261,61 @@ namespace Lidgren.Network
|
||||
retval[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16);
|
||||
return retval;
|
||||
}
|
||||
|
||||
public static string ToHumanReadable(long bytes)
|
||||
{
|
||||
if (bytes < 4000) // 1-4 kb is printed in bytes
|
||||
return bytes + " bytes";
|
||||
if (bytes < 1000 * 1000) // 4-999 kb is printed in kb
|
||||
return Math.Round(((double)bytes / 1000.0), 2) + " kilobytes";
|
||||
return Math.Round(((double)bytes / (1000.0 * 1000.0)), 2) + " megabytes"; // else megabytes
|
||||
}
|
||||
|
||||
internal static int RelativeSequenceNumber(int nr, int expected)
|
||||
{
|
||||
int retval = ((nr + NetConstants.NumSequenceNumbers) - expected) % NetConstants.NumSequenceNumbers;
|
||||
if (retval > (NetConstants.NumSequenceNumbers / 2))
|
||||
retval -= NetConstants.NumSequenceNumbers;
|
||||
return retval;
|
||||
}
|
||||
|
||||
// shell sort
|
||||
internal static void SortMembersList(System.Reflection.MemberInfo[] list)
|
||||
{
|
||||
int h;
|
||||
int j;
|
||||
System.Reflection.MemberInfo tmp;
|
||||
|
||||
h = 1;
|
||||
while (h * 3 + 1 <= list.Length)
|
||||
h = 3 * h + 1;
|
||||
|
||||
while (h > 0)
|
||||
{
|
||||
for (int i = h - 1; i < list.Length; i++)
|
||||
{
|
||||
tmp = list[i];
|
||||
j = i;
|
||||
while (true)
|
||||
{
|
||||
if (j >= h)
|
||||
{
|
||||
if (string.Compare(list[j - h].Name, tmp.Name, StringComparison.InvariantCulture) > 0)
|
||||
{
|
||||
list[j] = list[j - h];
|
||||
j -= h;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
list[j] = tmp;
|
||||
}
|
||||
h /= 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Lidgren.Network/SenderChannelBase.cs
Normal file
11
Lidgren.Network/SenderChannelBase.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Lidgren.Network
|
||||
{
|
||||
internal abstract class SenderChannelBase
|
||||
{
|
||||
internal abstract NetSendResult Send(float now, NetOutgoingMessage message);
|
||||
internal abstract void SendQueuedMessages(float now);
|
||||
internal abstract void Reset();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user