diff --git a/GUI/GuiUtils.cs b/GUI/GuiUtils.cs index 7b0eb65d0..5b33b6080 100644 --- a/GUI/GuiUtils.cs +++ b/GUI/GuiUtils.cs @@ -22,6 +22,9 @@ using System.Windows.Forms; namespace MCGalaxy.Gui { + // NET 2.0 doesn't have Action delegate defined + public delegate void UIAction(); + /// Shortcuts for MessageBox.Show public static class Popup { diff --git a/GUI/PropertyWindow/PropertyWindow.cs b/GUI/PropertyWindow/PropertyWindow.cs index f68f02ed6..b5ad679e1 100644 --- a/GUI/PropertyWindow/PropertyWindow.cs +++ b/GUI/PropertyWindow/PropertyWindow.cs @@ -32,7 +32,7 @@ namespace MCGalaxy.Gui propsZG.SelectedObject = zsSettings; } - public void RunOnUI_Async(Action act) { BeginInvoke(act); } + public void RunOnUI_Async(UIAction act) { BeginInvoke(act); } void PropertyWindow_Load(object sender, EventArgs e) { // try to use same icon as main window diff --git a/GUI/Window/Window.cs b/GUI/Window/Window.cs index 61b8411a0..4f1831f77 100644 --- a/GUI/Window/Window.cs +++ b/GUI/Window/Window.cs @@ -28,8 +28,10 @@ using MCGalaxy.Generator; using MCGalaxy.Gui.Popups; using MCGalaxy.Tasks; -namespace MCGalaxy.Gui { - public partial class Window : Form { +namespace MCGalaxy.Gui +{ + public partial class Window : Form + { // for cross thread use delegate void StringCallback(string s); delegate void PlayerListCallback(List players); @@ -237,7 +239,7 @@ Trying to mix two versions is unsupported - you may experience issues"; RunOnUI_Async(() => main_btnProps.Enabled = true); } - public void RunOnUI_Async(Action act) { BeginInvoke(act); } + public void RunOnUI_Async(UIAction act) { BeginInvoke(act); } void Player_PlayerConnect(Player p) { RunOnUI_Async(() => { diff --git a/MCGalaxy/Network/Listeners.cs b/MCGalaxy/Network/Listeners.cs index ac945dcff..88f3993e9 100644 --- a/MCGalaxy/Network/Listeners.cs +++ b/MCGalaxy/Network/Listeners.cs @@ -115,7 +115,7 @@ namespace MCGalaxy.Network static void AcceptCallback(IAsyncResult result) { if (Server.shuttingDown) return; TcpListen listen = (TcpListen)result.AsyncState; - TcpSocket s = null; + INetSocket s = null; try { Socket raw = listen.socket.EndAccept(result); @@ -126,7 +126,13 @@ namespace MCGalaxy.Network // intentionally non-clean connection close try { raw.Close(); } catch { } } else { + #if NET_20 + // TODO better non-hardcoded detection? move to OperatingSystem? + s = Environment.OSVersion.Platform == PlatformID.Win32Windows ? (INetSocket)(new TcpLegacySocket(raw)) : (INetSocket)(new TcpSocket(raw)); + #else s = new TcpSocket(raw); + #endif + if (announce) Logger.Log(LogType.UserActivity, s.IP + " connected to the server."); s.Init(); } @@ -139,7 +145,7 @@ namespace MCGalaxy.Network public override void Close() { try { - Listening = false; + Listening = false; if (socket != null) socket.Close(); } catch (Exception ex) { Logger.LogError(ex); diff --git a/MCGalaxy/Network/Sockets.cs b/MCGalaxy/Network/Sockets.cs index dac49740b..48a0d3bd3 100644 --- a/MCGalaxy/Network/Sockets.cs +++ b/MCGalaxy/Network/Sockets.cs @@ -327,4 +327,135 @@ namespace MCGalaxy.Network return IPAddress.IsLoopback(ip) || ip.Equals(ccnetIP); } } + + // TODO avoid copying so much of TcpSocket + #if NET_20 + /// Backwards compatible socket for older Windows versions where Recv/SendAsync doesn't work + public sealed class TcpLegacySocket : INetSocket + { + readonly Socket socket; + byte[] recvBuffer = new byte[256]; + + byte[] sendBuffer = new byte[4096]; + readonly object sendLock = new object(); + readonly Queue sendQueue = new Queue(64); + volatile bool sendInProgress; + + public TcpLegacySocket(Socket s) { socket = s; } + + public override void Init() { + ReceiveNextAsync(); + } + + public override IPAddress IP { + get { return SocketUtil.GetIP(socket); } + } + public override bool LowLatency { set { socket.NoDelay = value; } } + + + static AsyncCallback recvCallback = RecvCallback; + void ReceiveNextAsync() { + socket.BeginReceive(recvBuffer, 0, recvBuffer.Length, 0, recvCallback, this); + } + + static void RecvCallback(IAsyncResult result) { + TcpLegacySocket s = (TcpLegacySocket)result.AsyncState; + if (s.Disconnected) return; + + try { + // If received 0, means socket was closed + int recvLen = s.socket.EndReceive(result); + if (recvLen == 0) { s.Disconnect(); return; } + + s.HandleReceived(s.recvBuffer, recvLen); + if (!s.Disconnected) s.ReceiveNextAsync(); + } catch (SocketException) { + s.Disconnect(); + } catch (ObjectDisposedException) { + // Socket was closed by another thread, mark as disconnected + } catch (Exception ex) { + Logger.LogError(ex); + s.Disconnect(); + } + } + + + static AsyncCallback sendCallback = SendCallback; + public override void Send(byte[] buffer, SendFlags flags) { + if (Disconnected || !socket.Connected) return; + + // TODO: Low priority sending support + try { + if ((flags & SendFlags.Synchronous) != 0) { + socket.Send(buffer, 0, buffer.Length, SocketFlags.None); + return; + } + + lock (sendLock) { + if (sendInProgress) { + sendQueue.Enqueue(buffer); + } else { + TrySendAsync(buffer); + } + } + } catch (SocketException) { + Disconnect(); + } catch (ObjectDisposedException) { + // Socket was already closed by another thread + } + } + + void TrySendAsync(byte[] buffer) { + // BlockCopy has some overhead, not worth it for very small data + if (buffer.Length <= 16) { + for (int i = 0; i < buffer.Length; i++) { + sendBuffer[i] = buffer[i]; + } + } else { + Buffer.BlockCopy(buffer, 0, sendBuffer, 0, buffer.Length); + } + + sendInProgress = true; + socket.BeginSend(sendBuffer, 0, buffer.Length, 0, sendCallback, this); + } + + static void SendCallback(IAsyncResult result) { + TcpLegacySocket s = (TcpLegacySocket)result.AsyncState; + try { + lock (s.sendLock) { + s.socket.EndSend(result); + s.sendInProgress = false; + + if (s.sendQueue.Count > 0) { + s.TrySendAsync(s.sendQueue.Dequeue()); + if (s.Disconnected) s.sendQueue.Clear(); + } + } + } catch (SocketException) { + s.Disconnect(); + } catch (ObjectDisposedException) { + // Socket was already closed by another thread + } catch (Exception ex) { + Logger.LogError(ex); + } + } + + // Close while also notifying higher level (i.e. show 'X disconnected' in chat) + void Disconnect() { + if (protocol != null) protocol.Disconnect(); + Close(); + } + + public override void Close() { + Disconnected = true; + pending.Remove(this); + + // swallow errors as connection is being closed anyways + try { socket.Shutdown(SocketShutdown.Both); } catch { } + try { socket.Close(); } catch { } + + lock (sendLock) { sendQueue.Clear(); } + } + } + #endif } diff --git a/MCGalaxy/Scripting/Scripting.cs b/MCGalaxy/Scripting/Scripting.cs index 0723eff1c..a8e4b8a88 100644 --- a/MCGalaxy/Scripting/Scripting.cs +++ b/MCGalaxy/Scripting/Scripting.cs @@ -44,6 +44,7 @@ namespace MCGalaxy.Scripting // only used for resolving plugin DLLs depending on other plugin DLLs static Assembly ResolvePluginAssembly(object sender, ResolveEventArgs args) { +#if !NET_20 if (args.RequestingAssembly == null) return null; if (!IsPluginDLL(args.RequestingAssembly)) return null; @@ -57,6 +58,7 @@ namespace MCGalaxy.Scripting Logger.Log(LogType.Warning, "Custom command/plugin [{0}] tried to load [{1}], but it could not be found", args.RequestingAssembly.FullName, args.Name); +#endif return null; }