TCPClient断开连接时显示消息

时间:2015-12-24 17:29:53

标签: c# tcpclient tcplistener disconnect

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

现在,标签会在客户端启动时显示已连接客户端的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)";
            }                  

我确定它可能是一件我想念的简单事物,但我无法弄清楚它可能是什么。

编辑:我发现了一些解决方法,当我点击关闭我的客户端应用的按钮时,客户端会在关闭之前向服务器发送消息。服务器知道如果它收到此特定消息(在这种情况下"关闭客户端")以更改' ClientIPLabel.text'中的文本。到#34;(没有连接客户端)"

这在大多数情况下都有效,但它是一种黑客攻击,如果客户端由于错误或崩溃而关闭等等。服务器不会知道它已经断开连接,所以它仍然会显示最后发送的已知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();
    }

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

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

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

// 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()方法以发送消息,但我显然错过了一些内容,因为我的"心跳计时器"仍然将我的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)";
            });
        }
    }

3 个答案:

答案 0 :(得分:1)

您可能需要使用:

if (TcpClient.Connected){}

答案 1 :(得分:1)

威尔的评论是否正确。您可能需要实现Heartbeat机制,您可以定期向客户端发送Heartbeat(或虚拟)消息。如果您在尝试发送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)";
            });
        }
    }

答案 2 :(得分:1)

鉴于OP限制服务器无法向客户端发送消息,另一种方法是客户端每隔X秒(例如5秒)向服务器发送心跳消息。

然后,

服务器将检查它是否在过去的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

    }