/// <summary> /// Send a single, out-of-band unreliable message /// </summary> internal void DoSendOutOfBandMessage(NetBuffer data, IPEndPoint recipient) { m_sendBuffer.Reset(); // message type and channel m_sendBuffer.Write((byte)((int)NetMessageLibraryType.OutOfBand | ((int)NetChannel.Unreliable << 3))); m_sendBuffer.Write((ushort)0); // payload length; variable byte encoded if (data == null) { m_sendBuffer.WriteVariableUInt32((uint)0); } else { int dataLen = data.LengthBytes; m_sendBuffer.WriteVariableUInt32((uint)(dataLen)); m_sendBuffer.Write(data.Data, 0, dataLen); } SendPacket(recipient); // unreliable; we can recycle this immediately RecycleBuffer(data); }
private void InitiateDisconnect() { if (m_requestSendGoodbye) { NetBuffer scratch = m_owner.m_scratchBuffer; scratch.Reset(); scratch.Write(string.IsNullOrEmpty(m_futureDisconnectReason) ? "" : m_futureDisconnectReason); m_owner.SendSingleUnreliableSystemMessage( NetSystemType.Disconnect, scratch, m_remoteEndPoint, false ); } if (m_requestLinger <= 0) { SetStatus(NetConnectionStatus.Disconnected, m_futureDisconnectReason); FinalizeDisconnect(); m_futureClose = double.MaxValue; } else { SetStatus(NetConnectionStatus.Disconnecting, m_futureDisconnectReason); m_futureClose = NetTime.Now + m_requestLinger; } m_requestDisconnect = false; }
internal static void QueuePing(NetBase netBase, IPEndPoint toEndPoint, double now) { ushort nowEnc = NetTime.Encoded(now); NetBuffer buffer = netBase.m_scratchBuffer; buffer.Reset(); buffer.Write(nowEnc); netBase.QueueSingleUnreliableSystemMessage( NetSystemType.Ping, buffer, toEndPoint, false ); }
/// <summary> /// Reads all packets and create messages /// </summary> protected void BaseHeartbeat(double now) { if (!m_isBound) { return; } // discovery m_discovery.Heartbeat(now); // hole punching if (m_holePunches != null) { if (now > m_lastHolePunch + NetConstants.HolePunchingFrequency) { if (m_holePunches.Count <= 0) { m_holePunches = null; } else { IPEndPoint dest = m_holePunches[0]; m_holePunches.RemoveAt(0); NotifyApplication(NetMessageType.DebugMessage, "Sending hole punch to " + dest, null); NetConnection.SendPing(this, dest, now); if (m_holePunches.Count < 1) { m_holePunches = null; } m_lastHolePunch = now; } } } // Send queued system messages if (m_susmQueue.Count > 0) { lock (m_susmQueue) { while (m_susmQueue.Count > 0) { SUSystemMessage su = m_susmQueue.Dequeue(); SendSingleUnreliableSystemMessage(su.Type, su.Data, su.Destination, su.UseBroadcast); } } } // Send out-of-band messages if (m_unsentOutOfBandMessages.Count > 0) { lock (m_unsentOutOfBandMessages) { while (m_unsentOutOfBandMessages.Count > 0) { NetBuffer buf = m_unsentOutOfBandMessages.Dequeue(); IPEndPoint ep = m_unsentOutOfBandRecipients.Dequeue(); DoSendOutOfBandMessage(buf, ep); } } } try { #if DEBUG SendDelayedPackets(now); #endif while (true) { if (m_socket == null || m_socket.Available < 1) { return; } m_receiveBuffer.Reset(); int bytesReceived = 0; try { bytesReceived = m_socket.ReceiveFrom(m_receiveBuffer.Data, 0, m_receiveBuffer.Data.Length, SocketFlags.None, ref m_senderRemote); } catch (SocketException) { // no good response to this yet return; } if (bytesReceived < 1) { return; } if (bytesReceived > 0) { m_statistics.CountPacketReceived(bytesReceived); } m_receiveBuffer.LengthBits = bytesReceived * 8; //LogVerbose("Read packet: " + bytesReceived + " bytes"); IPEndPoint ipsender = (IPEndPoint)m_senderRemote; NetConnection sender = GetConnection(ipsender); if (sender != null) { sender.m_statistics.CountPacketReceived(bytesReceived, now); } // create messages from packet while (m_receiveBuffer.Position < m_receiveBuffer.LengthBits) { int beginPosition = m_receiveBuffer.Position; // read message header IncomingNetMessage msg = CreateIncomingMessage(); msg.m_sender = sender; msg.ReadFrom(m_receiveBuffer, ipsender); // statistics if (sender != null) { sender.m_statistics.CountMessageReceived(msg.m_type, msg.m_sequenceChannel, (m_receiveBuffer.Position - beginPosition) / 8, now); } // handle message HandleReceivedMessage(msg, ipsender); } } } catch (SocketException sex) { if (sex.ErrorCode == 10054) { // forcibly closed; but m_senderRemote is unreliable, we can't trust it! //NetConnection conn = GetConnection((IPEndPoint)m_senderRemote); //HandleConnectionForciblyClosed(conn, sex); return; } } catch (Exception ex) { throw new NetException("ReadPacket() exception", ex); } }
internal void SendUnsentMessages(double now) { // Add any acknowledges to unsent messages if (m_acknowledgesToSend.Count > 0) { if (m_unsentMessages.Count < 1) { // Wait before sending acknowledges? if (m_ackMaxDelayTime > 0.0f) { if (m_ackWithholdingStarted == 0.0) { m_ackWithholdingStarted = now; } else { if (now - m_ackWithholdingStarted < m_ackMaxDelayTime) { return; // don't send (only) acks just yet } // send acks "explicitly" ie. without any other message being sent m_ackWithholdingStarted = 0.0; } } } // create ack messages and add to m_unsentMessages CreateAckMessages(); } if (m_unsentMessages.Count < 1) { return; } // throttling float throttle = m_owner.m_config.ThrottleBytesPerSecond; float maxSendBytes = float.MaxValue; if (throttle > 0) { double frameLength = now - m_lastSentUnsentMessages; //int wasDebt = (int)m_throttleDebt; if (m_throttleDebt > 0) { m_throttleDebt -= (float)(frameLength * (double)m_owner.m_config.ThrottleBytesPerSecond); } //int nowDebt = (int)m_throttleDebt; //if (nowDebt != wasDebt) // LogWrite("THROTTLE worked off -" + (nowDebt - wasDebt) + " bytes = " + m_throttleDebt); m_lastSentUnsentMessages = now; maxSendBytes = throttle - m_throttleDebt; if (maxSendBytes < 0) { return; // throttling; no bytes allowed to be sent } } int mtu = m_owner.Configuration.MaximumTransmissionUnit; bool useCoalescing = m_owner.Configuration.UseMessageCoalescing; int messagesInPacket = 0; NetBuffer sendBuffer = m_owner.m_sendBuffer; sendBuffer.Reset(); while (m_unsentMessages.Count > 0) { OutgoingNetMessage msg = m_unsentMessages.Peek(); int estimatedMessageSize = msg.m_data.LengthBytes + 5; // check if this message fits the throttle window if (estimatedMessageSize > maxSendBytes) // TODO: Allow at last one message if no debt { break; } // need to send packet and start a new one? if (messagesInPacket > 0) { if (!useCoalescing || (sendBuffer.LengthBytes + estimatedMessageSize > mtu)) { m_owner.SendPacket(m_remoteEndPoint); int sendLen = sendBuffer.LengthBytes; m_statistics.CountPacketSent(sendLen); //LogWrite("THROTTLE Send packet +" + sendLen + " bytes = " + m_throttleDebt + " (maxSendBytes " + maxSendBytes + " estimated " + estimatedMessageSize + ")"); m_throttleDebt += sendLen; sendBuffer.Reset(); } } if (msg.m_sequenceNumber == -1) { AssignSequenceNumber(msg); } // pop and encode message m_unsentMessages.Dequeue(); int pre = sendBuffer.m_bitLength; msg.m_data.m_readPosition = 0; msg.Encode(sendBuffer); int encLen = (sendBuffer.m_bitLength - pre) / 8; m_statistics.CountMessageSent(msg, encLen); maxSendBytes -= encLen; if (msg.m_sequenceChannel >= NetChannel.ReliableUnordered) { // reliable; store message (incl. buffer) msg.m_numSent++; StoreMessage(now, msg); } else { // not reliable, don't store - recycle... NetBuffer b = msg.m_data; b.m_refCount--; msg.m_data = null; // ... unless someone else is using the buffer if (b.m_refCount <= 0) { m_owner.RecycleBuffer(b); } //m_owner.m_messagePool.Push(msg); } messagesInPacket++; } // send current packet if (messagesInPacket > 0) { m_owner.SendPacket(m_remoteEndPoint); int sendLen = sendBuffer.LengthBytes; m_statistics.CountPacketSent(sendLen); //LogWrite("THROTTLE Send packet +" + sendLen + " bytes = " + m_throttleDebt); m_throttleDebt += sendLen; } }
static unsafe void Main(string[] args) { // JIT stuff NetBuffer msg = new NetBuffer(20); msg.Write((short)short.MaxValue); // Go double timeStart = NetTime.Now; msg = new NetBuffer(20); for (int n = 0; n < 10000; n++) { msg.Reset(); msg.Write((short)short.MaxValue); msg.Write((short)short.MinValue); msg.Write((short)-42); msg.Write(421); msg.Write((byte)7); msg.Write(-42.8f); if (msg.LengthBytes != 15) throw new Exception("Bad message length"); msg.Write("duke of earl"); int bytesWritten; bytesWritten = msg.WriteVariableInt32(-1); bytesWritten = msg.WriteVariableInt32(5); bytesWritten = msg.WriteVariableInt32(-18); bytesWritten = msg.WriteVariableInt32(42); bytesWritten = msg.WriteVariableInt32(-420); msg.Write((uint)9991); // byte boundary kept until here msg.Write(true); msg.Write((uint)3, 5); msg.Write(8.111f); msg.Write("again"); byte[] arr = new byte[] { 1, 6, 12, 24 }; msg.Write(arr); msg.Write((byte)7, 7); msg.Write(Int32.MinValue); msg.Write(UInt32.MaxValue); msg.WriteRangedSingle(21.0f, -10, 50, 12); // test reduced bit signed writing msg.Write(15, 5); msg.Write(2, 5); msg.Write(0, 5); msg.Write(-1, 5); msg.Write(-2, 5); msg.Write(-15, 5); msg.Write(UInt64.MaxValue); msg.Write(Int64.MaxValue); msg.Write(Int64.MinValue); msg.Write(42); msg.WritePadBits(); int numBits = msg.WriteRangedInteger(0, 10, 5); if (numBits != 4) throw new Exception("Ack WriteRangedInteger failed"); // verify msg.Position = 0; short a = msg.ReadInt16(); short b = msg.ReadInt16(); short c = msg.ReadInt16(); if (a != short.MaxValue || b != short.MinValue || c != -42) throw new Exception("Ack thpth short failed"); if (msg.ReadInt32() != 421) throw new Exception("Ack thphth 1"); if (msg.ReadByte() != (byte)7) throw new Exception("Ack thphth 2"); if (msg.ReadSingle() != -42.8f) throw new Exception("Ack thphth 3"); if (msg.ReadString() != "duke of earl") throw new Exception("Ack thphth 4"); if (msg.ReadVariableInt32() != -1) throw new Exception("ReadVariableInt32 failed 1"); if (msg.ReadVariableInt32() != 5) throw new Exception("ReadVariableInt32 failed 2"); if (msg.ReadVariableInt32() != -18) throw new Exception("ReadVariableInt32 failed 3"); if (msg.ReadVariableInt32() != 42) throw new Exception("ReadVariableInt32 failed 4"); if (msg.ReadVariableInt32() != -420) throw new Exception("ReadVariableInt32 failed 5"); if (msg.ReadUInt32() != 9991) throw new Exception("Ack thphth 4.5"); if (msg.ReadBoolean() != true) throw new Exception("Ack thphth 5"); if (msg.ReadUInt32(5) != (uint)3) throw new Exception("Ack thphth 6"); if (msg.ReadSingle() != 8.111f) throw new Exception("Ack thphth 7"); if (msg.ReadString() != "again") throw new Exception("Ack thphth 8"); byte[] rrr = msg.ReadBytes(4); if (rrr[0] != arr[0] || rrr[1] != arr[1] || rrr[2] != arr[2] || rrr[3] != arr[3]) throw new Exception("Ack thphth 9"); if (msg.ReadByte(7) != 7) throw new Exception("Ack thphth 10"); if (msg.ReadInt32() != Int32.MinValue) throw new Exception("Ack thphth 11"); if (msg.ReadUInt32() != UInt32.MaxValue) throw new Exception("Ack thphth 12"); float v = msg.ReadRangedSingle(-10, 50, 12); // v should be close to, but not necessarily exactly, 21.0f if ((float)Math.Abs(21.0f - v) > 0.1f) throw new Exception("Ack thphth *RangedSingle() failed"); if (msg.ReadInt32(5) != 15) throw new Exception("Ack thphth ReadInt32 1"); if (msg.ReadInt32(5) != 2) throw new Exception("Ack thphth ReadInt32 2"); if (msg.ReadInt32(5) != 0) throw new Exception("Ack thphth ReadInt32 3"); if (msg.ReadInt32(5) != -1) throw new Exception("Ack thphth ReadInt32 4"); if (msg.ReadInt32(5) != -2) throw new Exception("Ack thphth ReadInt32 5"); if (msg.ReadInt32(5) != -15) throw new Exception("Ack thphth ReadInt32 6"); UInt64 longVal = msg.ReadUInt64(); if (longVal != UInt64.MaxValue) throw new Exception("Ack thphth UInt64"); if (msg.ReadInt64() != Int64.MaxValue) throw new Exception("Ack thphth Int64"); if (msg.ReadInt64() != Int64.MinValue) throw new Exception("Ack thphth Int64"); if (msg.ReadInt32() != 42) throw new Exception("Ack thphth end"); msg.SkipPadBits(); if (msg.ReadRangedInteger(0, 10) != 5) throw new Exception("Ack thphth ranged integer"); } // test writevariableuint64 NetBuffer largeBuffer = new NetBuffer(100 * 8); UInt64[] largeNumbers = new ulong[100]; for (int i = 0; i < 100; i++) { largeNumbers[i] = ((ulong)NetRandom.Instance.NextUInt() << 32) | (ulong)NetRandom.Instance.NextUInt(); largeBuffer.WriteVariableUInt64(largeNumbers[i]); } largeBuffer.Position = 0; for (int i = 0; i < 100; i++) { UInt64 ln = largeBuffer.ReadVariableUInt64(); if (ln != largeNumbers[i]) throw new Exception("large fail"); } // // Extended tests on padbits // for (int i = 1; i < 31; i++) { NetBuffer buf = new NetBuffer(); buf.Write((int)1, i); if (buf.LengthBits != i) throw new Exception("Bad length!"); buf.WritePadBits(); int wholeBytes = buf.LengthBits / 8; if (wholeBytes * 8 != buf.LengthBits) throw new Exception("WritePadBits failed! Length is " + buf.LengthBits); } NetBuffer small = new NetBuffer(100); byte[] rnd = new byte[24]; int[] bits = new int[24]; for (int i = 0; i < 24; i++) { rnd[i] = (byte)NetRandom.Instance.Next(0, 65); bits[i] = NetUtility.BitsToHoldUInt((uint)rnd[i]); small.Write(rnd[i], bits[i]); } small.Position = 0; for (int i = 0; i < 24; i++) { byte got = small.ReadByte(bits[i]); if (got != rnd[i]) throw new Exception("Failed small allocation test"); } double timeEnd = NetTime.Now; double timeSpan = timeEnd - timeStart; Console.WriteLine("Trivial tests passed in " + (timeSpan * 1000.0) + " milliseconds"); Console.WriteLine("Creating client and server for live testing..."); NetConfiguration config = new NetConfiguration("unittest"); config.Port = 14242; NetServer server = new NetServer(config); NetBuffer serverBuffer = new NetBuffer(); server.Start(); config = new NetConfiguration("unittest"); NetClient client = new NetClient(config); client.SetMessageTypeEnabled(NetMessageType.Receipt, true); NetBuffer clientBuffer = client.CreateBuffer(); client.Start(); client.Connect("127.0.0.1", 14242); List<string> events = new List<string>(); double end = double.MaxValue; double disconnect = double.MaxValue; while (NetTime.Now < end) { double now = NetTime.Now; NetMessageType nmt; NetConnection sender; // // client // if (client.ReadMessage(clientBuffer, out nmt)) { switch (nmt) { case NetMessageType.StatusChanged: Console.WriteLine("Client: " + client.Status + " (" + clientBuffer.ReadString() + ")"); events.Add("CStatus " + client.Status); if (client.Status == NetConnectionStatus.Connected) { // send reliable message NetBuffer buf = client.CreateBuffer(); buf.Write(true); buf.Write((int)52, 7); buf.Write("Hallon"); client.SendMessage(buf, NetChannel.ReliableInOrder1, new NetBuffer("kokos")); } if (client.Status == NetConnectionStatus.Disconnected) end = NetTime.Now + 1.0; // end in one second break; case NetMessageType.Receipt: events.Add("CReceipt " + clientBuffer.ReadString()); break; case NetMessageType.ConnectionRejected: case NetMessageType.BadMessageReceived: throw new Exception("Failed: " + nmt); case NetMessageType.DebugMessage: // silently ignore break; default: // ignore Console.WriteLine("Ignored: " + nmt); break; } } // // server // if (server.ReadMessage(serverBuffer, out nmt, out sender)) { switch (nmt) { case NetMessageType.StatusChanged: events.Add("SStatus " + sender.Status); Console.WriteLine("Server: " + sender.Status + " (" + serverBuffer.ReadString() + ")"); break; case NetMessageType.ConnectionRejected: case NetMessageType.BadMessageReceived: throw new Exception("Failed: " + nmt); case NetMessageType.Data: events.Add("DataRec " + serverBuffer.LengthBits); bool shouldBeTrue = serverBuffer.ReadBoolean(); int shouldBeFifthTwo = serverBuffer.ReadInt32(7); string shouldBeHallon = serverBuffer.ReadString(); if (shouldBeTrue != true || shouldBeFifthTwo != 52 || shouldBeHallon != "Hallon") throw new Exception("Bad data transmission"); disconnect = now + 1.0; break; case NetMessageType.DebugMessage: // silently ignore break; default: // ignore Console.WriteLine("Ignored: " + nmt); break; } } if (now > disconnect) { server.Connections[0].Disconnect("Bye", 0.1f); disconnect = double.MaxValue; } } // verify events string[] expected = new string[] { "CStatus Connecting", "SStatus Connecting", "CStatus Connected", "SStatus Connected", "DataRec 64", "CReceipt kokos", "SStatus Disconnecting", "CStatus Disconnecting", "SStatus Disconnected", "CStatus Disconnected" }; if (events.Count != expected.Length) throw new Exception("Mismatch in events count! Expected " + expected.Length + ", got " + events.Count); for(int i=0;i<expected.Length;i++) { if (events[i] != expected[i]) throw new Exception("Event " + i + " (" + expected[i] + ") mismatched!"); } Console.WriteLine("All tests successful"); Console.ReadKey(); server.Shutdown("App exiting"); client.Shutdown("App exiting"); }
private void ReceivePackets(double now) { int networkResets = 0; int connectionResets = 0; int availableData = 0; try { for (;;) { try { if (m_socket == null) { return; } availableData = m_socket.availableData; if (availableData <= 0) { return; } m_receiveBuffer.Reset(); double localTimeRecv; int bytesReceived = m_socket.ReceivePacket(m_receiveBuffer.Data, 0, m_receiveBuffer.Data.Length, out m_senderRemote, out localTimeRecv); if (bytesReceived <= 0) { continue; } m_statistics.CountPacketReceived(bytesReceived); m_receiveBuffer.LengthBits = bytesReceived * 8; var ipsender = (NetworkEndPoint)m_senderRemote; if (localTimeRecv == 0) { localTimeRecv = NetTime.Now; } // lookup the endpoint (if we have a connection) var sender = GetConnection(ipsender); if (sender != null) { sender.m_statistics.CountPacketReceived(bytesReceived, localTimeRecv); sender.m_lastPongReceived = localTimeRecv; // TODO: fulhack! } // try parsing the packet try { // create messages from packet while (m_receiveBuffer.PositionBits < m_receiveBuffer.LengthBits) { int beginPosition = m_receiveBuffer.PositionBits; // read message header var msg = CreateIncomingMessage(); msg.m_sender = sender; if (!msg.ReadFrom(m_receiveBuffer, ipsender)) { break; } // statistics if (sender != null) { sender.m_statistics.CountMessageReceived(msg.m_type, msg.m_sequenceChannel, (m_receiveBuffer.PositionBits - beginPosition) / 8, now); } // handle message HandleReceivedMessage(msg, ipsender, localTimeRecv); } } catch (Exception e) // silent exception handlers make debugging impossible, comment out when you wonder "WTF is happening???" { // TODO: fix so logging doesn't allocate an argument array (for using params object[]). // Enabling us to hopefully do to log calls without overhead, if the log level isn't on. #if DEBUG // NOTE: in production it might not be desirable to log bad packets, because of targeted DoS attacks. Log.Error(LogFlags.Socket, "Failed parsing an incoming packet from ", ipsender, ": ", e); #endif } } catch (SocketException sex) { if (sex.ErrorCode == 10040 || sex.SocketErrorCode == SocketError.MessageSize) { int bufferSize = m_receiveBuffer.Data.Length; Log.Error(LogFlags.Socket, "Failed to read available data (", availableData, " bytes) because it's larger than the receive buffer (", bufferSize, " bytes). This might indicate that the socket receive buffer size is not correctly configured: ", sex); } if (sex.ErrorCode == 10052 || sex.SocketErrorCode == SocketError.NetworkReset) { networkResets++; if (networkResets > 2000) { break; } } else if (sex.ErrorCode == 10054 || sex.SocketErrorCode == SocketError.ConnectionReset) { connectionResets++; if (connectionResets > 2000) { break; } } else { throw; } } } } catch (SocketException sex) { if (sex.ErrorCode == 10035 || sex.SocketErrorCode == SocketError.WouldBlock) { // NOTE: add independent ability to log warnings instead of this hack. Log.Warning(LogFlags.Socket, "Receive Buffer is empty, ignore error: ", sex.Message); return; } throw new NetException(NetTime.Now + " (" + DateTime.Now + "): Could not receive packet", sex); } catch (Exception ex) { throw new NetException(NetTime.Now + " (" + DateTime.Now + "): Could not receive packet", ex); } finally { if (networkResets > 0) { Log.Debug(LogFlags.Socket, NetTime.Now, " (", DateTime.Now, "): Ignored number of socket network reset errors: ", networkResets); } if (connectionResets > 0) { Log.Debug(LogFlags.Socket, NetTime.Now, " (", DateTime.Now, "): Ignored number of socket connection reset errors: ", connectionResets); } } }
internal NetBuffer GetTempBuffer() { m_tempBuffer.Reset(); return(m_tempBuffer); }