Search code examples
c#tcpclienttcplistenerdisconnect

Display message when TCPClient disconnects


I've been trying to get my server application to display a message in a label when my client has disconnected from it.

Right now the label displays the connected client's IP address upon the client being started, but when the client is shut down, the IP address is still displayed in the label.

I've tried these methods so far without any luck:

            // DETECT IF CLIENT DISCONNECTED

//METHOD 1
            if (bytesRead == 0)
            {
                ClientIPLabel.Text = "(No Clients Connected)";
                break;
            }

//METHOD 2
            if (!tcpListener.Pending())
            {
                ClientIPLabel.Text = "(No Clients Connected)";
            }

//METHOD 3
            if (tcpClient.Client.Poll(0, SelectMode.SelectRead))
            {
                byte[] buff = new byte[1];
                if (tcpClient.Client.Receive(buff, SocketFlags.Peek) == 0)
            {
                ClientIPLabel.Text = "(No Clients Connected)";
            }                  

I'm sure it's likely something simple that I'm missing, I just can't figure out what it might be.


EDIT: I've found a bit of a workaround where when I click the button that closes my client app, the client sends a message to the server before closing. The server knows that if it receives this particular message (in this case "SHUTDOWN CLIENT") to change the text in the 'ClientIPLabel.text' to "(No Clients Connected)"

This does work for the most part, but it's kind of a hack, and if the client closed due to an error or crash, etc. The server wouldn't know it has disconnected, so it would still display the last known IP it was sent.

2nd EDIT: So it appears that workaround won't work for me. After adding that to my project, for some reason no matter what message my client sends to the server results in the "(No Clients Connected)" message being displayed.

My client is still actually connected, and receiving messages, but the 'ClientIPLabel' is not labelled correctly



3rd EDIT: This is a snippet showing how I've got my client set up to send messages to the server as per detailed in one of my comments below:

private void SendMessage(string msg)
    {
        NetworkStream clientStream = ConsoleClient.GetStream();

        ASCIIEncoding encoder = new ASCIIEncoding();
        byte[] buffer = encoder.GetBytes(msg);

        clientStream.Write(buffer, 0, buffer.Length);
        clientStream.Flush();
    }

4th EDIT: My server code, with my temporary workaround in getting a client disconnect message directly from the client shutdown button:

public partial class Server : Form
{
    private TcpListener tcpListener;
    private Thread listenThread;       
    private delegate void WriteMessageDelegate(string msg);

    public Server()
    {
        InitializeComponent();
        Server();
    }


    // WAIT FOR CLIENT

    private void Server()
    {
        this.tcpListener = new TcpListener(IPAddress.Any, 8888);
        this.listenThread = new Thread(new ThreadStart(ListenForClients));
        this.listenThread.Start();
    }

    private void ListenForClients()
    {
        this.tcpListener.Start();


    // GET CLIENT IP ADDRESS

        while (true)
        {
            TcpClient client = this.tcpListener.AcceptTcpClient();                 
            string clientIPAddress = "" + IPAddress.Parse(((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString());
            ClientIPLabel.Text = clientIPAddress;
            Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
            clientThread.Start(client);               
        }
    }


    // COMMUNICATE WITH CLIENT

    private void HandleClientComm(object client)
    {
        TcpClient tcpClient = (TcpClient)client;
        NetworkStream clientStream = tcpClient.GetStream();

        byte[] message = new byte[4096];
        int bytesRead;

        while (true)
        {
            bytesRead = 0;

            try
            {
                bytesRead = clientStream.Read(message, 0, 4096);
            }
            catch
            {
                break;
            }


            ASCIIEncoding encoder = new ASCIIEncoding();


    // CHECK FOR CLIENT DISCONNECT

            string msg = encoder.GetString(message, 0, bytesRead);
            WriteMessage(msg);

            if (msg.Equals("Client Disconnected (" + DateTime.Now + ")"))
            {
                ClientIPLabel.Text = ("(No Client Connected)");
            }              
        }

       tcpClient.Close();
    }


5th EDIT: I've updated my code, and I've (almost) got the heartbeat timer implemented, but it's still not setup quite right... Here's my code:

// CHECK FOR CLIENT DISCONNECT

            // SHUTDOWN BUTTON PRESSED
            string msg = encoder.GetString(message, 0, bytesRead);
            WriteMessage(msg);

            if (msg.Equals("Client Disconnected (" + DateTime.Now + ")"))
            {
                ClientIPLabel.Text = ("(No Client Connected)");
            }              
        }                        
       tcpClient.Close();
    }

    // CHECK FOR CONNECTION FAILED 
    void heartbeatTimer_Elapsed(object sender, ElapsedEventArgs e)
    {
        try
        {
            HandleClientComm("Check for connection");
        }
        catch (Exception)
        {                
            Invoke((MethodInvoker)delegate
            {
                ClientIPLabel.Text = "(No Clients Connected)";
            });
        }
    }

The "(No Clients Connected)" message pops up on my server automatically after the timer, regardless of whether or not my client has disconnected or not, so it's not catching the exception properly.

I've tried implementing interceptwind's suggestion as he wrote it, but for some reason where I should have catch (Exception e) I'm only able to build it if I get rid of the e and write it as catch (Exception). If I leave the e there, I get an warning saying "The variable 'e' is declared but never used".

Also, I know interceptwind wrote his example to use my SendMessage() method, but that method only gets used in my client, so I changed the code to try and use my HandleClientComm() method, so I'm wondering if that's the reason this isn't working properly. I've tried changing a few things around, but still can't seem to get it working. After 5 seconds I still get the message "(No Clients Connected)" even though my client IS still connected and functioning properly.


6th EDIT: I've attempted adjusting my HandleClientComm() method to be able to send messages, but I've obviously missed something, because my "heartbeat timer" is still switching my ClientIPLabel.text to "(No Clients Connected)" even though my client is still connected.

Here is my code:

    // COMMUNICATE WITH CLIENT

    private void HandleClientComm(object client)
    {
        TcpClient tcpClient = (TcpClient)client;
        NetworkStream clientStream = tcpClient.GetStream();
        ASCIIEncoding encoder = new ASCIIEncoding();
        byte[] message = new byte[4096];
        byte[] buffer = encoder.GetBytes("Send Message");
        int bytesRead;
        clientStream.Write(buffer, 0, buffer.Length);
        clientStream.Flush();
        while (true)
        {
            bytesRead = 0;

            try
            {
                bytesRead = clientStream.Read(message, 0, 4096);
            }
            catch
            {
                break;
            }                                


            // START HEARTBEAT TIMER

            System.Timers.Timer heartbeatTimer = new System.Timers.Timer();
            heartbeatTimer.Interval = 5000;
            heartbeatTimer.Elapsed += heartbeatTimer_Elapsed;
            heartbeatTimer.Start();

            // CHECK FOR CLIENT DISCONNECT

            // SHUTDOWN BUTTON PRESSED
            string msg = encoder.GetString(message, 0, bytesRead);
            WriteMessage(msg);

            if (msg.Equals("Client Disconnected (" + DateTime.Now + ")"))
            {
                ClientIPLabel.Text = ("(No Client Connected)");
            }              
        }                        
       tcpClient.Close();
    }

    // CHECK FOR CONNECTION FAILED 
    void heartbeatTimer_Elapsed(object sender, ElapsedEventArgs e)
    {
        try
        {
            HandleClientComm("Check for connection");
        }
        catch (Exception)
        {                
            Invoke((MethodInvoker)delegate
            {
                ClientIPLabel.Text = "(No Clients Connected)";
            });
        }
    }

Solution

  • Given OP's restriction that Server cannot send message to Client, the other way is for Client to send heartbeat messages to Server every X seconds (eg. 5 sec).

    Server will then check whether it has received any message from Client over the past Y seconds (eg. 30 Sec). If it doesn't, that means Client is disconnected.

    Client's Code

        public Client()
        {
            ...
    
            //After connection, CALL ONCE ONLY
            Timer heartbeatTimer = new System.Timers.Timer();
            heartbeatTimer.Interval = 5000; //5 seconds
            heartbeatTimer.Elapsed += heartbeatTimer_Elapsed;
            heartbeatTimer.Start(); 
        }
    
        void heartbeatTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            SendMessage("Heartbeat");
        }
    

    Server's Code

        bool receiveHeartBeat = false;
    
        public Server()
        {
            ...
    
            //After connection, CALL ONCE ONLY
            Timer checkHeartbeatTimer = new System.Timers.Timer();
            checkHeartbeatTimer.Interval = 30000; //30 seconds
            checkHeartbeatTimer.Elapsed += checkHeartbeatTimer_Elapsed;
            checkHeartbeatTimer.Start(); 
        }
    
        void checkHeartbeatTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            if(receiveHeartBeat)
            {
                Invoke((MethodInvoker)delegate //prevent cross-thread exception
                {
                    ClientIPLabel.Text = "Connected";
                });
            }
            else
            {
                Invoke((MethodInvoker)delegate //prevent cross-thread exception
                {
                    ClientIPLabel.Text = "(No Clients Connected)";
                });
            }
        }
    
        void WriteMessage(string msg)
        {
            receiveHeartBeat = true;
            // rest of your code
    
        }