2

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)";
            });
        }
    }
Patrick
  • 430
  • 6
  • 21
  • Your server app has a GUI? – B. Clay Shannon-B. Crow Raven Dec 24 '15 at 18:07
  • Yes, it just sits in the system tray but it also has a basic GUI that you can open that displays messages that my client sends to the server (kind of like a log), but it also shows the IP of the currently connected client at the bottom of the GUI. – Patrick Dec 24 '15 at 18:16
  • Does the object you're using for listening to the client have an event such as "OnDisconnected" or such that you could monitor? – B. Clay Shannon-B. Crow Raven Dec 24 '15 at 18:18
  • 1
    I don't believe so. I'm pretty new to this, which is why I'm having this issue. How would I go about creating an "OnDisconnected" event? Because that sounds like exactly what I need. I've come up with a temporary workaround, and updated my question, but I'd like a better solution than what I've come up with. – Patrick Dec 24 '15 at 18:22
  • 1
    You are going to want to implement a ping/pong packet to determine when a client has disconnected. That way you can more easily control how long a client has to respond before considering them disconnected. Otherwise you can try messing with the RetransmissionTimeout(https://msdn.microsoft.com/en-us/library/windows/desktop/ms738596(v=vs.85).aspx) and MaxRetransmission(https://msdn.microsoft.com/en-us/library/windows/desktop/jj710204(v=vs.85).aspx). – Will Dec 24 '15 at 19:27
  • So what you're saying is I would need the client to (almost) continuously tell the server "I'm still here!" in order for the "No Clients Connected" message to not come up? – Patrick Dec 24 '15 at 19:32
  • 1
    Yes, since the TCP protocol is designed to allow connections to be interrupted for a reasonable time(generally minutes). Depending on the underlying provider(e.g. winsock) the connection may be left open for hours(http://stackoverflow.com/questions/13085676) by default. – Will Dec 24 '15 at 19:42
  • I'm pretty new to this, is there a chance you might be able to explain to me how to implement this into my project code, or give me an example? Thanks. – Patrick Dec 24 '15 at 19:57
  • As a side note, do I have `tcpClient.close()` in the right spot? I was just looking at that, and it doesn't seem like it should be there... – Patrick Dec 27 '15 at 20:08

3 Answers3

1

You probably need to use:

if (TcpClient.Connected){}
Aaron Thomas
  • 1,770
  • 2
  • 13
  • 14
1

Will's comment is correct. You probably need to implement a Heartbeat mechanism, where you send a Heartbeat (or dummy) message to your client periodically. If you get an error when you tried to send Heartbeat, then you know that your client has been disconnected.

The simplest way to implement this will look something like this:

    //using System.Timers;

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

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

        //rest of your code
    }


    void heartbeatTimer_Elapsed(object sender, ElapsedEventArgs e)
    {
        try
        {
            SendMessage("Can be anything :D");
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message); //Check what is the exception

            //Fail to send message - client disconnected.
            Invoke((MethodInvoker)delegate //prevent cross-thread exception
            {
                ClientIPLabel.Text = "(No Clients Connected)";
            });
        }
    }
interceptwind
  • 665
  • 4
  • 14
  • Awesome! That looks like it'll work great! I haven't had a chance to test it out yet, but I had one question about part of it. Where it's sending the message "HEARTBEAT" it looks as though it's being sent as a string "msg", is that correct? I only ask, because the main part of my app relies on my client sending messages to the server as "msg" already, so I could see that conflicting with the main function of my app. If that won't be the case, then that's great. Otherwise, am I able to change "msg" to something else, like "message" or something like that? Or will I need to worry about that? – Patrick Dec 25 '15 at 06:28
  • I've updated my question to include the bit of code where my client sends messages to the server already – Patrick Dec 25 '15 at 06:30
  • Ughhh.... I hate being so new at this. I've tried adjusting your code to get it to fit into a couple different places in my project, and I just can't seem to get it to work properly. I've updated my question with my server code if anyone feels like helping me get the "heartbeat mechanism" working with it. Sorry for being so noobish... – Patrick Dec 25 '15 at 07:00
  • We are all noobs at one point or another, no worries :) Anw I have modified my answer to use your ListenForClient() and SendMessage() methods. You see if it works? This is a one way heartbeat: Server send heartbeat to Client only. I am assuming your SendMessage() will only throw exception when trying to send message after your Client disconnected. If not, you might need to look out for specific exceptions. Try disabling your client's network adaptor (or remove LAN cable) while client is connected to test this. – interceptwind Dec 27 '15 at 01:16
  • Thanks a lot! I think I've almost got it working, still a bit of an issue though, I've updated my question detailing the issue I'm having – Patrick Dec 27 '15 at 18:59
  • I don't know what HandleClientComm() does, but I assume this sends a dummy message FROM Server TO Client, right? (The method name doesn't sound like it :) Also perhaps you should see what is the Exception message? It will help you debug the problem. Updated my answer to show how to see Exception message. – interceptwind Dec 28 '15 at 02:29
  • One more thing, only start the heartbeatTimer AFTER you are sure they are connected, if not sending message will cause exception. And one last thing, will your ClientIPLabel.Text updates correctly once they are connected / reconnected AFTER it shows "(No client connected)"? – interceptwind Dec 28 '15 at 02:35
  • I only used HandleClientComm() because my server doesn't use the SendMessage() method, and HandleClientComm() seemed like the only one that it uses that might work for me. Should I try adding the SendMessage() method to my server? I just didn't want to mess something else up by adding it – Patrick Dec 28 '15 at 02:36
  • I start my timer right before it checks for the client being disconnected, so I don't think that's the issue, and yes, my label updates when it's reconnected, and then it goes back to saying it's disconnected after 5 seconds again – Patrick Dec 28 '15 at 02:41
  • I'm not really sure, lol. As I said, I'm new to this... You can see the code for HandleClientComm() in my 4th edit in my question, if that helps. – Patrick Dec 28 '15 at 02:43
  • Haha i missed that. No it won't work this way. Server has to send message to Client. – interceptwind Dec 28 '15 at 02:46
  • Hmm, okay. I'm trying to add SendMessage() to the server, but for some reason in `NetworkStream clientStream = Server.GetStream();` it says there isn't a definition for `GetStream` – Patrick Dec 28 '15 at 02:49
  • I've tried setting it up the same way I have it set up in my client (you can see that in Edit 3) – Patrick Dec 28 '15 at 02:50
  • I've added a 6th edit to my question with my attempt at adjusting the `HandleClientComm()` method to send messages – Patrick Dec 28 '15 at 03:07
  • Dude, your 6th edit doesn't make sense anymore. Please revert to your 4th edit while I try to come up with another answer. – interceptwind Dec 28 '15 at 03:31
1

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

    }
interceptwind
  • 665
  • 4
  • 14
  • Thanks for putting in so much effort in helping me with this. I think I'm gonna take a break from it for a while though, and use my little workaround for the time being; for whatever reason this thing just keeps going over my head... I still have some other parts of my project to work on that aren't giving me so much trouble lol. – Patrick Dec 28 '15 at 04:35
  • 1
    I'll mark this as the correct answer because I know that it is. I just can't get it to work yet, but that's on my skill level, not your answer. – Patrick Dec 28 '15 at 04:36