You've already forked lidgren-network-gen3
mirror of
https://github.com/lidgren/lidgren-network-gen3.git
synced 2026-05-16 15:16:33 +09:00
major update; gen 3.5
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user