【问题标题】:c# detecting tcp disconnectc#检测tcp断开连接
【发布时间】:2013-02-10 14:22:59
【问题描述】:

我有两个简单的应用程序:

  • 在特定 tcp 端口上等待客户端连接的服务器应用程序。然后听他说什么,发回一些反馈并断开该客户。

  • 一个表单应用程序连接到服务器应用程序,然后说点什么,然后等待反馈并断开与服务器的连接,然后在表单中显示反馈。

虽然服务器应用程序似乎运行正常(我已经使用 Telnet 对其进行了测试,并且我看到了反馈,并且我看到在反馈之后直接发生断开连接),但是表单应用程序 似乎没有注意到断开连接从服务器。 (即使在服务器断开连接后,TcpClient.Connected 似乎仍然存在)

我的问题是:为什么 TcpClient.Connected 保持真实,我如何知道服务器是否/何时断开连接?

这是我的完整代码:

表格申请:

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

    }
}

服务器应用程序:

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

}

【问题讨论】:

标签: c# timeout tcpclient disconnect


【解决方案1】:

连接关闭时,TcpClient / NetworkStream 不会收到通知。 您唯一可用的选项是在写入流时捕获异常。

几年前,我们开始使用套接字而不是 tcp 客户端。与 tcpclient 相比,socket 更有用。

您可以使用几种方法

民意调查就是其中之一

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

您还可以检查 Write 本身的结果。它为您提供实际写入的字节数。

Connected 属性本身仅反映上次操作时的状态。其文档指出“Connected 属性的值反映了最近一次操作时的连接状态。如果需要确定连接的当前状态,请进行非阻塞、零字节的发送调用。如果调用返回成功或抛出WAEWOULDBLOCK错误码(10035),则socket仍处于连接状态;否则,socket不再连接。”

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

【讨论】:

  • 我还不太明白。为什么轮询套接字(我认为这意味着实际上尝试使用套接字并查看它是否失败,从而导致开销),而服务器已经告诉客户端它已断开连接。甚至telnet直接看到断开连接并警告我,所以这个消息必须从服务器发送,操作系统将这个消息传递给应用程序......你能帮我理解吗?
  • 绝对清楚:所以 c#/.NET 不记得连接方是否已正常断开连接?它会轮询连接以知道连接是否断开,即使连接方之前已经优雅地断开连接?
  • 已接受答案。虽然我仍然不完全同意。 RFC tools.ietf.org/html/rfc793#page-38(案例 2)声明用户(应用程序?)将被告知已收到 FIN(断开连接通知)。这个基本数据位必须存在于 .NET 中的某个地方...
  • @HermitDave 是的,我也读过。这几乎和你之前建议的一样。虽然根据我之前的评论:我真的不明白为什么 .NET 不会跟踪优雅的 tcp 断开状态。如果对方宣布将断开连接,那么至少应该有一个事件。
  • if (tcpClient.Client.Poll(1, SelectMode.SelectRead) && !networkStream.DataAvailable) { //检查连接是否关闭:使用SelectRead时, Poll() 只有在以下情况下才会返回 true: (1) 我们正在监听;我们不是。 (2) 有新数据可用;所以我们在调用 Poll() 之后检查 networkStream.DataAvailable,因为它可能在 Poll() 期间发生变化 (3) 连接确实关闭了。 }
【解决方案2】:

如果您使用无限超时进行阻塞读取,即

                NetworkStream.ReadTimeout = -1

那么在这种情况下,当连接丢失时,Read 方法将返回零

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-07-04
    • 1970-01-01
    • 1970-01-01
    • 2019-02-11
    • 2013-06-23
    • 2013-11-16
    • 2014-01-24
    • 2014-05-16
    相关资源
    最近更新 更多