当 TCPClient 断开连接时显示消息

Display message when TCPClient disconnects

我一直在尝试让我的服务器应用程序在客户端断开连接时在标签中显示一条消息。

此时标签在客户端启动时会显示已连接客户端的IP地址,但当客户端关闭时,IP地址仍会显示在标签中。

到目前为止,我已经尝试了这些方法,但都没有成功:

            // 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)";
            }                  

我确定我可能遗漏了一些简单的东西,我只是不知道它可能是什么。


编辑: 我发现了一些解决方法,当我单击关闭客户端应用程序的按钮时,客户端会在关闭之前向服务器发送一条消息。服务器知道,如果它收到此特定消息(在本例中为 "SHUTDOWN CLIENT"),则将 'ClientIPLabel.text' 中的文本更改为“(未连接客户端)”

这在大多数情况下确实有效,但它有点 hack,如果客户端由于错误或崩溃等原因关闭。服务器不会知道它已断开连接,因此它仍会显示发送的最后一个已知 IP。

第二次编辑: 所以看起来解决方法对我不起作用。将其添加到我的项目后,出于某种原因,无论我的客户端向服务器发送什么消息都会导致显示“(未连接客户端)”消息。

我的客户端实际上仍然连接并接收消息,但是 'ClientIPLabel' 没有正确标记



第 3 次编辑: 这是一个片段,显示了我如何设置我的客户端以按照我在下面的评论之一中详细说明的方式将消息发送到服务器:

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();
    }

第 4 次编辑: 我的服务器代码,以及直接从客户端关闭按钮获取客户端断开消息的临时解决方法:

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();
    }


第 5 次编辑: 我已经更新了我的代码,并且我(几乎)实现了心跳定时器,但它仍然没有设置正确......这是我的代码:

// 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)";
            });
        }
    }

无论我的客户端是否断开连接,“(未连接客户端)”消息在计时器后自动在我的服务器上弹出,因此它没有正确捕获异常。

我已经尝试实施 interceptwind 的建议,因为他写了它,但出于某种原因我应该 catch (Exception e) 我只能在我摆脱 e 和写成catch (Exception)。如果我将 e 留在那里,我会收到一条警告说 "The variable 'e' is declared but never used".

此外,我知道 interceptwind 编写他的示例是为了使用我的 SendMessage() 方法,但该方法只在我的客户端中使用,所以我更改了代码以尝试使用我的 HandleClientComm() 方法,所以我想知道这是否是无法正常工作的原因。我试过改变一些东西,但似乎仍然无法正常工作。 5 秒后我仍然收到消息“(没有客户端连接)”,即使我的客户端仍然连接并正常运行。


第 6 次编辑: 我试图调整我的 HandleClientComm() 方法以便能够发送消息,但我显然错过了一些东西,因为我的 "heartbeat timer" 仍在将我的 ClientIPLabel.text 切换为“(未连接客户端)”,即使我的客户端仍处于连接状态。

这是我的代码:

    // 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)";
            });
        }
    }

您可能需要使用:

if (TcpClient.Connected){}

Will 的评论是正确的。您可能需要实施心跳机制,您可以在其中定期向客户端发送心跳(或虚拟)消息。如果您在尝试发送 Heartbeat 时遇到错误,那么您就知道您的客户端已断开连接。

最简单的实现方式如下所示:

    //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)";
            });
        }
    }

鉴于OP限制Server不能向Client发送消息,另一种方式是Client每X秒(例如5秒)向Server发送一次心跳消息。

服务器随后将检查在过去 Y 秒(例如 30 秒)内是否收到来自客户端的任何消息。如果没有,则意味着客户端已断开连接。

客户代码

    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");
    }

服务器代码

    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

    }