Search code examples
c#.netubuntunginxtcp

TCP Listener in .NET 8 deployed on Ubuntu Server environment Responds Slowly Compared to Local Execution


I have a TCP listener application written in .NET 8 which is supposed to listen for incoming client connection, accept connection and process the the massage that comes in and responds to the client.

Locally when I run the TCP listener and client, I am able to send message from the client to the listener and the listener responds fast as it should.

Below is the code for the listener, Please note the listener processes an ISO8583 message

public class TcpServerHostedService : BackgroundService
{
    IPAddress _ipAddress = IPAddress.Parse("127.0.0.1");
    int _port = 2000;
    private readonly IServiceProvider _serviceProvider;
    private readonly IConfiguration _configuration;
    private readonly ILogger<TcpServerHostedService> _logger;

    public TcpServerHostedService(IServiceProvider serviceProvider,
        IConfiguration configuration,
        ILogger<TcpServerHostedService> logger)
    {
        _serviceProvider = serviceProvider;
        _configuration = configuration;
        _logger = logger;
    }



    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {

        TcpListener listener = new TcpListener(_ipAddress, _port);
        listener.Start();
        Console.WriteLine("EchoServer started...");
        while (!stoppingToken.IsCancellationRequested)
        {
            Console.WriteLine("Waiting for incoming client connections...");
            TcpClient client = await listener.AcceptTcpClientAsync();
            Console.WriteLine("Accepted new client connection...");
            NetworkStream stream = client.GetStream();

            try
            {
                using (var scope = _serviceProvider.CreateScope())
                {
                    var _transactionService = scope.ServiceProvider.GetRequiredService<IAfslTransactionService>();
                    var _mailService = scope.ServiceProvider.GetRequiredService<IMailService>();


                    while (!stoppingToken.IsCancellationRequested)
                    {
                        // Read the header bytes
                        byte[] headerBytes = new byte[2];
                        stream.Read(headerBytes, 0, 2);

                        // Calculate the message length based on the header bytes
                        int messageLength = headerBytes[0] * 256 + headerBytes[1];

                        // Receive data from the client
                        byte[] receiveBuffer = new byte[messageLength];
                        int bytesRead = await stream.ReadAsync(receiveBuffer, 0, receiveBuffer.Length);

                        // Convert received bytes to string
                        string receivedData = Encoding.UTF8.GetString(receiveBuffer, 0, bytesRead);


                        // Process the ISO 8583 message
                        string response = await ProcessIsoMessage(receivedData, _transactionService, _mailService);



                        // Convert string message to bytes using UTF-8 encoding
                        string message = response;
                        byte[] messageBytes = Encoding.UTF8.GetBytes(message);

                        // Get the length of the message and convert it to bytes (using 2 bytes for simplicity)
                        byte[] resheaderBytes = BitConverter.GetBytes((short)messageBytes.Length);

                        // Ensure the bytes are in network byte order (big endian)
                        if (BitConverter.IsLittleEndian)
                        {
                            Array.Reverse(resheaderBytes);
                        }

                        // Send the length header bytes to the stream
                        await stream.WriteAsync(resheaderBytes, 0, resheaderBytes.Length);

                        // Send the actual message bytes to the stream
                        await stream.WriteAsync(messageBytes, 0, messageBytes.Length);

                        await stream.FlushAsync();
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "An error occurred on TCP");
            }
        }
    }


    private async Task<string> ProcessIsoMessage(string isoMessage, IAfslTransactionService _transactionService, IMailService _mailService)
    {
        MessageParser.NET.Tools.ISO8583 iso = new MessageParser.NET.Tools.ISO8583();
        string[] data = iso.Parse(isoMessage);


        Dictionary<int, string> isoFields = ParseStringArrayToDictionary(data);

        string messageTypeCode = isoFields.ContainsKey(0) ? isoFields[0] : "";

        if (!string.IsNullOrEmpty(messageTypeCode))
        {
            Dictionary<int, string> result = new Dictionary<int, string>();

            switch (messageTypeCode)
            {
                case "0100":
                    result = await _transactionService.ProcessHoldReleaseFunds(isoFields);
                    break;
                case "0200":
                    result = await _transactionService.ProcessFinancialTransaction(isoFields);
                    break;
                case "0420":
                    result = await _transactionService.ProcessReversal(isoFields);
                    break;
                case "0800":
                    result = await _transactionService.ProcessNetworkManagement(isoFields);
                    break;
            }

            //Message Type Identifier (Financial Message Response)
            string MTI = result[0];

            //1. Create Arrays AND Assign corresponding data to each array
            string[] DE = ParseDictionaryToStringArray(result);

            //3.Use "Build" method of object iso8583 to create a new  message.
            string NewISOmsg = iso.Build(DE, MTI);

            // Send ISO MESSAGE
            return NewISOmsg;
        }

        return string.Empty;
    }


    static Dictionary<int, string> ParseStringArrayToDictionary(string[] array)
    {
        Dictionary<int, string> dictionary = new Dictionary<int, string>();

        for (int i = 0; i < array.Length; i++)
        {
            // Extract key and value from the item
            int key = i; // Assuming keys are single digits
            string value = array[i];

            // Check if key is "129", assign it to key "0" in dictionary
            if (key == 129)
            {
                dictionary[0] = value;
            }
            // Check if key is "0" or "1", merge them and assign to key "1" in dictionary
            else if (key == 0 || key == 1)
            {
                if (dictionary.ContainsKey(1))
                {
                    dictionary[1] += value;
                }
                else
                {
                    dictionary[1] = value;
                }
            }
            // Check if the value is not "null"
            else if (value != null)
            {
                dictionary[key] = value;
            }
        }

        return dictionary;
    }

    static string[] ParseDictionaryToStringArray(Dictionary<int, string> dictionary)
    {
        dictionary.Remove(0);
        dictionary.Remove(1);
        string[] DE = new string[130];

        for (int i = 0; i < DE.Length; i++)
        {
            string value;

            dictionary.TryGetValue(i, out value);

            DE[i] = !string.IsNullOrEmpty(value) ? value : null;
        }

        return DE;
    }

}

The problem I am facing here is that when I deploy the listener on an ubuntu server I send a message from the TCP Client to the listener and it takes time to respond sometimes even up to 3-5 minutes and this is not good.

Just to mention, I have an Nginx server that handles incoming request for Stream and sends it to my application on the ubuntu server but I don't think the nginx is the problem because I tried binding the application port to the server port direction and I still got same issue of delayed response.

Please has anyone faced this type of challenge and what can I do to resolve this issue, because this doesn't look like a code issue


Solution

  • Well, for one thing, your message framing code is incorrect. I explain what message framing is on my blog. A red flag for this is stream.Read with a discarded return code - such code must be wrong; there is no way for it to be correct.

    Since you're on .NET 8, there's some nifty ReadExactly methods available:

    // Read the header bytes
    byte[] headerBytes = new byte[2];
    stream.ReadExactly(headerBytes);
    
    // Calculate the message length based on the header bytes
    int messageLength = headerBytes[0] * 256 + headerBytes[1];
    
    // Receive data from the client
    byte[] receiveBuffer = new byte[messageLength];
    await stream.ReadExactlyAsync(receiveBuffer);
    

    Is this the cause of the slowdown? Maybe. If your client automatically retries its requests (with a delay) and if the first (few?) try failed, then it could cause what would appear to be a slowdown.