Add connection timeout using server keepalives

Vanilla client will consider that connection has been lost
when no server keepalive was received during the last 30 seconds.
This commit implements a similar mechanism in MCC. See #802
This commit is contained in:
ORelio 2019-09-15 17:01:53 +02:00
parent 1406c00abd
commit 877e50579d
4 changed files with 61 additions and 6 deletions

View file

@ -52,6 +52,8 @@ namespace MinecraftClient
private string uuid; private string uuid;
private string sessionid; private string sessionid;
private Inventory playerInventory; private Inventory playerInventory;
private DateTime lastKeepAlive;
private object lastKeepAliveLock = new object();
public int GetServerPort() { return port; } public int GetServerPort() { return port; }
public string GetServerHost() { return host; } public string GetServerHost() { return host; }
@ -64,6 +66,7 @@ namespace MinecraftClient
TcpClient client; TcpClient client;
IMinecraftCom handler; IMinecraftCom handler;
Thread cmdprompt; Thread cmdprompt;
Thread timeoutdetector;
/// <summary> /// <summary>
/// Starts the main chat client /// Starts the main chat client
@ -167,6 +170,10 @@ namespace MinecraftClient
cmdprompt = new Thread(new ThreadStart(CommandPrompt)); cmdprompt = new Thread(new ThreadStart(CommandPrompt));
cmdprompt.Name = "MCC Command prompt"; cmdprompt.Name = "MCC Command prompt";
cmdprompt.Start(); cmdprompt.Start();
timeoutdetector = new Thread(new ThreadStart(TimeoutDetector));
timeoutdetector.Name = "MCC Connection timeout detector";
timeoutdetector.Start();
} }
} }
} }
@ -253,6 +260,29 @@ namespace MinecraftClient
catch (NullReferenceException) { } catch (NullReferenceException) { }
} }
/// <summary>
/// Periodically checks for server keepalives and consider that connection has been lost if the last received keepalive is too old.
/// </summary>
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);
}
/// <summary> /// <summary>
/// Perform an internal MCC command (not a server command, use SendText() instead for that!) /// Perform an internal MCC command (not a server command, use SendText() instead for that!)
/// </summary> /// </summary>
@ -336,6 +366,9 @@ namespace MinecraftClient
if (cmdprompt != null) if (cmdprompt != null)
cmdprompt.Abort(); cmdprompt.Abort();
if (timeoutdetector != null)
timeoutdetector.Abort();
Thread.Sleep(1000); Thread.Sleep(1000);
if (client != null) if (client != null)
@ -607,6 +640,10 @@ namespace MinecraftClient
/// <param name="links">Links embedded in text</param> /// <param name="links">Links embedded in text</param>
public void OnTextReceived(string text, bool isJson) public void OnTextReceived(string text, bool isJson)
{ {
lock (lastKeepAliveLock)
{
lastKeepAlive = DateTime.Now;
}
List<string> links = new List<string>(); List<string> links = new List<string>();
string json = null; string json = null;
if (isJson) if (isJson)
@ -637,11 +674,22 @@ namespace MinecraftClient
} }
} }
/// <summary>
/// Received a connection keep-alive from the server
/// </summary>
public void OnServerKeepAlive()
{
lock (lastKeepAliveLock)
{
lastKeepAlive = DateTime.Now;
}
}
/// <summary> /// <summary>
/// When an inventory is opened /// When an inventory is opened
/// </summary> /// </summary>
/// <param name="inventory">Location to reach</param> /// <param name="inventory">Location to reach</param>
public void onInventoryOpen(Inventory inventory) public void OnInventoryOpen(Inventory inventory)
{ {
//TODO: Handle Inventory //TODO: Handle Inventory
if (!inventories.Contains(inventory)) if (!inventories.Contains(inventory))
@ -654,7 +702,7 @@ namespace MinecraftClient
/// When an inventory is close /// When an inventory is close
/// </summary> /// </summary>
/// <param name="inventoryID">Location to reach</param> /// <param name="inventoryID">Location to reach</param>
public void onInventoryClose(byte inventoryID) public void OnInventoryClose(byte inventoryID)
{ {
for (int i = 0; i < inventories.Count; i++) for (int i = 0; i < inventories.Count; i++)
{ {

View file

@ -83,6 +83,7 @@ namespace MinecraftClient.Protocol.Handlers
{ {
case 0x00: byte[] keepalive = new byte[5] { 0, 0, 0, 0, 0 }; case 0x00: byte[] keepalive = new byte[5] { 0, 0, 0, 0, 0 };
Receive(keepalive, 1, 4, SocketFlags.None); Receive(keepalive, 1, 4, SocketFlags.None);
handler.OnServerKeepAlive();
Send(keepalive); break; Send(keepalive); break;
case 0x01: readData(4); readNextString(); readData(5); break; case 0x01: readData(4); readNextString(); readData(5); break;
case 0x02: readData(1); readNextString(); readNextString(); readData(4); break; case 0x02: readData(1); readNextString(); readNextString(); readData(4); break;

View file

@ -185,6 +185,7 @@ namespace MinecraftClient.Protocol.Handlers
{ {
case PacketIncomingType.KeepAlive: case PacketIncomingType.KeepAlive:
SendPacket(PacketOutgoingType.KeepAlive, packetData); SendPacket(PacketOutgoingType.KeepAlive, packetData);
handler.OnServerKeepAlive();
break; break;
case PacketIncomingType.JoinGame: case PacketIncomingType.JoinGame:
handler.OnGameJoined(); handler.OnGameJoined();
@ -481,7 +482,7 @@ namespace MinecraftClient.Protocol.Handlers
byte slots = dataTypes.ReadNextByte(packetData); byte slots = dataTypes.ReadNextByte(packetData);
Inventory inventory = new Inventory(windowID, inventoryType, title, slots); Inventory inventory = new Inventory(windowID, inventoryType, title, slots);
handler.onInventoryOpen(inventory); handler.OnInventoryOpen(inventory);
} }
break; break;
case PacketIncomingType.CloseWindow: case PacketIncomingType.CloseWindow:
@ -489,7 +490,7 @@ namespace MinecraftClient.Protocol.Handlers
{ {
byte windowID = dataTypes.ReadNextByte(packetData); byte windowID = dataTypes.ReadNextByte(packetData);
handler.onInventoryClose(windowID); handler.OnInventoryClose(windowID);
} }
break; break;
case PacketIncomingType.WindowItems: case PacketIncomingType.WindowItems:

View file

@ -43,15 +43,20 @@ namespace MinecraftClient.Protocol
/// <param name="isJson">TRUE if the text is JSON-Encoded</param> /// <param name="isJson">TRUE if the text is JSON-Encoded</param>
void OnTextReceived(string text, bool isJson); void OnTextReceived(string text, bool isJson);
/// <summary>
/// Called when receiving a connection keep-alive from the server
/// </summary>
void OnServerKeepAlive();
/// <summary> /// <summary>
/// Called when an inventory is opened /// Called when an inventory is opened
/// </summary> /// </summary>
void onInventoryOpen(Inventory inventory); void OnInventoryOpen(Inventory inventory);
/// <summary> /// <summary>
/// Called when an inventory is closed /// Called when an inventory is closed
/// </summary> /// </summary>
void onInventoryClose(byte inventoryID); void OnInventoryClose(byte inventoryID);
/// <summary> /// <summary>
/// Called when the player respawns, which happens on login, respawn and world change. /// Called when the player respawns, which happens on login, respawn and world change.