private SimpleSocket CreateSocketContext() { var socketContext = new SimpleSocket(); socketContext.Connected = (clientSocketContext) => { Task.Run(async () => { try { // Register service server await socketContext.WriteStream.WriteInt16Async((short)RouterMessage.ServiceProvideServer); await socketContext.WriteStream.WriteStringAsync(serverUrl); await socketContext.WriteStream.FlushAsync(); while (true) { var routerMessage = (RouterMessage)await socketContext.ReadStream.ReadInt16Async(); switch (routerMessage) { case RouterMessage.ServiceRequestServer: { var requestedUrl = await clientSocketContext.ReadStream.ReadStringAsync(); var guid = await clientSocketContext.ReadStream.ReadGuidAsync(); // Spawn actual server var realServerSocketContext = new SimpleSocket(); realServerSocketContext.Connected = async (clientSocketContext2) => { // Write connection string await clientSocketContext2.WriteStream.WriteInt16Async((short)RouterMessage.ServerStarted); await clientSocketContext2.WriteStream.WriteGuidAsync(guid); // Delegate next steps to actual server HandleClient(clientSocketContext2, requestedUrl); }; // Start connection await realServerSocketContext.StartClient(address, port); break; } default: Console.WriteLine("Router: Unknown message: {0}", routerMessage); throw new ArgumentOutOfRangeException(); } } } catch (Exception e) { // TODO: Ideally, separate socket-related error messages (disconnection) from real errors // Unfortunately, it seems WinRT returns Exception, so it seems we can't filter with SocketException/IOException only? Log.Info("Client {0}:{1} disconnected with exception: {2}", clientSocketContext.RemoteAddress, clientSocketContext.RemotePort, e.Message); clientSocketContext.Dispose(); } }); }; return socketContext; }
public async Task StartServer(int port, bool singleConnection) { // Create TCP listener var listener = new TcpSocketListener(2048); listener.ConnectionReceived = async (sender, args) => { var clientSocketContext = new SimpleSocket(); try { // Stop listening if we accept only a single connection if (singleConnection) await listener.StopListeningAsync(); clientSocketContext.SetSocket((TcpSocketClient)args.SocketClient); // Do an ack with magic packet (necessary so that we know it's not a dead connection, // it sometimes happen when doing port forwarding because service don't refuse connection right away but only fails when sending data) await SendAndReceiveAck(clientSocketContext.socket, MagicAck, MagicAck); if (Connected != null) Connected(clientSocketContext); clientSocketContext.isConnected = true; } catch (Exception) { clientSocketContext.DisposeSocket(); } }; // Start listening await listener.StartListeningAsync(port); }
/// <summary> /// Initiates a connection to the router. /// </summary> /// <returns></returns> private static Task<SimpleSocket> InitiateConnectionToRouter() { var socketContextTCS = new TaskCompletionSource<SimpleSocket>(); var socketContext = new SimpleSocket(); socketContext.Connected = async context => { socketContextTCS.TrySetResult(context); }; Task.Run(async () => { try { // If connecting as a client, try once, otherwise try to listen multiple time (in case port is shared) switch (ConnectionMode) { case RouterConnectionMode.Connect: await socketContext.StartClient("127.0.0.1", DefaultPort); break; case RouterConnectionMode.Listen: await socketContext.StartServer(DefaultListenPort, true, 10); break; case RouterConnectionMode.ConnectThenListen: bool clientException = false; try { await socketContext.StartClient("127.0.0.1", DefaultPort); } catch (Exception) // Ideally we should filter SocketException, but not available on some platforms (maybe it should be wrapped in a type available on all paltforms?) { clientException = true; } if (clientException) { await socketContext.StartServer(DefaultListenPort, true, 10); } break; default: throw new ArgumentOutOfRangeException(); } } catch (Exception e) { Log.Error("Could not connect to connection router using mode {0}: {1}", ConnectionMode, e.Message); throw; } }); // Wait for server to connect to us (as a Task) return socketContextTCS.Task; }
private SimpleSocket CreateSocketContext() { var socketContext = new SimpleSocket(); socketContext.Connected = (clientSocketContext) => { Task.Run(async () => { try { // Routing var routerMessage = (RouterMessage)await clientSocketContext.ReadStream.ReadInt16Async(); Log.Info("Client {0}:{1} connected, with message {2}", clientSocketContext.RemoteAddress, clientSocketContext.RemotePort, routerMessage); switch (routerMessage) { case RouterMessage.ServiceProvideServer: { await HandleMessageServiceProvideServer(clientSocketContext); break; } case RouterMessage.ServerStarted: { await HandleMessageServerStarted(clientSocketContext); break; } case RouterMessage.ClientRequestServer: { await HandleMessageClientRequestServer(clientSocketContext); break; } default: throw new ArgumentOutOfRangeException(string.Format("Router: Unknown message: {0}", routerMessage)); } } catch (Exception e) { // TODO: Ideally, separate socket-related error messages (disconnection) from real errors // Unfortunately, it seems WinRT returns Exception, so it seems we can't filter with SocketException/IOException only? Log.Info("Client {0}:{1} disconnected with exception: {2}", clientSocketContext.RemoteAddress, clientSocketContext.RemotePort, e.Message); clientSocketContext.Dispose(); } }); }; return socketContext; }
private SimpleSocket CreateSocketContext() { var socketContext = new SimpleSocket(); socketContext.Connected = async (clientSocketContext) => { // Register service server await socketContext.WriteStream.WriteInt16Async((short)RouterMessage.ServiceProvideServer); await socketContext.WriteStream.WriteStringAsync(serverUrl); await socketContext.WriteStream.FlushAsync(); while (true) { var routerMessage = (RouterMessage)await socketContext.ReadStream.ReadInt16Async(); switch (routerMessage) { case RouterMessage.ServiceRequestServer: { var requestedUrl = await clientSocketContext.ReadStream.ReadStringAsync(); var guid = await clientSocketContext.ReadStream.ReadGuidAsync(); // Spawn actual server var realServerSocketContext = new SimpleSocket(); realServerSocketContext.Connected = async (clientSocketContext2) => { // Write connection string await clientSocketContext2.WriteStream.WriteInt16Async((short)RouterMessage.ServerStarted); await clientSocketContext2.WriteStream.WriteGuidAsync(guid); // Delegate next steps to actual server HandleClient(clientSocketContext2, requestedUrl); }; // Start connection await realServerSocketContext.StartClient(address, port); break; } default: Console.WriteLine("Router: Unknown message: {0}", routerMessage); throw new ArgumentOutOfRangeException(); } } }; return socketContext; }
/// <summary> /// Initiates a connection to the router. /// </summary> /// <returns></returns> private static Task<SimpleSocket> InitiateConnectionToRouter() { var socketContextTCS = new TaskCompletionSource<SimpleSocket>(); var socketContext = new SimpleSocket(); socketContext.Connected = async context => { socketContextTCS.TrySetResult(context); }; Task.Run(async () => { // Keep trying to establish connections until no errors bool hasErrors; do { try { hasErrors = false; if (PlatformIsPortForward) await socketContext.StartServer(DefaultListenPort, true); else await socketContext.StartClient("127.0.0.1", DefaultPort); } catch (Exception) { hasErrors = true; } if (hasErrors) { // Wait a little bit before next try await Task.Delay(100); } } while (hasErrors); }); // Wait for server to connect to us (as a Task) return socketContextTCS.Task; }
/// <inheritdoc/> protected override async void HandleClient(SimpleSocket clientSocket, string url) { string[] urlSegments; string urlParameters; RouterHelper.ParseUrl(url, out urlSegments, out urlParameters); var parameters = RouterHelper.ParseQueryString(urlParameters); var mode = parameters["mode"]; // We accept everything await AcceptConnection(clientSocket); var socketMessageLayer = new SocketMessageLayer(clientSocket, true); Guid? packageId = null; { Guid packageIdParsed; if (Guid.TryParse(parameters["packageid"], out packageIdParsed)) packageId = packageIdParsed; } if (mode == "gamestudio") { Console.WriteLine("GameStudio mode started!"); if (!packageId.HasValue) return; lock (gameStudioPerPackageId) { gameStudioPerPackageId[packageId.Value] = socketMessageLayer; } } else { // Create an effect compiler per connection var effectCompiler = new EffectCompiler(); Console.WriteLine("Client connected"); // TODO: Properly close the file, and choose where to copy/move it? var recordedEffectCompile = new EffectLogStore(new MemoryStream()); // TODO: This should come from an "init" packet effectCompiler.SourceDirectories.Add(EffectCompilerBase.DefaultSourceShaderFolder); // Make a VFS that will access remotely the DatabaseFileProvider // TODO: Is that how we really want to do that in the future? var networkVFS = new NetworkVirtualFileProvider(socketMessageLayer, "/asset"); VirtualFileSystem.RegisterProvider(networkVFS); effectCompiler.FileProvider = networkVFS; socketMessageLayer.AddPacketHandler<RemoteEffectCompilerEffectRequest>((packet) => ShaderCompilerRequestHandler(socketMessageLayer, recordedEffectCompile, effectCompiler, packet)); socketMessageLayer.AddPacketHandler<RemoteEffectCompilerEffectRequested>((packet) => { if (!packageId.HasValue) return; SocketMessageLayer gameStudio; lock (gameStudioPerPackageId) { if (!gameStudioPerPackageId.TryGetValue(packageId.Value, out gameStudio)) return; } // Forward to game studio gameStudio.Send(packet); }); } Task.Run(() => socketMessageLayer.MessageLoop()); }
/// <summary> /// Handles ClientRequestServer messages. /// It will try to find a matching service (spawn it if not started yet), and ask it to establish a new "server" connection back to us. /// </summary> /// <param name="clientSocket">The client socket context.</param> /// <returns></returns> private async Task HandleMessageClientRequestServer(SimpleSocket clientSocket) { // Check for an existing server // TODO: Proper Url parsing (query string) var url = await clientSocket.ReadStream.ReadStringAsync(); string[] urlSegments; string urlParameters; RouterHelper.ParseUrl(url, out urlSegments, out urlParameters); if (urlSegments.Length == 0) throw new InvalidOperationException("No URL Segments"); SimpleSocket serverSocket = null; ExceptionDispatchInfo serverSocketCapturedException = null; try { // For now, we handle only "service" URL switch (urlSegments[0]) { case "service": { // From the URL, start service (if not started yet) and ask it to provide a server serverSocket = await SpawnServerFromService(url); break; } default: throw new InvalidOperationException("This type of URL is not supported"); } } catch (Exception e) { serverSocketCapturedException = ExceptionDispatchInfo.Capture(e); } if (serverSocketCapturedException != null) { try { // Notify client that there was an error await clientSocket.WriteStream.WriteInt16Async((short)RouterMessage.ClientServerStarted); await clientSocket.WriteStream.WriteInt32Async(1); // error code Failure await clientSocket.WriteStream.WriteStringAsync(serverSocketCapturedException.SourceException.Message); await clientSocket.WriteStream.FlushAsync(); } finally { serverSocketCapturedException.Throw(); } } try { // Notify client that we've found a server for it await clientSocket.WriteStream.WriteInt16Async((short)RouterMessage.ClientServerStarted); await clientSocket.WriteStream.WriteInt32Async(0); // error code OK await clientSocket.WriteStream.FlushAsync(); // Let's forward clientSocketContext and serverSocketContext await await Task.WhenAny( ForwardSocket(clientSocket, serverSocket), ForwardSocket(serverSocket, clientSocket)); } catch { serverSocket.Dispose(); throw; } }
public Service(SimpleSocket socket) { Socket = socket; }
private async Task ForwardSocket(SimpleSocket source, SimpleSocket target) { var buffer = new byte[1024]; while (true) { var bufferLength = await source.ReadStream.ReadAsync(buffer, 0, buffer.Length); if (bufferLength == 0) throw new IOException("Socket closed"); await target.WriteStream.WriteAsync(buffer, 0, bufferLength); await target.WriteStream.FlushAsync(); } }
/// <summary> /// Handles ServiceProvideServer messages. It allows service to publicize what "server" they can instantiate. /// </summary> /// <param name="clientSocket">The client socket context.</param> /// <returns></returns> private async Task HandleMessageServiceProvideServer(SimpleSocket clientSocket) { var url = await clientSocket.ReadStream.ReadStringAsync(); TaskCompletionSource<Service> service; lock (registeredServices) { if (!registeredServices.TryGetValue(url, out service)) { service = new TaskCompletionSource<Service>(); registeredServices.Add(url, service); } service.TrySetResult(new Service(clientSocket)); } // TODO: Handle server disconnections //clientSocketContext.Disconnected += }
/// <summary> /// Handles ServerStarted messages. It happens when service opened a new "server" connection back to us. /// </summary> /// <param name="clientSocket">The client socket context.</param> /// <returns></returns> private async Task HandleMessageServerStarted(SimpleSocket clientSocket) { var guid = await clientSocket.ReadStream.ReadGuidAsync(); var errorCode = await clientSocket.ReadStream.ReadInt32Async(); var errorMessage = (errorCode != 0) ? await clientSocket.ReadStream.ReadStringAsync() : null; // Notify any waiter that a server with given GUID is available TaskCompletionSource<SimpleSocket> serverSocketTCS; lock (pendingServers) { if (!pendingServers.TryGetValue(guid, out serverSocketTCS)) { Log.Error("Could not find a matching server Guid"); clientSocket.Dispose(); return; } pendingServers.Remove(guid); } if (errorCode != 0) serverSocketTCS.TrySetException(new Exception(errorMessage)); else serverSocketTCS.TrySetResult(clientSocket); }
/// <summary> /// Let router knows that we want to continue with that connection. /// </summary> /// <param name="clientSocket">The client socket.</param> /// <returns></returns> protected async Task AcceptConnection(SimpleSocket clientSocket) { await clientSocket.WriteStream.WriteInt32Async(0); // error code OK await clientSocket.WriteStream.FlushAsync(); }
/// <summary> /// Called when a new client connection has been established. /// Before writing anything to the stream, HandleClient is responsible for either calling <see cref="AcceptConnection"/> or <see cref="RefuseConnection"/>. /// </summary> /// <param name="clientSocket">The client socket.</param> /// <param name="url">The requested URL.</param> protected abstract void HandleClient(SimpleSocket clientSocket, string url);
/// <summary> /// Let router knows we refuse the connection, and why. /// </summary> /// <param name="clientSocket">The client socket.</param> /// <param name="errorCode">The error code.</param> /// <param name="errorMessage">The error message.</param> /// <returns></returns> protected async Task RefuseConnection(SimpleSocket clientSocket, int errorCode, string errorMessage) { await clientSocket.WriteStream.WriteInt32Async(errorCode); await clientSocket.WriteStream.WriteStringAsync(errorMessage); await clientSocket.WriteStream.FlushAsync(); }