1
0
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:
lidgren
2010-10-19 17:45:55 +00:00
parent baaa5926f8
commit 609bc1afe2
167 changed files with 4065 additions and 9640 deletions

View File

@@ -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()

View File

@@ -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);

View File

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

View File

@@ -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

View File

@@ -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();
}
}
}
}

View File

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

View File

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

View 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,
}
}

View File

@@ -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());
}
}
}

View File

@@ -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
{

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

View File

@@ -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>

View File

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

View File

@@ -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);

View File

@@ -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");
}
}
}

View File

@@ -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");

View File

@@ -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]";
}
}
}

View File

@@ -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

View File

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

View File

@@ -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)

View File

@@ -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");
}
}
}

View File

@@ -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)
{

View File

@@ -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");

View File

@@ -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]";
}
}
}

View File

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

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

View File

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

View File

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

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

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

View File

@@ -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]";
}
}
}

View File

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

View File

@@ -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();
}
}

View File

@@ -21,6 +21,9 @@ using System;
namespace Lidgren.Network
{
/// <summary>
/// Status for a NetPeer instance
/// </summary>
public enum NetPeerStatus
{
NotRunning = 0,

View File

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

View File

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

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

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

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

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

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

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

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

View File

@@ -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 + "]";
}
}
}

View File

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

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

View File

@@ -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)
{

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

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

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

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

View File

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

View 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();
}
}