/// <summary> /// Sends a message to the client over the open network stream. /// </summary> /// <param name="Message">Message to send.</param> /// <returns>true if the message was sent successfully to the target recipient.</returns> protected async Task <bool> SendMessageInternalAsync(Message Message) { log.Trace("()"); bool res = false; string msgStr = Message.ToString(); log.Trace("Sending message:\n{0}", msgStr.SubstrMax(512)); byte[] messageBytes = ProtocolHelper.GetMessageBytes(Message); await streamWriteLock.WaitAsync(); try { if (Stream != null) { await Stream.WriteAsync(messageBytes, 0, messageBytes.Length); res = true; } else { log.Info("Connection to the client has been terminated."); } } catch (IOException) { log.Info("Connection to the client has been terminated."); } finally { streamWriteLock.Release(); } log.Trace("(-):{0}", res); return(res); }
/// <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> /// <para>Starts the TCP server listener and starts client thread handlers.</para> /// <para>If the application is restarted, it may be the case that the TCP port /// is unusable for a short period of time. This method repeatedly tries to reuse that port until it succeeds /// or until 10 unsuccessful attempts are reached.</para> /// </summary> /// <returns>true if the function succeeds, false otherwise</returns> public bool Start() { log.Info("(Roles:[{0}])", this.Roles); int tryCounter = 0; bool res = false; while (tryCounter < 10) { try { this.Listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); this.Listener.Start(); res = true; break; } catch (SocketException se) { log.Info("Socket error code {0} occurred while trying to reuse socket: {1}.", se.SocketErrorCode, se.ToString()); } int waitTime = tryCounter * 3; log.Info("Will wait {0} seconds and then try again.", waitTime); Thread.Sleep(waitTime * 1000); tryCounter--; } if (res) { clientQueueHandlerThread = new Thread(new ThreadStart(ClientQueueHandlerThread)); clientQueueHandlerThread.Start(); acceptThread = new Thread(new ThreadStart(AcceptThread)); acceptThread.Start(); IsRunning = true; } log.Info("(-):{0}", res); return(res); }
/// <summary> /// Reads messages from the client stream and processes them in a loop until the client disconnects /// or until an action (such as a protocol violation) that leads to disconnecting of the client occurs. /// </summary> public async Task ReceiveMessageLoop() { log.Trace("()"); try { if (UseTls) { SslStream sslStream = (SslStream)Stream; await sslStream.AuthenticateAsServerAsync(Base.Configuration.TcpServerTlsCertificate, false, SslProtocols.Tls12, false); } Stream clientStream = Stream; byte[] messageHeaderBuffer = new byte[ProtocolHelper.HeaderSize]; byte[] messageBuffer = null; ClientStatus clientStatus = ClientStatus.ReadingHeader; uint messageSize = 0; int messageHeaderBytesRead = 0; int messageBytesRead = 0; while (!server.ShutdownSignaling.IsShutdown) { Task <int> readTask = null; int remain = 0; log.Trace("Client status is '{0}'.", clientStatus); switch (clientStatus) { case ClientStatus.ReadingHeader: { remain = ProtocolHelper.HeaderSize - messageHeaderBytesRead; readTask = clientStream.ReadAsync(messageHeaderBuffer, messageHeaderBytesRead, remain, server.ShutdownSignaling.ShutdownCancellationTokenSource.Token); break; } case ClientStatus.ReadingBody: { remain = (int)messageSize - messageBytesRead; readTask = clientStream.ReadAsync(messageBuffer, ProtocolHelper.HeaderSize + messageBytesRead, remain, server.ShutdownSignaling.ShutdownCancellationTokenSource.Token); break; } default: log.Error("Invalid client status '{0}'.", clientStatus); break; } if (readTask == null) { break; } log.Trace("{0} bytes remains to be read.", remain); int readAmount = await readTask; if (readAmount == 0) { log.Info("Connection has been closed."); break; } log.Trace("Read completed: {0} bytes.", readAmount); bool protoViolationDisconnect = false; bool disconnect = false; switch (clientStatus) { case ClientStatus.ReadingHeader: { messageHeaderBytesRead += readAmount; if (readAmount == remain) { if (messageHeaderBuffer[0] == 0x0D) { uint hdr = ProtocolHelper.GetValueLittleEndian(messageHeaderBuffer, 1); if (hdr + ProtocolHelper.HeaderSize <= ProtocolHelper.MaxSize) { messageSize = hdr; clientStatus = ClientStatus.ReadingBody; messageBuffer = new byte[ProtocolHelper.HeaderSize + messageSize]; Array.Copy(messageHeaderBuffer, messageBuffer, messageHeaderBuffer.Length); log.Trace("Reading of message header completed. Message size is {0} bytes.", messageSize); } else { log.Warn("Client claimed message of size {0} which exceeds the maximum.", hdr + ProtocolHelper.HeaderSize); protoViolationDisconnect = true; } } else { log.Warn("Message has invalid format - it's first byte is 0x{0:X2}, should be 0x0D.", messageHeaderBuffer[0]); protoViolationDisconnect = true; } } break; } case ClientStatus.ReadingBody: { messageBytesRead += readAmount; if (readAmount == remain) { clientStatus = ClientStatus.ReadingHeader; messageBytesRead = 0; messageHeaderBytesRead = 0; log.Trace("Reading of message size {0} completed.", messageSize); Message incomingMessage = CreateMessageFromRawData(messageBuffer); if (incomingMessage != null) { disconnect = !await messageProcessor.ProcessMessageAsync(this, incomingMessage); } else { protoViolationDisconnect = true; } } break; } } if (protoViolationDisconnect) { await messageProcessor.SendProtocolViolation(this); break; } if (disconnect) { break; } } } catch (Exception e) { if ((e is ObjectDisposedException) || (e is IOException)) { log.Info("Connection to client has been terminated."); } else { log.Error("Exception occurred: {0}", e.ToString()); } } log.Trace("(-)"); }