TCP socket concurrency

You found a bug or have any issues? Please post them here!
Post Reply
Thijmen
Posts: 92
Joined: 16 Jul 2019 20:19
TCP socket concurrency

Post by Thijmen »

Dear,

I have a background worker in C# code which interacts with ProppFrexx with the TCP client.
Sometimes, I send a command en retrieve the data, but I get input from another command.

Is there something possible to combat this?

Hereby my proof of concept code;

Code: Select all

using System.Net;
using System.Net.Sockets;
using System.Text;

var serverIp = "192.168.1.112"; // Replace with your server's IP address
var serverPort = 8052; // Replace with your server's port number
var serverEndPoint = new IPEndPoint(IPAddress.Parse(serverIp), serverPort);

using var client = new TcpClient();

client.Connect(serverEndPoint);

void SendTCP(string msg)
{
    var message = $"{msg}\r\n\r\n";
    var data = Encoding.ASCII.GetBytes(message);
    client.GetStream().Write(data, 0, data.Length);
}

string Receive()
{
    byte[] buffer = new byte[1024];
    int bytesRead = client.GetStream().Read(buffer, 0, buffer.Length);
    string response = Encoding.ASCII.GetString(buffer, 0, bytesRead);
  //  Console.WriteLine("Server response: {0}", response);

    return response;
}


void GetEntries()
{
    while (true)
    {
        SendTCP("AUTHORIZATION password");
        Receive();
        SendTCP("PLS_CURRENT_GET_ENTRIES");
        Receive();
    }
}

void GetName()
{
    while (true)
    {
        SendTCP("AUTHORIZATION password");
        Receive();
        SendTCP("PLS_CURRENT_NAME_GET");
        var data = Receive();
        Console.WriteLine($"current name: {data}");
    }

}

// Create two threads
Thread thread1 = new Thread(new ThreadStart(GetEntries));
Thread thread2 = new Thread(new ThreadStart(GetName));

// Start the threads
thread1.Start();
thread2.Start();

// Wait for the threads to finish
thread1.Join();
thread2.Join();

// Close the connection
client.Close();
Last edited by Thijmen on 04 May 2023 19:09, edited 1 time in total.
Thijmen
Posts: 92
Joined: 16 Jul 2019 20:19
Re: TCP socket concurrency

Post by Thijmen »

I guess there is some max length of the socket (Is it 1448 perhaps? :)). I need to do some while loop to fetch resuming data in the socket before I send a new command. PLS_CURRENT_GET_ENTRIES returns more than my buffer length (even if I do 1024 * 10 as buffer length, PF sends 1448 string length back).

Do you have any suggestions how I can combat my issue?

The code below doesnt work, because at some point the content.Length > 0 but it is also the last batch of data.

Code: Select all

void GetName()
{
    while (true)
    {
       
            SendTCP(client2, "AUTHORIZATION password");
            Receive(client2);
            SendTCP(client2, "PLS_CURRENT_GET_ENTRIES");
            var content = Receive(client2);
            while (content.Length > 1024)
            {
                content = Receive(client2);
            }

            SendTCP(client2, "PLS_CURRENT_NAME_GET");
            var data = Receive(client2);
            Console.WriteLine($"current name: {data}");
    }

}
So question is: what is the last character of a socket response or what is the max length of it?

Even if there is such character, "\n" perhaps, is it 100% sure that it is at the end and not splitted in 2 batches?
User avatar
radio42
Site Admin
Posts: 8295
Joined: 05 Apr 2012 16:26
Location: Hamburg, Germany
Contact:
Re: TCP socket concurrency

Post by radio42 »

I am sorry, but these are C# coding questions - which I can not answer here in this forum ;-)
So you'll still receive an example code here, but in general I do not answer C# coding questions here - these should be addressed to other forums...

Code: Select all

