/// <summary> /// Thread procedure that is responsible for accepting new clients on the TCP server port. /// New clients are put into clientQueue, from which they are consumed by clientQueueHandlerThread. /// </summary> private void AcceptThread() { LogDiagnosticContext.Start(); log.Trace("()"); acceptThreadFinished.Reset(); AutoResetEvent acceptTaskEvent = new AutoResetEvent(false); while (!ShutdownSignaling.IsShutdown) { log.Debug("Waiting for new client."); Task <TcpClient> acceptTask = Listener.AcceptTcpClientAsync(); acceptTask.ContinueWith(t => acceptTaskEvent.Set()); WaitHandle[] handles = new WaitHandle[] { acceptTaskEvent, ShutdownSignaling.ShutdownEvent }; int index = WaitHandle.WaitAny(handles); if (handles[index] == ShutdownSignaling.ShutdownEvent) { log.Info("Shutdown detected."); break; } try { // acceptTask is finished here, asking for Result won't block. TcpClient client = acceptTask.Result; EndPoint ep = client.Client.RemoteEndPoint; lock (clientQueueLock) { clientQueue.Enqueue(client); } log.Debug("New client '{0}' accepted.", ep); clientQueueEvent.Set(); } catch (Exception e) { log.Error("Exception occurred: {0}", e.ToString()); } } acceptThreadFinished.Set(); log.Trace("(-)"); LogDiagnosticContext.Stop(); }
/// <summary> /// Connects to a specific IP address and port and initializes stream. /// If TLS is used, client authentication is done as well. /// </summary> /// <returns>true if the connection was established succcessfully, false otherwise.</returns> public async Task <bool> ConnectAsync() { log.Trace("()"); bool res = false; try { await TcpClient.ConnectAsync(remoteEndPoint.Address, remoteEndPoint.Port); stream = TcpClient.GetStream(); if (UseTls) { SslStream sslStream = new SslStream(stream, false, PeerCertificateValidationCallback); await sslStream.AuthenticateAsClientAsync("", null, SslProtocols.Tls12, false); stream = sslStream; } res = true; } catch (Exception e) { log.Debug("Unable to connect to {0}, error exception: {1}", remoteEndPoint, e.ToString()); } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Thread procedure that consumes clients from clientQueue. /// When a new client is detected in the queue, it is removed from the queue /// and enters asynchronous read and processing loop. /// </summary> private void ClientQueueHandlerThread() { log.Info("()"); clientQueueHandlerThreadFinished.Reset(); while (!ShutdownSignaling.IsShutdown) { WaitHandle[] handles = new WaitHandle[] { clientQueueEvent, ShutdownSignaling.ShutdownEvent }; int index = WaitHandle.WaitAny(handles); if (handles[index] == ShutdownSignaling.ShutdownEvent) { log.Info("Shutdown detected."); break; } log.Debug("New client in the queue detected, queue count is {0}.", clientQueue.Count); bool queueEmpty = false; while (!queueEmpty && !ShutdownSignaling.IsShutdown) { TcpClient tcpClient = null; lock (clientQueueLock) { if (clientQueue.Count > 0) { tcpClient = clientQueue.Peek(); } } if (tcpClient != null) { int keepAliveInterval = IsServingClientsOnly ? ClientKeepAliveIntervalSeconds : NodeKeepAliveIntervalSeconds; Client client = new Client(this, tcpClient, clientList.GetNewClientId(), UseTls, keepAliveInterval); ClientHandlerAsync(client); lock (clientQueueLock) { clientQueue.Dequeue(); queueEmpty = clientQueue.Count == 0; } } else { queueEmpty = true; } } } clientQueueHandlerThreadFinished.Set(); log.Info("(-)"); }
/// <summary> /// Asynchronous read and processing function for each client that connects to the TCP server. /// </summary> /// <param name="Client">Client that is connected to TCP server.</param> /// <remarks>The client is being handled in the processing loop until the connection to it /// is terminated by either side. This function implements reading the message from the network stream, /// which includes reading the message length prefix followed by the entire message.</remarks> private async void ClientHandlerAsync(Client Client) { this.log.Info("(Client.RemoteEndPoint:{0})", Client.RemoteEndPoint); string prefix = string.Format("{0}[{1}] ", this.logPrefix, Client.RemoteEndPoint); PrefixLogger log = new PrefixLogger(this.logName, prefix); clientList.AddNetworkPeer(Client); log.Debug("Client ID set to 0x{0:X16}.", Client.Id); await Client.ReceiveMessageLoop(); // Free resources used by the client. clientList.RemoveNetworkPeer(Client); await Client.HandleDisconnect(); Client.Dispose(); log.Info("(-)"); }
/// <summary> /// Callback routine that is called once the timeoutTimer expires. /// <para> /// If relay status is WaitingForCalleeResponse, the callee has to reply to the incoming call notification /// within a reasonable time. If it does the timer is cancelled. If it does not, the timeout occurs. /// </para> /// <para> /// If relay status is WaitingForFirstInitMessage, both clients are expected to connect to clAppService port /// and send an initial message over that service. The timeoutTimer expires when none of the clients /// connects to clAppService port and sends its initialization message within a reasonable time. /// </para> /// <para> /// Then if relay status is WaitingForSecondInitMessage, the node receives a message from the first client /// on clAppService port, it starts the timer again, which now expires if the second client does not connect /// and send its initial message within a reasonable time. /// </para> /// </summary> /// <param name="state">Status of the relay when the timer was installed.</param> private async void TimeoutCallback(object State) { RelayConnectionStatus previousStatus = (RelayConnectionStatus)State; log.Trace("(State:{0})", previousStatus); Client clientToSendMessage = null; Message messageToSend = null; bool destroyRelay = false; await lockObject.WaitAsync(); if (timeoutTimer != null) { switch (status) { case RelayConnectionStatus.WaitingForCalleeResponse: { // The caller requested the call and the callee was notified. // The callee failed to send us response on time, this is situation 2) // from ProcessMessageCallIdentityApplicationServiceRequestAsync. // We send ERROR_NOT_AVAILABLE to the caller and destroy the relay. log.Debug("Callee failed to reply to the incoming call notification, closing relay."); clientToSendMessage = caller; messageToSend = caller.MessageBuilder.CreateErrorNotAvailableResponse(pendingMessage); break; } case RelayConnectionStatus.WaitingForFirstInitMessage: { // Neither client joined the channel on time, nothing to do, just destroy the relay. log.Debug("None of the clients joined the relay on time, closing relay."); break; } case RelayConnectionStatus.WaitingForSecondInitMessage: { // One client is waiting for the other one to join, but the other client failed to join on time. // We send ERROR_NOT_FOUND to the waiting client and close its connection. log.Debug("{0} failed to join the relay on time, closing relay.", callee != null ? "Caller" : "Callee"); clientToSendMessage = callee != null ? callee : caller; messageToSend = clientToSendMessage.MessageBuilder.CreateErrorNotFoundResponse(pendingMessage); break; } default: log.Debug("Time out triggered while the relay status was {0}.", status); break; } // In case of any timeouts, we just destroy the relay. destroyRelay = true; } else { log.Debug("Timeout timer of relay '{0}' has been destroyed, no action taken.", id); } lockObject.Release(); if (messageToSend != null) { if (!await clientToSendMessage.SendMessageAsync(messageToSend)) { log.Warn("Unable to send message to the client ID '0x{0:X16}' in relay '{1}', maybe it is not connected anymore.", clientToSendMessage.Id, id); } } if (destroyRelay) { Server serverComponent = (Server)Base.ComponentDictionary["Network.Server"]; ClientList clientList = serverComponent.GetClientList(); await clientList.DestroyNetworkRelay(this); } log.Trace("(-)"); }