private void Heartbeat()
{
VerifyNetworkThread();
double now = NetTime.Now;
double delta = now - m_lastHeartbeat;
int maxCHBpS = 1250 - m_connections.Count;
if (maxCHBpS < 250)
{
maxCHBpS = 250;
}
if (delta > (1.0 / (double)maxCHBpS) || delta < 0.0) // max connection heartbeats/second max
{
m_frameCounter++;
m_lastHeartbeat = now;
// do handshake heartbeats
if ((m_frameCounter % 3) == 0)
{
foreach (var kvp in m_handshakes)
{
NetConnection conn = kvp.Value as NetConnection;
#if DEBUG
// sanity check
if (kvp.Key != kvp.Key)
{
LogWarning("Sanity fail! Connection in handshake list under wrong key!");
}
#endif
conn.UnconnectedHeartbeat(now);
if (conn.m_status == NetConnectionStatus.Connected || conn.m_status == NetConnectionStatus.Disconnected)
{
#if DEBUG
// sanity check
if (conn.m_status == NetConnectionStatus.Disconnected && m_handshakes.ContainsKey(conn.RemoteEndPoint))
{
LogWarning("Sanity fail! Handshakes list contained disconnected connection!");
m_handshakes.Remove(conn.RemoteEndPoint);
}
#endif
break; // collection has been modified
}
}
}
#if DEBUG
SendDelayedPackets();
#endif
// update m_executeFlushSendQueue
if (m_configuration.m_autoFlushSendQueue && m_needFlushSendQueue == true)
{
m_executeFlushSendQueue = true;
m_needFlushSendQueue = false; // a race condition to this variable will simply result in a single superfluous call to FlushSendQueue()
}
// do connection heartbeats
lock (m_connections)
{
for (int i = m_connections.Count - 1; i >= 0; i--)
{
var conn = m_connections[i];
conn.Heartbeat(now, m_frameCounter);
if (conn.m_status == NetConnectionStatus.Disconnected)
{
//
// remove connection
//
m_connections.RemoveAt(i);
m_connectionLookup.Remove(conn.RemoteEndPoint);
}
}
}
m_executeFlushSendQueue = false;
// send unsent unconnected messages
NetTuple <NetEndPoint, NetOutgoingMessage> unsent;
while (m_unsentUnconnectedMessages.TryDequeue(out unsent))
{
NetOutgoingMessage om = unsent.Item2;
int len = om.Encode(m_sendBuffer, 0, 0);
Interlocked.Decrement(ref om.m_recyclingCount);
if (om.m_recyclingCount <= 0)
{
Recycle(om);
}
bool connReset;
SendPacket(len, unsent.Item1, 1, out connReset);
}
}
if (m_upnp != null)
{
m_upnp.CheckForDiscoveryTimeout();
}
//
// read from socket
//
if (m_socket == null)
{
return;
}
if (!m_socket.Poll(1000, SelectMode.SelectRead)) // wait up to 1 ms for data to arrive
{
return;
}
//if (m_socket == null || m_socket.Available < 1)
// return;
// update now
now = NetTime.Now;
do
{
int bytesReceived = 0;
try
{
bytesReceived = m_socket.ReceiveFrom(m_receiveBuffer, 0, m_receiveBuffer.Length, SocketFlags.None, ref m_senderRemote);
}
catch (SocketException sx)
{
switch (sx.SocketErrorCode)
{
case 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?!
// So, what to do?
LogWarning("ConnectionReset");
return;
case SocketError.NotConnected:
// socket is unbound; try to rebind it (happens on mobile when process goes to sleep)
BindSocket(true);
return;
default:
LogWarning("Socket exception: " + sx.ToString());
return;
}
}
if (bytesReceived < NetConstants.HeaderByteSize)
{
return;
}
//LogVerbose("Received " + bytesReceived + " bytes");
var ipsender = (NetEndPoint)m_senderRemote;
if (m_upnp != null && now < m_upnp.m_discoveryResponseDeadline && bytesReceived > 32)
{
// is this an UPnP response?
string resp = System.Text.Encoding.UTF8.GetString(m_receiveBuffer, 0, bytesReceived);
if (resp.Contains("upnp:rootdevice") || resp.Contains("UPnP/1.0"))
{
try
{
resp = resp.Substring(resp.ToLower().IndexOf("location:") + 9);
resp = resp.Substring(0, resp.IndexOf("\r")).Trim();
m_upnp.ExtractServiceUrl(resp);
return;
}
catch (Exception ex)
{
LogDebug("Failed to parse UPnP response: " + ex.ToString());
// don't try to parse this packet further
return;
}
}
}
NetConnection sender = null;
m_connectionLookup.TryGetValue(ipsender, out sender);
//
// parse packet into messages
//
int numMessages = 0;
int numFragments = 0;
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
numMessages++;
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));
if (isFragment)
{
numFragments++;
}
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 (tp >= NetMessageType.Unused1 && tp <= NetMessageType.Unused29)
{
ThrowOrLog("Unexpected NetMessageType: " + tp);
return;
}
try
{
if (tp >= NetMessageType.LibraryError)
{
if (sender != null)
{
sender.ReceivedLibraryMessage(tp, ptr, payloadByteLength);
}
else
{
ReceivedUnconnectedLibraryMessage(now, 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_receiveTime = now;
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 (tp == NetMessageType.Unconnected)
{
// 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);
}
}
else
{
// at this point we know the message type is enabled
// unconnected application (non-library) message
msg.m_incomingMessageType = NetIncomingMessageType.UnconnectedData;
ReleaseMessage(msg);
}
}
}
catch (Exception ex)
{
LogError("Packet parsing error: " + ex.Message + " from " + ipsender);
}
ptr += payloadByteLength;
}
m_statistics.PacketReceived(bytesReceived, numMessages, numFragments);
if (sender != null)
{
sender.m_statistics.PacketReceived(bytesReceived, numMessages, numFragments);
}
} while (m_socket.Available > 0);
}