using System;
using System.Threading;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace ProppFrexx.RemoteControl
{
    /// <summary>
    /// Represents a TCP remote control client which is able to send commands and receive replies to and from a remote control server.
    /// </summary>
    /// <remarks>The client remote controls the server.</remarks>
    public class RemoteControlClient : IDisposable
    {
        #region IDisposable Members

        public void Dispose()
        {
            Disconnect();
        }

        #endregion

        private TcpClient _tcpClient = null;
        private NetworkStream _tcpStream = null;
        private Timer _pingTimer = null;
        private TimerCallback _timerDelegate;

        /// <summary>
        /// Creates a new instance of a TCP remote control client.
        /// </summary>
        public RemoteControlClient()
        {
            // Create the delegate that invokes methods for the timer.
            _timerDelegate = new TimerCallback(Ping);
        }

        /// <summary>
        /// Gets a value indicating, if the client is connected to a server.
        /// </summary>
        public bool IsConnected
        {
            get { return _tcpClient != null && _tcpClient.Connected; }
        }

        /// <summary>
        /// Connects the client to the TCP remote control server.
        /// </summary>
        /// <param name="host">The DNS of the remote control server to which you intend to connect.</param>
        /// <param name="port">The port number of the remote control server to which you intend to connect.</param>
        /// <param name="password">The authorization password of the TCP remote control server.</param>
        /// <returns>TRUE on success - else FALSE.</returns>
        public bool Connect(string host, int port, string password)
        {
            _tcpClient = new TcpClient(host, port);
            _tcpClient.NoDelay = true;
            _tcpClient.LingerState = new LingerOption(false, 0);
            _tcpClient.ReceiveTimeout = 5000;
            _tcpClient.SendTimeout = 5000;
            
            if (_tcpClient.Connected)
            {
                // get the network stream
                _tcpStream = _tcpClient.GetStream();

                // send authorization
                string reply = Send("AUTHORIZATION " + password + Environment.NewLine + Environment.NewLine);
                if (reply.StartsWith("OK" + Environment.NewLine))
                {
                    // start the ping timer
                    _pingTimer = new Timer(_timerDelegate, null, 5000, 5000);
                    return true;
                }
                else
                {
                    Disconnect();
                    throw new Exception("Remote Authorization failed!");
                }
            }

            return false;
        }

        /// <summary>
        /// Disconnects the client from the TCP remote control server.
        /// </summary>
        /// <returns></returns>
        public bool Disconnect()
        {
            if (_pingTimer != null)
            {
                _pingTimer.Change(Timeout.Infinite, Timeout.Infinite);
                _pingTimer.Dispose();
                _pingTimer = null;
            }
            if (_tcpStream != null)
            {
                _tcpStream.Close();
                _tcpStream.Dispose();
                _tcpStream = null;
            }
            if (_tcpClient != null)
            {
                _tcpClient.Close();
                _tcpClient = null;
            }
            return true;
        }

        /// <summary>
        /// Sends the TCP remote control message to a server.
        /// </summary>
        /// <param name="tcpMessage">The message resp. command to send.</param>
        /// <returns>The servers reply message on success - else an exception will be thrown.</returns>
        /// <remarks>
        /// Note: This command blocks
        /// </remarks>
        public string Send(string tcpMessage)
        {
            if (!IsConnected || _tcpStream == null)
            {
                throw new Exception("Not connected to the TCP remote control server.");
            }

            // the tcp message (must be terminated with a double CRLF)
            if (!tcpMessage.EndsWith(Environment.NewLine + Environment.NewLine))
            {
                if (!tcpMessage.EndsWith(Environment.NewLine))
                    tcpMessage += Environment.NewLine + Environment.NewLine;
                else
                    tcpMessage += Environment.NewLine;
            }

            string reply = String.Empty;
            lock (this)
            {
                byte[] bufferSend = Encoding.UTF8.GetBytes(tcpMessage);
                _tcpStream.Write(bufferSend, 0, bufferSend.Length);

                if (!tcpMessage.StartsWith("BYE"))
                {
                    // Read the reply (must also end with a double CRLF)
                    byte[] bufferRead = new byte[_tcpClient.ReceiveBufferSize];
                    int len;
                    DateTime _timeout = DateTime.Now;
                    do
                    {
                        // Read can return anything from 0 to numBytesToRead. 
                        // This method blocks until at least one byte is read.
                        len = _tcpStream.Read(bufferRead, 0, _tcpClient.Available);
                        if (len > 0)
                            reply += Encoding.UTF8.GetString(bufferRead, 0, len);
                        else if ((int)(DateTime.Now - _timeout).TotalMilliseconds > _tcpStream.ReadTimeout)
                            break;
                    }
                    while (!reply.EndsWith(Environment.NewLine + Environment.NewLine));
                }
            }

            return reply;
        }

        private void Ping(object stateInfo)
        {
            try
            {
                Send("PING" + Environment.NewLine + Environment.NewLine);
            }
            catch
            {
                Disconnect();
            }
        }

    }
}
There is no defined length of the reply, you basically loop until you receive all bytes.
Note, the reply also ends with a double CRLF.

Here is an example to read commands from a file and send them all one by one and receive the replies...

Code: Select all

private static bool ProcessCommandFile(string host, int port, string password, string fileName)
{
    bool ok = true;
    try
    {
        string[] commands = File.ReadAllLines(fileName);
        if (commands != null)
        {
            using (RemoteControl.RemoteControlClient remoteClient = new RemoteControl.RemoteControlClient())
            {
                ok = remoteClient.Connect(host, port, password);
                if (ok)
                {
                    foreach (string command in commands)
                    {
                        if (String.IsNullOrEmpty(command))
                            continue;

                        Console.WriteLine("Processing Command: {0}", command);
                        
                        string result = remoteClient.Send(command);

                        Console.WriteLine(result.TrimEnd());
                        
                        if (result.StartsWith("ERROR")
                            ok = false;
                    }

                    remoteClient.Send("BYE");
                    remoteClient.Disconnect();
                }
                else
                {
                    Console.WriteLine("Connect to server '{0}:{1}' failed!", host, port);
                }
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("ERROR: {0}", ex.Message);
        ok = false;
    }
    return ok;
}
Thijmen
Posts: 92
Joined: 16 Jul 2019 20:19
Re: TCP socket concurrency

Post by Thijmen »

Awesome, I only had to check for the double line endings. Thank you so much!

Post Reply