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
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.