/// <summary> /// Sends a packet via Steamworks; Returns whether or not the packed could be sent /// </summary> public bool SendPacket(SteamId id, byte[] data, int len = -1, P2PChannel channel = P2PChannel.RELIABLE) { if (len > MP_PACKET_MAX || data.Length > MP_PACKET_MAX) { Output.LogWarning($"{Name}: Attempted to send a packet which exceeded MP_MAX_SIZE!"); return(false); } if (channel < 0 || channel >= P2PChannel.NUM_CHANNELS) { Output.LogWarning($"{Name}: Invalid send channel specified, defaulting to Reliable"); return(SteamNetworking.SendP2PPacket(id, data, len, (int)P2PChannel.RELIABLE, P2PSend.Reliable)); } // Cast channel to send type P2PSend send = (P2PSend)channel; return(SteamNetworking.SendP2PPacket(id, data, len, (int)channel, send)); }
protected override void HandleConnectionFailed(SteamId id, P2PSessionError error) { try { if (!id.IsValid) { throw new Exception("An invalid SteamId is reporting connection failure"); } if (!PlayerRegistry.Contains(id)) { throw new Exception("Received connection failure message from a peer that isn't in the player registry"); } int conn = PlayerRegistry.GetConnId(id); // If no connection id could be found for that SteamId if (conn == -1) { throw new Exception($"Invalid connection id for SteamId {id}, can't disconnect"); } string info = P2PErrorToString(error); Exception ex = new Exception($"Connection failed with host {id} ({info})"); Output.LogError(ex.Message); PlayerRegistry.Remove(id); CloseSessionWithUser(id); // Raise OnError event OnError?.Invoke(conn, ex); } catch (Exception e) { Output.LogWarning($"{Name}: Exception in Server.HandleConnectionFailed ({e.Message})"); return; } }
protected override void HandleConnectionFailed(SteamId id, P2PSessionError error) { try { if (!id.IsValid) { throw new Exception("An invalid SteamId is reporting connection failure"); } if (!HostId.IsValid) { throw new Exception($"Connection failed with {id}, but we didn't have a host anyway..."); } if (id != HostId) { throw new Exception($"Connection failed with {id}, but they weren't our host"); } string info = P2PErrorToString(error); Exception ex = new Exception($"Connection failed with host {id} ({info})"); Output.LogError(ex.Message); _host = new SteamId(); CloseSessionWithUser(id); ResetStatus(); // Raise OnError event OnError?.Invoke(ex); } catch (Exception e) { Output.LogWarning($"{Name}: Exception in Client.HandleConnectionFailed ({e.Message})"); return; } }
/// <summary> /// Static method to process any messages in the receive queue /// </summary> public static bool ProcessMessages(MirrorpunchCommon a) { if (!a.Active) { return(false); // Don't process messages if inactive } if (!a.ReceiveQueue.HasNodes) { return(true); // Only return false if something goes wrong } DateTime timer = DateTime.Now; try { // Want to avoid a potential infinite loop, so only looping through a known number of nodes for (int i = 0; i < a.ReceiveQueue.Length; i++) { P2Packet?packet = a.ReceiveQueue.Dequeue(); if (packet == null) { continue; } SteamId senderId = packet.Value.SteamId; if (packet.Value.Data == null || packet.Value.Data.Length == 0) { continue; } if (!a.IsKnown(senderId)) { byte byteType = packet.Value.Data[0]; if (byteType < 0 || byteType >= (byte)PacketType.NUM_TYPES) { throw new Exception("Packet with invalid PacketType received from unexpected SteamId; check sending code?"); } PacketType packetType = (PacketType)byteType; switch (packetType) { case PacketType.CONNECT: Output.Log($"Connection requested by {senderId}"); a.OnSessionRequest(senderId); break; case PacketType.CONNECTION_ACCEPTED: Output.Log($"Connection accepted by {senderId}"); break; default: throw new Exception($"Unexpected packet type received from {senderId} [{byteType}]"); } // Packet from unknown sender handled without exception, continue continue; } // Received a packet from a known sender, handle received data byte[] data = packet.Value.Data; a.OnReceivedData(senderId, data); } TimeSpan checkTimer = DateTime.Now - timer; if (checkTimer.TotalMilliseconds >= MP_PROCESS_WARNING) { Output.LogWarning($"{a.Name}: ProcessMessages took longer than expected ({checkTimer.TotalMilliseconds} ms)"); } // We made it here without an exception, report success return(true); } catch (Exception e) { Output.LogError($"{a.Name}: Exception in ProcessMessages ({e.Message})"); return(false); } }
/// <summary> /// Static loop method for receiving Steam P2P packets and enqueuing them in the receive queue /// </summary> public static async Task ReceiveLoop(MirrorpunchCommon a) { if (!a.Active) { return; // Don't bother with receive loop if inactive } bool warned = false; DateTime timeWarned = DateTime.Now; try { while (a.Active) { for (int i = 0; i < (int)P2PChannel.NUM_CHANNELS; i++) { if (!a.GetNextPacket(out P2Packet? packet, (P2PChannel)i)) { continue; } if (packet == null) { continue; } int packetLen = packet.Value.Data.Length; if (packetLen >= MP_PACKET_MAX) { Output.LogWarning($"{a.Name}: Received a packet that is too large ({packetLen})"); continue; } a.EnqueuePacket((P2Packet)packet); } if (!warned && a.ReceiveQueue.Length > MP_QUEUE_SIZE_WARNING) { Output.LogWarning($"{a.Name}: ReceiveQueue is backing up ({a.ReceiveQueue.Length} packets queued)"); timeWarned = DateTime.Now; } if (warned) { TimeSpan timeSinceWarning = DateTime.Now - timeWarned; if (timeSinceWarning.TotalSeconds > MP_QUEUE_WARNING_TIMEOUT) { warned = false; } } await Task.Delay(TimeSpan.FromMilliseconds(TickRate)); } // Return if behavior is no longer active return; } catch (Exception e) { Output.LogError($"{a.Name}: Exception raised in ReceiveLoop ({e.Message})"); return; // Return on any exception } }
public async void ConnectToId(SteamId hostId) { // Refuse to attempt another connection if one is active or in progress if (Active || Status == PeerStatus.CONNECTING) { Output.LogWarning($"{Name}: Attempted to connect while a previous connection is active or in progress"); return; } // Set timeout start time, set client active, and set client status to Connecting DateTime timeoutStart = DateTime.Now; // Set status appropriately _active = true; _status = PeerStatus.CONNECTING; try { // Send a request to connect byte[] sendData = new byte[] { (byte)PacketType.CONNECT }; SendPacket(hostId, sendData, sendData.Length); while (Status == PeerStatus.CONNECTING) { // Wait for a packet to arrive while (!IsPacketAvailable()) { TimeSpan timeoutChk = DateTime.Now - timeoutStart; if (timeoutChk.TotalMilliseconds >= MP_TIMEOUT) { throw new Exception("Timed out attempting to connect"); } await Task.Delay(TimeSpan.FromMilliseconds(TickRate)); } GetNextPacket(out P2Packet? packet); if (packet == null) { throw new Exception("Null packet received"); } // If the packet is not null, get data and sender SteamId byte[] data = packet.Value.Data; SteamId sender = packet.Value.SteamId; if (data.Length < 1 || data == null) { throw new Exception("Null or zero-length data array received"); } // Ignore packets sent by someone who isn't expected if (sender != hostId) { continue; } if (data[0] != (byte)PacketType.CONNECTION_ACCEPTED) { throw new Exception($"Unexpected response ({data[0]})"); } // Now connected to a new server, set status and refresh receive queue _status = PeerStatus.CONNECTED; _receiveQueue.Clear(); // Raise OnConnected event OnConnected?.Invoke(); } _host = hostId; } catch (Exception e) { Output.LogError($"{Name}: Error connecting to host ({e.Message})"); // If we catch an exception, reset active and status ResetStatus(); } }