/// <summary> /// Sends 'EndSession' command to client to inform them of opponent disconnect. /// Waits for client acknowledgment, but doesn't do anything with it. /// </summary> private void SendEndSessionCommand(Client client, NetworkPlayer player) { try { //vars uint timeoutMs = 1000; ushort sequence = (ushort)(UInt32.MaxValue - _random.Next(1000)); CommandResult result; //message Log.Write($"Sending 'EndSession' request to '{client.RemoteIP}'"); //create packet CommandRequestPacket packet = new CommandRequestPacket( gameTitle: player.GameTitle, gameVersion: player.GameVersion, sourceIP: _ip, destinationIP: player.IP, playerName: player.Name, commandType: CommandType.EndSession, sequence: sequence, retryAttempt: 0, timeoutMs: timeoutMs, data: null); //record command request being sent _commandManager.RequestSent(packet); //forward request to destination client.SendPacket(packet); //wait for response or timeout while (true) { //get current status result = _commandManager.GetCommandStatus(packet.Sequence); //have answer or timeout? return! if (result.Code != ResultCode.Unspecified) { return; } //sleep Thread.Sleep(15); } } catch (Exception ex) { Log.Write("SendEndSessionCommand: Error processing expired session"); ErrorHandler.LogError(ex); } }
/// <summary> /// Answers a 'GetPlayers' request, responds with a list of connected players (of same game). /// </summary> private void Answer_GetPlayers(Client sourceClient, CommandRequestPacket packet) { try { //get source player NetworkPlayer player = NetworkPlayer.FromPacket(packet); //get list of connected players (not source player) List <NetworkPlayer> otherPlayers = GetMatchingPlayers(player.GameTitle, player.GameVersion) .Where(p => p.PlayerKey != player.PlayerKey) .ToList(); //message Log.Write($"Sending '{packet.CommandType}' ({otherPlayers.Count} player{(otherPlayers.Count != 1 ? "s" : "")}) response to '{player.IP}'"); //serialize list to bytes PacketBuilder builder = new PacketBuilder(); builder.AddUInt16((ushort)otherPlayers.Count); foreach (NetworkPlayer p in otherPlayers) { builder.AddBytes(p.ToBytes()); } //create packet CommandResponsePacket response = new CommandResponsePacket( gameTitle: player.GameTitle, gameVersion: player.GameVersion, sourceIP: _ip, destinationIP: player.IP, playerName: player.Name, commandType: CommandType.GetPlayers, sequence: packet.Sequence, code: ResultCode.Accept, data: builder.ToBytes()); //send response back to source sourceClient.SendPacket(response); } catch (Exception ex) { Log.Write("Answer_GetPlayers: Error returning player list"); ErrorHandler.LogError(ex); } }
/// <summary> /// Answers a 'QuitGame' request, responds with acceptance. /// </summary> private void Answer_QuitGame(Client sourceClient, CommandRequestPacket packet) { try { //get source player NetworkPlayer player = NetworkPlayer.FromPacket(packet); player = GetPlayerByKey(player.PlayerKey); //set quit flag player.QuitGame = true; //remove expired players RemoveExpiredPlayers(); //remove expired sessions RemoveExpiredSessions(); //message Log.Write($"Sending '{packet.CommandType}' ({ResultCode.Accept}) response to '{player.IP}'"); //create packet CommandResponsePacket response = new CommandResponsePacket( gameTitle: player.GameTitle, gameVersion: player.GameVersion, sourceIP: _ip, destinationIP: player.IP, playerName: player.Name, commandType: CommandType.QuitGame, sequence: packet.Sequence, code: ResultCode.Accept, data: null); //send response back to source sourceClient.SendPacket(response); } catch (Exception ex) { Log.Write("Answer_QuitGame: Unknown error"); ErrorHandler.LogError(ex); } }
/// <summary> /// Records when a command request packet was sent, to create a state that can be tied back to a response. /// </summary> public void RequestSent(CommandRequestPacket packet) { lock (_commands) { Command command = _commands .Where(c => c.Sequence == packet.Sequence) .OrderBy(c => c.Elapsed) .FirstOrDefault(); if (command != null) { command.RetryAttempt = packet.RetryAttempt; command.StartTime = DateTime.Now; } else { command = new Command(packet.CommandType, packet.Sequence, TimeSpan.FromMilliseconds(packet.TimeoutMs)); _commands.Add(command); } } }
private void CommandRequestPacketHandle(CommandRequestPacket pk) { string command = pk.Command.Remove(0, 1); Server.Instance.CommandManager.HandlePlayerCommand(this, command); }
/// <summary> /// Forwards a command request packet to destination player, waits for response (or timeout), /// responds to source player with answer. /// </summary> private void Passthrough_Command(Client sourceClient, CommandRequestPacket requestPacket) { CommandResult result = new CommandResult(ResultCode.Unspecified); NetworkPlayer sourcePlayer = null; NetworkPlayer destinationPlayer = null; bool sessionEnded = false; try { //get source player sourcePlayer = NetworkPlayer.FromPacket(requestPacket); if (sourcePlayer == null) { Log.Write($"Passthrough_Command: Unable to parse packet"); result.Code = ResultCode.Error; return; } //get destination player destinationPlayer = GetMatchingPlayer(requestPacket.DestinationIP, requestPacket.GameTitle, requestPacket.GameVersion); if (destinationPlayer == null) { Log.Write($"Passthrough_Command: Cannot find destination player at '{requestPacket.DestinationIP}'"); result.Code = ResultCode.Error; return; } //create session? if (requestPacket.CommandType == CommandType.ConnectToPlayer) { CreateSession(sourcePlayer.PlayerKey, destinationPlayer.PlayerKey); } //get session Session session = GetSession(sourcePlayer.PlayerKey, destinationPlayer.PlayerKey); if ((session == null) && (requestPacket.CommandType != CommandType.ConnectToPlayer)) { Log.Write($"Passthrough_Command: Live session does not exist between '{sourcePlayer.IP}' and '{destinationPlayer.IP}'"); result.Code = ResultCode.Error; sessionEnded = true; return; } //get destination client Client destinationClient = GetClientByPlayer(destinationPlayer); if (destinationClient == null) { Log.Write($"Passthrough_Command: Destination player at {destinationClient.RemoteIP} does not have assigned TCP client"); result.Code = ResultCode.Error; return; } //message Log.Write($"Forwarding '{requestPacket.CommandType}' request from '{sourcePlayer.IP}' to '{destinationPlayer.IP}'"); //record command request being sent _commandManager.RequestSent(requestPacket); //forward request to destination destinationClient.SendPacket(requestPacket); //wait for response or timeout while (true) { //get current status result = _commandManager.GetCommandStatus(requestPacket.Sequence); //have answer or timeout? return! if (result.Code != ResultCode.Unspecified) { return; } //sleep Thread.Sleep(15); } } catch (Exception ex) { Log.Write("Passthrough_Command: Error forwarding request or waiting for response"); ErrorHandler.LogError(ex); } finally { try { //create response packet CommandResponsePacket responsePacket; if ((result.Code.In(ResultCode.Accept, ResultCode.Reject)) && (result.ResponsePacket != null)) { ////create session? //if ((requestPacket.CommandType == CommandType.ConnectToPlayer) && (result.Code == ResultCode.Accept)) // CreateSession(sourcePlayer.UniqueKey, destinationPlayer.UniqueKey); //confirm session? if ((requestPacket.CommandType == CommandType.ConnectToPlayer) && (result.Code == ResultCode.Accept)) { ConfirmSession(sourcePlayer.PlayerKey, destinationPlayer.PlayerKey); } //get original packet responsePacket = result.ResponsePacket; //message Log.Write($"Forwarding '{result.ResponsePacket.CommandType}' ({result.ResponsePacket.Code}) response from '{destinationPlayer.IP}' to '{sourcePlayer.IP}'"); } else { //create new timeout/error packet responsePacket = new CommandResponsePacket( gameTitle: requestPacket.GameTitle, gameVersion: requestPacket.GameVersion, sourceIP: requestPacket.DestinationIP, destinationIP: requestPacket.SourceIP, playerName: "", commandType: requestPacket.CommandType, sequence: requestPacket.Sequence, code: result.Code, data: null); //message Log.Write($"Sending '{responsePacket.CommandType}' ({responsePacket.Code}) response to '{sourceClient.RemoteIP}'"); } //send response to source sourceClient.SendPacket(responsePacket); //send session-ended command? if (sessionEnded) { SendEndSessionCommand(sourceClient, sourcePlayer); } } catch (Exception ex) { Log.Write("Passthrough_Command: Error sending command response to source"); ErrorHandler.LogError(ex); } } }
/// <summary> /// Sends command request, blocks until response or timeout. Data is optional. /// </summary> public CommandResult SendCommandRequest(IPAddress destinationIP, CommandType type, byte[] data, TimeSpan timeout) { try { //disabled? if (_connectionState == ConnectionState.Disabled) { return(new CommandResult(ResultCode.Error)); } //vars int sequence; lock (this) { sequence = _random.Next(Int32.MinValue, Int32.MaxValue); } //create packet CommandRequestPacket packet = new CommandRequestPacket( gameTitle: _config.GameTitle, gameVersion: _config.GameVersion, sourceIP: _config.LocalIP, destinationIP: destinationIP, playerName: _localPlayer.Name, commandType: type, sequence: sequence, retryAttempt: 0, timeoutMs: (uint)timeout.TotalMilliseconds, data: data); //record command request _commandManager.RequestSent(packet); //send packet _client.SendPacket(packet); _commandRequestsSent++; //loop while (true) { //get current status CommandResult result = _commandManager.GetCommandStatus(sequence); //have answer or timeout? return! if (result.Code != ResultCode.Unspecified) { return(result); } //sleep Thread.Sleep(15); } } catch (Exception ex) { _tcpErrors++; Log.Write("SendCommandRequest: Error sending data"); ErrorHandler.LogError(ex); return(new CommandResult(ResultCode.Error)); } }