25

I have two simple applications:

  • A Server application that waits on a specific tcp port for a client to connect. Then listens to what he says, send back some feedback and DISCONNECT that client.

  • A Form application that connects to the server application, then says something, then wait for the feedback and disconnect from the server, then show the feedback in the form.

Though the server application seems to behave correctly (I have tested it with Telnet and I see the feedback and I see the disconnect occurring directly after the feedback), the form application however doesn't seem to notice the disconnect from the server. ( TcpClient.Connected seems to stay true even after the server has disconnected )

My question is: why is TcpClient.Connected staying true and how can I know if/when the server has disconnected?

Here is my full code:

Form application:

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Windows.Forms;
using System.Threading;

namespace Sender
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void sendButton_Click(object sender, EventArgs e)
        {
            TcpClient tcpClient = new TcpClient();
            tcpClient.Connect(IPAddress.Parse("127.0.0.1"), 81);
            responseLabel.Text = "waiting for response...";
            responseLabel.Invalidate();

            // write request
            NetworkStream networkStream = tcpClient.GetStream();
            byte[] buffer = (new ASCIIEncoding()).GetBytes("Hello World! ");
            networkStream.Write(buffer, 0, buffer.Length);
            networkStream.Flush();

            // read response
            Thread readThread = new Thread(new ParameterizedThreadStart(ReadResponse));
            readThread.Start(tcpClient);
        }

        void ReadResponse(object arg)
        {
            TcpClient tcpClient = (TcpClient)arg;
            StringBuilder stringBuilder = new StringBuilder();
            NetworkStream networkStream = tcpClient.GetStream();
            bool timeout = false;
            DateTime lastActivity = DateTime.Now;
            while (tcpClient.Connected && !timeout)
            {
                if (networkStream.DataAvailable)
                {
                    lastActivity = DateTime.Now;
                    while (networkStream.DataAvailable)
                    {
                        byte[] incomingBuffer = new byte[1024];
                        networkStream.Read(incomingBuffer, 0, 1024);
                        char[] receivedChars = new char[1024];
                        (new ASCIIEncoding()).GetDecoder().GetChars(incomingBuffer, 0, 1024, receivedChars, 0);
                        stringBuilder.Append(receivedChars);
                    }
                }
                else
                {
                    if (DateTime.Now > lastActivity.AddSeconds(60))
                        timeout = true;
                }
                System.Threading.Thread.Sleep(50);
            }
            Invoke((MethodInvoker)delegate
            {
                responseLabel.Text = "Response from Listener:\n" + stringBuilder.ToString();
                responseLabel.Invalidate();
            });

            if (timeout)
            {
                Console.Write("A timeout occured\n");
                networkStream.Close();
                tcpClient.Close();
            }
        }

    }
}

Server application:

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

namespace Listener
{
    class Program
    {
        static void Main(string[] args)
        {
            var tcpListener = new TcpListener(IPAddress.Any, 81);
            tcpListener.Start();
            Thread clientThread = new Thread(new ParameterizedThreadStart(Listen));
            clientThread.Start(tcpListener);
        }

        static void Listen(object arg)
        {
            TcpListener tcpListener = (TcpListener)arg;
            while (true)
            {
                TcpClient tcpClient = tcpListener.AcceptTcpClient();
                Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClient));
                clientThread.Start(tcpClient);
            }
        }

        static void HandleClient(object arg)
        {
            TcpClient tcpClient = (TcpClient)arg;
            StringBuilder stringBuilder = new StringBuilder();
            ASCIIEncoding encoder = new ASCIIEncoding();
            DateTime lastActivity = DateTime.Now;

            // read request
            NetworkStream networkStream = tcpClient.GetStream();
            int timeout = 5; // gives client some time to send data after connecting
            while (DateTime.Now < lastActivity.AddSeconds(timeout) && stringBuilder.Length==0)
            {
                if (!networkStream.DataAvailable)
                {
                    System.Threading.Thread.Sleep(50);
                }
                else
                {
                    while (networkStream.DataAvailable)
                    {
                        lastActivity = DateTime.Now;
                        byte[] incomingBuffer = new byte[1024];
                        networkStream.Read(incomingBuffer, 0, 1024);
                        char[] receivedChars = new char[1024];
                        encoder.GetDecoder().GetChars(incomingBuffer, 0, 1024, receivedChars, 0);
                        stringBuilder.Append(receivedChars);
                    }
                }
            }
            string request = stringBuilder.ToString();

            // write response
            string response = "The listener just received: " + request;
            byte[] outgoingBuffer = encoder.GetBytes(response);
            networkStream.Write(outgoingBuffer, 0, outgoingBuffer.Length);
            networkStream.Flush();

            networkStream.Close();
            tcpClient.Close();
        }
    }

}
nl-x
  • 11,762
  • 7
  • 33
  • 61

