/// <summary> /// reads a message from connection /// </summary> /// <param name="buffer">buffer where the message will be written</param> /// <returns>true if we got a message, false if we got disconnected</returns> public async UniTask <int> ReceiveAsync(MemoryStream buffer) { try { while (!_cancellationToken.IsCancellationRequested) { while (_queuedIncomingData.TryDequeue(out Message message)) { Libuv2kNGLogger.Log( $"Libuv2kConnection processing message: {BitConverter.ToString(message.Data)}"); buffer.SetLength(0); buffer.Write(message.Data, 0, message.Data.Length); return(message.Channel); } await UniTask.Delay(1); } throw new EndOfStreamException(); } catch (EndOfStreamException) { throw new EndOfStreamException(); } }
/// <summary> /// Callback when connection has closed. /// </summary> /// <param name="handle">The stream handle in which closed connection to.</param> private void OnLibuvClientClosed(TcpStream handle) { Libuv2kNGLogger.Log("libuv client callback: closed connection"); handle.Dispose(); // set client to null so we can't send to an old reference anymore _client = null; }
/// <summary> /// Shut down the server. /// </summary> public void Shutdown() { if (_server != null) { _cancellationToken.Cancel(); _server?.Dispose(); _server = null; Libuv2kNGLogger.Log("libuv server: TCP stopped!"); _serverLoop?.Dispose(); } }
/// <summary> /// Callback when connection receives a new message. /// </summary> /// <param name="handle">The stream handle we used to connect with server with.</param> /// <param name="segment">The data that has come in with it.</param> private void OnLibuvClientMessage(TcpStream handle, ArraySegment <byte> segment) { Libuv2kNGLogger.Log($"libuv client callback received: data= {BitConverter.ToString(segment.Array)}"); byte[] data = new byte[segment.Count]; Array.Copy(segment.Array, segment.Offset, data, 0, segment.Count); _queuedIncomingData.Enqueue(new Message { Data = data }); }
/// <summary> /// Start listening for incoming connections. /// </summary> /// <param name="port">The port to bind to listen connections on.</param> private void ListenAsync(int port) { // start server IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, port); Libuv2kNGLogger.Log("libuv server: starting TCP..." + endPoint); _server = new TcpStream(_serverLoop); _server.SimultaneousAccepts(true); _server.onServerConnect = OnLibuvServerConnected; _server.Listen(endPoint); Libuv2kNGLogger.Log("libuv server: TCP started!"); }
/// <summary> /// Connect to server. /// </summary> /// <param name="uri">The server <see cref="Uri"/> to connect to.</param> /// <returns>Returns back a new <see cref="Libuv2kConnection"/> when connected or null when failed.</returns> public async UniTask <IConnection> ConnectAsync(Uri uri) { try { // libuv doesn't resolve host name, and it needs ipv4. if (LibuvUtils.ResolveToIPV4(uri.Host, out IPAddress address)) { // connect client var localEndPoint = new IPEndPoint(IPAddress.Any, IPEndPoint.MinPort); var remoteEndPoint = new IPEndPoint(address, uri.Port); Libuv2kNGLogger.Log("Libuv client connecting to: " + address + ":" + uri.Port); _client.onClientConnect = ConnectedAction; _client.ConnectTo(localEndPoint, remoteEndPoint); } else { Libuv2kNGLogger.Log("Libuv client connect: no IPv4 found for hostname: " + uri.Host, LogType.Warning); } _connectedComplete = new UniTaskCompletionSource(); UniTask connectedCompleteTask = _connectedComplete.Task; while (await UniTask.WhenAny(connectedCompleteTask, UniTask.Delay(TimeSpan.FromSeconds(Math.Max(1, 30)))) != 0) { Disconnect(); return(null); } Libuv2kNGLogger.Log("Libuv client connected to: " + address + ":" + uri.Port); return(this); } catch (Exception ex) { Libuv2kNGLogger.Log($"Error trying to attempting to connect. Error: {ex}", LogType.Error); Disconnect(); } return(null); }
/// <summary> /// Queue up connections that are trying to connect. /// </summary> /// <param name="handle">The stream which is trying to connect to server.</param> /// <param name="error"></param> private void OnLibuvServerConnected(TcpStream handle, Exception error) { Libuv2kNGLogger.Log("libuv server: client connected =" + handle.GetPeerEndPoint().Address); // close if errors (AFTER setting up onClosed callback!) if (error != null) { Libuv2kNGLogger.Log($"libuv server: client connection failed {error}"); handle.Dispose(); return; } var newClient = new Libuv2kConnection(true, handle); _transport.Connected.Invoke(newClient); }
/// <summary> /// Receive call back when we finally connect to a server. /// </summary> /// <param name="handle">The stream handle we used to connect with server with.</param> /// <param name="error">If there were any errors during connection.</param> private void ConnectedAction(TcpStream handle, Exception error) { handle.onMessage = OnLibuvClientMessage; handle.onError = OnLibuvClientError; handle.onClosed = OnLibuvClientClosed; // close if errors (AFTER setting up onClosed callback!) if (error != null) { Libuv2kNGLogger.Log($"libuv client callback: client error {error}", LogType.Error); handle.Dispose(); return; } _connectedComplete.TrySetResult(); }
/// <summary> /// Disconnect this connection /// </summary> public void Disconnect() { Libuv2kNGLogger.Log("libuv client: closed connection"); _cancellationToken.Cancel(); _connectedComplete?.TrySetCanceled(); _client?.Dispose(); while (_queuedIncomingData.TryDequeue(out _)) { // do nothing } while (_queueOutgoingData.TryDequeue(out _)) { // do nothing } _clientLoop?.Dispose(); }
/// <summary> /// Send data with channel specific settings. (NOOP atm until mirrorng links it) /// </summary> /// <param name="data">The data to be sent.</param> /// <param name="channel">The channel to send it on.</param> /// <returns></returns> public void Send(ArraySegment <byte> data, int channel) { if (_client is null || _client.IsClosing || _cancellationToken.IsCancellationRequested) { return; } Libuv2kNGLogger.Log("Libuv2kConnection client: send data=" + BitConverter.ToString(data.Array)); byte[] buffer = new byte[data.Count]; Array.Copy(data.Array, data.Offset, buffer, 0, data.Count); _queueOutgoingData.Enqueue(new Message { Data = buffer, Channel = channel }); return; }
private UniTask ProcessOutgoingMessages() { while (!_cancellationToken.IsCancellationRequested) { for (int messageIndex = 0; messageIndex < _queueOutgoingData.Count; messageIndex++) { if (_queueOutgoingData.TryDequeue(out Message outgoing)) { _client?.Send(new ArraySegment <byte>(outgoing.Data)); } UniTask.Delay(1); } UniTask.Delay(1); } Libuv2kNGLogger.Log("Shutting down processing task"); return(UniTask.CompletedTask); }
/// <summary> /// We must tick through to receive connection status. /// </summary> private UniTask Tick() { // tick client while (!(_clientLoop is null) && !(_client is null)) { // Run with UV_RUN_NOWAIT returns 0 when nothing to do, but we // should avoid deadlocks via LibuvMaxTicksPerFrame for (int i = 0; i < LibuvMaxTicksPerFrame; ++i) { if (_clientLoop.Run(uv_run_mode.UV_RUN_NOWAIT) == 0) { Debug.Log("Client libuv ticked only " + i + " times"); break; } } } Libuv2kNGLogger.Log("Shutting down tick task."); return(UniTask.CompletedTask); }
/// <summary> /// Callback for any errors that occur on the connection. /// </summary> /// <param name="handle">The stream handle we used to connect with server with.</param> /// <param name="error">The error that occurred on the connection.</param> private void OnLibuvClientError(TcpStream handle, Exception error) { Libuv2kNGLogger.Log($"libuv client callback: read error {error}", LogType.Error); handle.Dispose(); }