diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 31c6a451..8e613e86 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -52,6 +52,8 @@ namespace MinecraftClient private string uuid; private string sessionid; private Inventory playerInventory; + private DateTime lastKeepAlive; + private object lastKeepAliveLock = new object(); public int GetServerPort() { return port; } public string GetServerHost() { return host; } @@ -64,6 +66,7 @@ namespace MinecraftClient TcpClient client; IMinecraftCom handler; Thread cmdprompt; + Thread timeoutdetector; /// /// Starts the main chat client @@ -167,6 +170,10 @@ namespace MinecraftClient cmdprompt = new Thread(new ThreadStart(CommandPrompt)); cmdprompt.Name = "MCC Command prompt"; cmdprompt.Start(); + + timeoutdetector = new Thread(new ThreadStart(TimeoutDetector)); + timeoutdetector.Name = "MCC Connection timeout detector"; + timeoutdetector.Start(); } } } @@ -253,6 +260,29 @@ namespace MinecraftClient catch (NullReferenceException) { } } + /// + /// Periodically checks for server keepalives and consider that connection has been lost if the last received keepalive is too old. + /// + private void TimeoutDetector() + { + lock (lastKeepAliveLock) + { + lastKeepAlive = DateTime.Now; + } + do + { + Thread.Sleep(TimeSpan.FromSeconds(15)); + lock (lastKeepAliveLock) + { + if (lastKeepAlive.AddSeconds(30) < DateTime.Now) + { + OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, "Connection Timeout"); + } + } + } + while (true); + } + /// /// Perform an internal MCC command (not a server command, use SendText() instead for that!) /// @@ -336,6 +366,9 @@ namespace MinecraftClient if (cmdprompt != null) cmdprompt.Abort(); + if (timeoutdetector != null) + timeoutdetector.Abort(); + Thread.Sleep(1000); if (client != null) @@ -607,6 +640,10 @@ namespace MinecraftClient /// Links embedded in text public void OnTextReceived(string text, bool isJson) { + lock (lastKeepAliveLock) + { + lastKeepAlive = DateTime.Now; + } List links = new List(); string json = null; if (isJson) @@ -637,11 +674,22 @@ namespace MinecraftClient } } + /// + /// Received a connection keep-alive from the server + /// + public void OnServerKeepAlive() + { + lock (lastKeepAliveLock) + { + lastKeepAlive = DateTime.Now; + } + } + /// /// When an inventory is opened /// /// Location to reach - public void onInventoryOpen(Inventory inventory) + public void OnInventoryOpen(Inventory inventory) { //TODO: Handle Inventory if (!inventories.Contains(inventory)) @@ -654,7 +702,7 @@ namespace MinecraftClient /// When an inventory is close /// /// Location to reach - public void onInventoryClose(byte inventoryID) + public void OnInventoryClose(byte inventoryID) { for (int i = 0; i < inventories.Count; i++) { diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs index 65a7cf49..c8516766 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -83,6 +83,7 @@ namespace MinecraftClient.Protocol.Handlers { case 0x00: byte[] keepalive = new byte[5] { 0, 0, 0, 0, 0 }; Receive(keepalive, 1, 4, SocketFlags.None); + handler.OnServerKeepAlive(); Send(keepalive); break; case 0x01: readData(4); readNextString(); readData(5); break; case 0x02: readData(1); readNextString(); readNextString(); readData(4); break; diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 403267bb..119c1bb5 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -185,6 +185,7 @@ namespace MinecraftClient.Protocol.Handlers { case PacketIncomingType.KeepAlive: SendPacket(PacketOutgoingType.KeepAlive, packetData); + handler.OnServerKeepAlive(); break; case PacketIncomingType.JoinGame: handler.OnGameJoined(); @@ -481,7 +482,7 @@ namespace MinecraftClient.Protocol.Handlers byte slots = dataTypes.ReadNextByte(packetData); Inventory inventory = new Inventory(windowID, inventoryType, title, slots); - handler.onInventoryOpen(inventory); + handler.OnInventoryOpen(inventory); } break; case PacketIncomingType.CloseWindow: @@ -489,7 +490,7 @@ namespace MinecraftClient.Protocol.Handlers { byte windowID = dataTypes.ReadNextByte(packetData); - handler.onInventoryClose(windowID); + handler.OnInventoryClose(windowID); } break; case PacketIncomingType.WindowItems: diff --git a/MinecraftClient/Protocol/IMinecraftComHandler.cs b/MinecraftClient/Protocol/IMinecraftComHandler.cs index 3e2879de..0bcc0bcf 100644 --- a/MinecraftClient/Protocol/IMinecraftComHandler.cs +++ b/MinecraftClient/Protocol/IMinecraftComHandler.cs @@ -43,15 +43,20 @@ namespace MinecraftClient.Protocol /// TRUE if the text is JSON-Encoded void OnTextReceived(string text, bool isJson); + /// + /// Called when receiving a connection keep-alive from the server + /// + void OnServerKeepAlive(); + /// /// Called when an inventory is opened /// - void onInventoryOpen(Inventory inventory); + void OnInventoryOpen(Inventory inventory); /// /// Called when an inventory is closed /// - void onInventoryClose(byte inventoryID); + void OnInventoryClose(byte inventoryID); /// /// Called when the player respawns, which happens on login, respawn and world change.