3 Answers3

22

TcpClient / NetworkStream does not get notified when the connection is closed. The only option available to you is to catch exceptions when writing to the stream.

A few years back we moved to using sockets instead of tcp client. socket is more usable as compared to tcpclient.

there are a couple of methods that you can use

Poll is one of them

http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.poll.aspx

You can also do a check on outcome of Write itself. it gives you the number of bytes actually written.

The Connected property itself only reflects the state at the last operation. Its documentation states "The value of the Connected property reflects the state of the connection as of the most recent operation. If you need to determine the current state of the connection, make a non-blocking, zero-byte Send call. If the call returns successfully or throws a WAEWOULDBLOCK error code (10035), then the socket is still connected; otherwise, the socket is no longer connected."

http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.connected.aspx

GregC
  • 7,737
  • 2
  • 53
  • 67
Hermit Dave
  • 3,036
  • 1
  • 13
  • 13
  • agreed, socket would be a far better option to use here – XikiryoX Feb 25 '13 at 12:53
  • NetworkStream itsself uses a Socket (though its not a public propertie). How would I be able to use that? In other words: If I was using Socket, how would I proceed to know if the client has disconnected? – nl-x Feb 25 '13 at 12:59
  • Have a look at Poll.. I agree that Network Stream and TcpClient uses Socket but they provide a high level interface.. Socket however is socket.. what you see is what you get :) – Hermit Dave Feb 25 '13 at 13:10
  • @nl-x - If the property isn't public you can't use it. – Security Hound Feb 25 '13 at 13:17
  • 4
    I don't really understand it yet. Why poll the socket (I think this means actually trying to use the socket and see if it fails, thus causing overhead), while the server already has told the client that it has disconnected. Even telnet directly sees the disconnect and warns me, so this message must be sent from the server and the OS passes this message to the application... Can you please help me understand? – nl-x Feb 25 '13 at 13:22
  • Poll takes a time parameter.. you can wait a 50 milliseonds or 500 its your choice. As far as I know, the state disconnect isn't set on .NET Socket. Poll finally allows you to do that in a quick fashion – Hermit Dave Feb 25 '13 at 13:24
  • 3
    Just to be absolutely clear: So c#/.NET does not remember if the connected party has gracefully disconnected? It will poll the connection to know if the connection is disconnected, even if the connected party has gracefully disconnected earlier? – nl-x Feb 25 '13 at 13:31
  • As far as I know, .NET socket does not know of other party disconnecting. Poll is the fastest way of finding out. – Hermit Dave Feb 25 '13 at 13:34
  • 5
    Answer accepted. Though I still don't fully agree. The RFC http://tools.ietf.org/html/rfc793#page-38 (case 2) states that the user (application?) will be told a FIN (disconnect announcement) has been received. This elementary bit of data MUST be present somewhere in .NET... – nl-x Feb 25 '13 at 14:26
  • done some more reading and here's additional documentation from Connected Property "The value of the Connected property reflects the state of the connection as of the most recent operation. If you need to determine the current state of the connection, make a nonblocking, zero-byte Send call. If the call returns successfully or throws a WAEWOULDBLOCK error code (10035), then the socket is still connected; otherwise, the socket is no longer connected." – Hermit Dave Feb 25 '13 at 14:30
  • 1
    @HermitDave Yeah, I read that too. It's almost the same as you suggested earlier. Though as per my previous comment: I really don't understand why .NET would just not keep track of graceful tcp disconnect status. If the other party announces that it will disconnect, then there at least should be an event for this. – nl-x Feb 25 '13 at 14:39
  • I know.. the sad thing is that the behaviour has bee in place since .NET 1.1.. there you go.. – Hermit Dave Feb 25 '13 at 14:44
  • 16
    **if (tcpClient.Client.Poll(1, SelectMode.SelectRead) && !networkStream.DataAvailable) {** *// Check if connection is closed: When using SelectRead, Poll() will return true only if: (1) We are listening; which we are not. (2) New data is available; so we check networkStream.DataAvailable AFTER calling Poll(), because it could change during Poll() (3) The connection was indeed closed.* **}** – nl-x Feb 25 '13 at 16:51
  • @nl-x: "This elementary bit of data MUST be present somewhere in .NET..." it is not present in .NET itself, but you can check all existing TCP connections to see whether yours is still open, see my answer. You don't have to use `Socket`. – Tobias Knauss Jun 10 '22 at 09:02
  • @nl-x Thank you. This is the only method I've found anywhere that actually works! – Peter Morris Jun 16 '22 at 09:02
2

There is another way to detect the correct state of a TCP connection, where you don't have to use Socket. You can keep your TcpClient and use IPGlobalProperties.
The idea was taken from this answer.

Notice: There is a bug in .NET 5 which causes a memory leak when calling IPGlobalProperties.GetActiveTcpConnections(). This bug is not present in .NET Framework, and it is fixed in .NET 6. (https://github.com/dotnet/runtime/issues/64735)

public class CTcpipConnection
{
  public enum EnumState
  {
    NotInitialized,
    NotReady,
    Idle,
    Connecting,
    Connected,
    Disconnecting
  }

  public string IpAddress { get; }  // will be set in ctor
  public int    LocalPort { get; }  // will be set in Connect() and Disconnect()
  public int    Port      { get; }  // will be set in ctor

  EnumState GetState ()
  {
    var ipGlobProp   = IPGlobalProperties.GetIPGlobalProperties ();
    var tcpConnInfos = ipGlobProp.GetActiveTcpConnections ();
    TcpConnectionInformation tcpConnInfo = null;
    for (int index = 0; index < i_tcpConnInfos.Length; index++)
    {
      if (i_tcpConnInfos[index].LocalEndPoint.Port  == LocalPort
       && i_tcpConnInfos[index].RemoteEndPoint.Port == Port)
      {
        tcpConnInfo = i_tcpConnInfos[index];
        break;
      }
    }

    if (tcpConnInfo == null)
      return EnumState.Idle;

    var tcpState = tcpConnInfo.State;
    switch (tcpState)
    {
    case TcpState.Listen:
    case TcpState.SynSent:
    case TcpState.SynReceived:
      return EnumState.Connecting;

    case TcpState.Established:
      return EnumState.Connected;

    case TcpState.FinWait1:
    case TcpState.FinWait2:
    case TcpState.CloseWait:
    case TcpState.Closing:
    case TcpState.LastAck:
      return EnumState.Disconnecting;

    default:
      return EnumState.NotReady;
    }
  }

  private void Connect ()
  {
    m_tcpclient.Connect (IpAddress, Port);
    m_netstream = m_tcpclient.GetStream ();
    var ipendpoint = m_tcpclient.Client.LocalEndPoint as IPEndPoint;
    LocalPort = ipendpoint.Port;
  }

  private void Disconnect ()
  {
    if (m_netstream != null)
    {
      m_netstream.Flush ();
      m_netstream.Close (500);
    }

    m_tcpclient.Close ();
    LocalPort   = 0;
    m_tcpclient = new TcpClient ();
  }

}
Tobias Knauss
  • 3,361
  • 1
  • 21
  • 45
0

If you do a blocking Read with infinite timeout i.e.

                NetworkStream.ReadTimeout = -1

then in this case Read method will return a zero when connection is lost

                // Reading everything from network to memory stream

                var s = new MemoryStream();

                var buf = new byte[client.ReceiveBufferSize];
                                        
                do
                {
                    var n = stream.Read(buf, 0, buf.Length);
                    if(n == 0)
                    {
                        return; // connection is lost
                    }
                    s.Write(buf, 0, n);
                }
                while(s.Length < packetsize);
Mightywill
  • 511
  • 4
  • 8