Search code examples
c#.netsocketstcpclientnetworkstream

Reading information from a port waits even though data available


I am having a problem sending information down a socket and receiving a response. I have a demo program which is performing correctly so I know it is not an issue with the client on the other end.

The requestData is sent and the client acts correctly and responds, but my code never exits the loop in read response.

Could the client be responding before I am listening? How can I make sure I never miss an incoming message?

            networkStream = tcpClient.GetStream();
            StreamWriter clientStreamWriter = new StreamWriter();
            clientStreamWriter.WriteLine(requestData);
            clientStreamWriter.Flush();

            // Continuously read data on the socket and if it is more than just a ping, read the response.
            StringBuilder sbReadBuffer = new StringBuilder();

            while (true)
            {
                String response = readresponse(timeoutOn30Seconds);

                if (response.Length > 1 && (!response.Contains("\r\n") || response.Contains(",")))
                {
                    break;
                }
            }

            sbReadBuffer.Append(received);

            return sbReadBuffer.ToString();

readResponse:

    private string readresponse(Boolean timeoutOn30Seconds)
    {
        // Get network stream.
        DateTime lastConTime = DateTime.Now;
        Int32 i = 0;

        // Start listening to stream.
        while (!networkStream.DataAvailable)
        {
            Log.W(".");
            // Only check every 10ms.
            Thread.Sleep(10);

            // If no response in 30mins stop listening and give an offline warning.
            if ((DateTime.Now - lastConTime).TotalSeconds > tcpClient.ReceiveTimeout)
            {
                received = "CLIENT NOT FOUND";

                return received;
            }

            // Only check if application online every 1s.
            if (i > 100)
            {
                if (Process.GetProcessesByName(ConfigurationManager.AppSettings["ClientName"]).FirstOrDefault() == null && Convert.ToInt32(ConfigurationManager.AppSettings["Device"]) != 680)
                {
                    received = "CLIENT NOT FOUND";

                    return received;
                }

                i = 0;
            }

            i++;
        }

        // If data has been writted to the buffer, read it out and assign output variable and return that string for length comparison.
        byte[] receiveBuffer = new byte[tcpClient.ReceiveBufferSize];
        Int32 receiveCount = networkStream.Read(receiveBuffer, 0, receiveBuffer.Length);
        received = new ASCIIEncoding().GetString(receiveBuffer, 0, receiveCount);

        return received;
    }

Solution

  • DataAvailable isn't a good method to know if data are coming, especially in a while loop that is surely faster than network communications.

    A better way could be to use the Read method (bytes read) to know where data are available, into a timed loop; so change your while condition in this manner (and then adjust the other parts)

    while (networkStream.Read(receiveBuffer, 0, receiveBuffer.Length) > 0)
        {
            Log.W(".");
            // Only check every 10ms.
            Thread.Sleep(10);
    

    but I prefer, if possible, an async approach, so your client will be notified when data are incoming.

    See this answer that use this kind of approach.

    Basically set an async callback that will be fired when data are coming

    public void StartListening() {
    IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
    IPEndPoint localEP = new IPEndPoint(ipHostInfo.AddressList[0],11000);
    
    Console.WriteLine("Local address and port : {0}",localEP.ToString());
    
    Socket listener = new Socket( localEP.Address.AddressFamily,
        SocketType.Stream, ProtocolType.Tcp );
    
    try {
        listener.Bind(localEP);
        listener.Listen(10);
    
        while (true) {
            allDone.Reset();
    
            Console.WriteLine("Waiting for a connection...");
            listener.BeginAccept(
                new AsyncCallback(SocketListener.acceptCallback), 
                listener );
    
            allDone.WaitOne();
        }
    } catch (Exception e) {
        Console.WriteLine(e.ToString());
    }
    
    Console.WriteLine( "Closing the listener...");
    }
    

    and there you can read your data

    public static void acceptCallback(IAsyncResult ar) {
        // Get the socket that handles the client request.
        Socket listener = (Socket) ar.AsyncState;
        Socket handler = listener.EndAccept(ar);
    
        // Signal the main thread to continue.
        allDone.Set();
    
        // Create the state object.
        StateObject state = new StateObject();
        state.workSocket = handler;
        handler.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,
            new AsyncCallback(AsynchronousSocketListener.readCallback), state);
    }
    

    Here full MSDN documentation