A game about forced loneliness, made by TACStudios
1/*--------------------------------------------------------------------------------------------- 2 * Copyright (c) Microsoft Corporation. All rights reserved. 3 * Licensed under the MIT License. See License.txt in the project root for license information. 4 *--------------------------------------------------------------------------------------------*/ 5using System; 6using System.Net; 7using System.Net.Sockets; 8 9namespace Microsoft.Unity.VisualStudio.Editor.Messaging 10{ 11 internal class Messager : IDisposable 12 { 13 public event EventHandler<MessageEventArgs> ReceiveMessage; 14 public event EventHandler<ExceptionEventArgs> MessagerException; 15 16 private readonly UdpSocket _socket; 17 private readonly object _disposeLock = new object(); 18 private bool _disposed; 19 20#if UNITY_EDITOR_WIN 21 [System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true)] 22 private static extern bool SetHandleInformation(IntPtr hObject, HandleFlags dwMask, HandleFlags dwFlags); 23 24 [Flags] 25 private enum HandleFlags: uint 26 { 27 None = 0, 28 Inherit = 1, 29 ProtectFromClose = 2 30 } 31#endif 32 33 protected Messager(int port) 34 { 35 _socket = new UdpSocket(); 36 _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, false); 37 _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); 38 39#if UNITY_EDITOR_WIN 40 // Explicitely disable inheritance for our UDP socket handle 41 // We found that Unity is creating a fork when importing new assets that can clone our socket 42 SetHandleInformation(_socket.Handle, HandleFlags.Inherit, HandleFlags.None); 43#endif 44 45 _socket.Bind(IPAddress.Any, port); 46 47 BeginReceiveMessage(); 48 } 49 50 private void BeginReceiveMessage() 51 { 52 var buffer = new byte[UdpSocket.BufferSize]; 53 var any = UdpSocket.Any(); 54 55 try 56 { 57 lock (_disposeLock) 58 { 59 if (_disposed) 60 return; 61 62 _socket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref any, ReceiveMessageCallback, buffer); 63 } 64 } 65 catch (SocketException se) 66 { 67 MessagerException?.Invoke(this, new ExceptionEventArgs(se)); 68 69 BeginReceiveMessage(); 70 } 71 catch (ObjectDisposedException) 72 { 73 } 74 } 75 76 private void ReceiveMessageCallback(IAsyncResult result) 77 { 78 try 79 { 80 var endPoint = UdpSocket.Any(); 81 82 lock (_disposeLock) 83 { 84 if (_disposed) 85 return; 86 87 _socket.EndReceiveFrom(result, ref endPoint); 88 } 89 90 var message = DeserializeMessage(UdpSocket.BufferFor(result)); 91 if (message != null) 92 { 93 message.Origin = (IPEndPoint)endPoint; 94 95 if (IsValidTcpMessage(message, out var port, out var bufferSize)) 96 { 97 // switch to TCP mode to handle big messages 98 TcpClient.Queue(message.Origin.Address, port, bufferSize, buffer => 99 { 100 var originalMessage = DeserializeMessage(buffer); 101 originalMessage.Origin = message.Origin; 102 ReceiveMessage?.Invoke(this, new MessageEventArgs(originalMessage)); 103 }); 104 } 105 else 106 { 107 ReceiveMessage?.Invoke(this, new MessageEventArgs(message)); 108 } 109 } 110 } 111 catch (ObjectDisposedException) 112 { 113 return; 114 } 115 catch (Exception e) 116 { 117 RaiseMessagerException(e); 118 } 119 120 BeginReceiveMessage(); 121 } 122 123 private static bool IsValidTcpMessage(Message message, out int port, out int bufferSize) 124 { 125 port = 0; 126 bufferSize = 0; 127 if (message.Value == null) 128 return false; 129 if (message.Type != MessageType.Tcp) 130 return false; 131 var parts = message.Value.Split(':'); 132 if (parts.Length != 2) 133 return false; 134 if (!int.TryParse(parts[0], out port)) 135 return false; 136 return int.TryParse(parts[1], out bufferSize); 137 } 138 139 private void RaiseMessagerException(Exception e) 140 { 141 MessagerException?.Invoke(this, new ExceptionEventArgs(e)); 142 } 143 144 private static Message MessageFor(MessageType type, string value) 145 { 146 return new Message { Type = type, Value = value }; 147 } 148 149 public void SendMessage(IPEndPoint target, MessageType type, string value = "") 150 { 151 var message = MessageFor(type, value); 152 var buffer = SerializeMessage(message); 153 154 try 155 { 156 lock (_disposeLock) 157 { 158 if (_disposed) 159 return; 160 161 if (buffer.Length >= UdpSocket.BufferSize) 162 { 163 // switch to TCP mode to handle big messages 164 var port = TcpListener.Queue(buffer); 165 if (port > 0) 166 { 167 // success, replace original message with "switch to tcp" marker + port information + buffer length 168 message = MessageFor(MessageType.Tcp, string.Concat(port, ':', buffer.Length)); 169 buffer = SerializeMessage(message); 170 } 171 } 172 173 _socket.BeginSendTo(buffer, 0, Math.Min(buffer.Length, UdpSocket.BufferSize), SocketFlags.None, target, SendMessageCallback, null); 174 } 175 } 176 catch (SocketException se) 177 { 178 MessagerException?.Invoke(this, new ExceptionEventArgs(se)); 179 } 180 } 181 182 private void SendMessageCallback(IAsyncResult result) 183 { 184 try 185 { 186 lock (_disposeLock) 187 { 188 if (_disposed) 189 return; 190 191 _socket.EndSendTo(result); 192 } 193 } 194 catch (SocketException se) 195 { 196 MessagerException?.Invoke(this, new ExceptionEventArgs(se)); 197 } 198 catch (ObjectDisposedException) 199 { 200 } 201 } 202 203 private static byte[] SerializeMessage(Message message) 204 { 205 var serializer = new Serializer(); 206 serializer.WriteInt32((int)message.Type); 207 serializer.WriteString(message.Value); 208 209 return serializer.Buffer(); 210 } 211 212 private static Message DeserializeMessage(byte[] buffer) 213 { 214 if (buffer.Length < 4) 215 return null; 216 217 var deserializer = new Deserializer(buffer); 218 var type = (MessageType)deserializer.ReadInt32(); 219 var value = deserializer.ReadString(); 220 221 return new Message { Type = type, Value = value }; 222 } 223 224 public static Messager BindTo(int port) 225 { 226 return new Messager(port); 227 } 228 229 public void Dispose() 230 { 231 lock (_disposeLock) 232 { 233 _disposed = true; 234 _socket.Close(); 235 } 236 } 237 } 238}