【问题标题】:ReadAsync reads only once in async TcpClientReadAsync 在异步 TcpClient 中只读取一次
【发布时间】:2017-12-05 03:48:37
【问题描述】:

我写了一个简单的异步 TcpClient。这里是代码的相关部分:

public class AsyncTcpClient : IDisposable
{
    private TcpClient tcpClient;
    private Stream stream;

    private int bufferSize = 8192;
    bool IsReceiving = false;

    public event EventHandler<string> OnDataReceived;
    public event EventHandler OnDisconnected;
    public event EventHandler OnConnected;
    public event EventHandler<Exception> OnError;

    public bool IsConnected
    {
        get
        {
            return tcpClient != null && tcpClient.Connected;
        }
    }

    public AsyncTcpClient() { }

    public async Task ConnectAsync(string host, int port, CancellationToken token = default(CancellationToken))
    {
        try
        {
            if (IsConnected) Close();
            tcpClient = new TcpClient();
            if (!tcpClient.ConnectAsync(host, port).Wait(250))
            {
                throw new TimeoutException();
            }
            stream = tcpClient.GetStream();
            OnConnected?.Invoke(this, EventArgs.Empty);
            await Receive();
        }
        catch (Exception)
        {
            OnDisconnected?.Invoke(this, EventArgs.Empty);
        }
    }

    public async Task Receive(CancellationToken token = default(CancellationToken))
    {
        try
        {
            if (!IsConnected || IsReceiving) throw new InvalidOperationException();
            IsReceiving = true;
            byte[] buffer = new byte[bufferSize];
            while (IsConnected)
            {
                token.ThrowIfCancellationRequested();

                // First time it reads the incoming data, then it hangs here forever
                int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, token);
                if (bytesRead > 0)
                {
                    byte[] data = new byte[bytesRead];
                    Array.Copy(buffer, data, bytesRead);
                    OnDataReceived?.Invoke(this, Encoding.ASCII.GetString(data));
                }
                buffer = new byte[bufferSize];
            }
        }
        catch (ObjectDisposedException) { }
        catch (IOException)
        {
            throw;
        }
        finally
        {
            IsReceiving = false;
        }
    }
}

在另一个应用程序上,我有一个等待连接的 TcpListener。 连接成功后,服务器向客户端发送一些数据。从 ReadAsync 正确接收数据。然后,如果我尝试从服务器发送更多数据,客户端会在第二次调用 ReadAsync 时永远等待。

我很确定服务器正在工作,因为我收到了发送正确字节的 SendCallback。

我是否错误地使用了 ReadAsync?

更新

我在这里添加我的服务器的完整代码:

public class StateObject
{
    public Socket workSocket = null;
    public const int BufferSize = 4096;
    public byte[] buffer = new byte[BufferSize];
    public StringBuilder sb = new StringBuilder();
}

public class TcpServerAsync
{
    public readonly ConcurrentQueue<String> queue = new ConcurrentQueue<String>();
    public ManualResetEvent allDone = new ManualResetEvent(false);
    private Boolean _isRunning = true;

    public event EventHandler Connected;

    public TcpServerAsync(Int32 port)
    {
    }

    public void Start()
    {
        Thread t = new Thread(Run);
        t.Start();
    }

    public void Run()
    {
        IPHostEntry ipHostInfo = Dns.GetHostEntry("localhost");
        IPAddress ipAddress = ipHostInfo.AddressList[1];
        IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 5000);
        Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        try
        {
            listener.Bind(localEndPoint);
            listener.Listen(1);

            while (_isRunning)
            {
                allDone.Reset();
                listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
                allDone.WaitOne();
            }
        }
        catch (Exception e)
        {
            Debug.WriteLine(e.ToString());
        }
    }

    public void AcceptCallback(IAsyncResult ar)
    {
        allDone.Set();

        Connected.Invoke(this, new EventArgs());
        Socket listener = (Socket)ar.AsyncState;
        Socket handler = listener.EndAccept(ar);

        StateObject state = new StateObject
        {
            workSocket = handler
        };
        handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);

        while (handler.Connected)
        {
            if (queue.TryDequeue(out String data))
            {
                try
                {
                    SendData(handler, data);
                }
                catch (Exception ex)
                {
                    throw;
                }
            }
            Thread.Sleep(0); 
        }
    }

    public void ReadCallback(IAsyncResult ar)
    {
        String content = String.Empty;

        StateObject state = (StateObject)ar.AsyncState;
        Socket handler = state.workSocket;

        int bytesRead = handler.EndReceive(ar);

        if (bytesRead > 0)
        {
            state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
            content = state.sb.ToString();
            Debug.WriteLine("Read {0} bytes from socket. \n Data : {1}", content.Length, content);
        }
    }

    public void SendData(Socket handler, String data)
    {
        byte[] byteData = Encoding.ASCII.GetBytes(data);
        handler.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), handler);
    }

    private void SendCallback(IAsyncResult ar)
    {
        try
        {
            Socket handler = (Socket)ar.AsyncState;

            int bytesSent = handler.EndSend(ar);
            Debug.WriteLine("Sent {0} bytes to client.", bytesSent);
        }
        catch (Exception e)
        {
            Debug.WriteLine(e.ToString());
        }
    }
}

【问题讨论】:

  • 我刚刚测试了代码,它按预期工作。你确定服务器没有问题?
  • “我写了一个简单的异步 TcpClient” -- 为什么?为什么不直接从TcpClient 获取NetworkStream 并使用异步方法呢?为什么你要按照你的方式编写代码,使得ConnectAsync() 方法在连接关闭之前不会返回?这是一个非常令人困惑的设计。更不用说您的 Receive() 方法最好遵循约定(即名称中包含 ...Async),并且将 EAP 模型与 TPL 模型混合。但大多数情况下,如果您需要代码方面的帮助,您需要提供一个很好的 minimal reproducible example 来重现问题。
  • @PeterDuniho 我是这样写的,因为我还在学习,我基于微软的例子:msdn.microsoft.com/it-it/library/bew39x2a(v=vs.110).aspx。我不知道什么是 EAP 和 TPL 模型。我要去谷歌搜索他们。感谢您的提示。
  • @Rainman 用服务器代码更新了问题。

标签: c#


【解决方案1】:

我查看了您的服务器代码并进行了一些修改。首先,有一个名为“队列”的变量。我不明白它的目的,因为你没有在任何地方将它排入队列,而是试图在 BeginReceive 方法中的无限 while 块内将其出列。该while语句阻塞主线程并阻止从客户端接收数据,阻止接受其他客户端和它应该完成的其他操作。如果您从某个地方将其入队并且您想将出队的数据发送到 tcp 客户端,您可以在接收完成后执行此操作,并且不要对主线程使用无限 while 循环。我提供了这样的修改方法;

    public void AcceptCallback(IAsyncResult ar)
    {
        allDone.Set();

        if (Connected != null)
        {
            Connected.Invoke(this, new EventArgs());
        }
        Socket listener = (Socket)ar.AsyncState;
        Socket handler = listener.EndAccept(ar);

        BeginReceive(handler);

        //while (handler.Connected)
        //{
        //    if (queue.TryDequeue(out String data))
        //    {
        //        try
        //        {
        //            SendData(handler, data);
        //        }
        //        catch (Exception ex)
        //        {
        //            throw;
        //        }
        //    }
        //    Thread.Sleep(0);
        //}
    }

    public void BeginReceive(Socket handler)
    {
        StateObject state = new StateObject
        {
            workSocket = handler
        };
        handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
    }

    public void ReadCallback(IAsyncResult ar)
    {
        String content = String.Empty;

        StateObject state = (StateObject)ar.AsyncState;
        Socket handler = state.workSocket;

        int bytesRead = handler.EndReceive(ar);

        if (bytesRead > 0)
        {
            state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
            content = state.sb.ToString();
            if (queue.TryDequeue(out String data))
            {
                try
                {
                    SendData(handler, data);
                }
                catch (Exception ex)
                {
                    throw;
                }
            }
            //SendData(handler, content);
            Debug.WriteLine("Read {0} bytes from socket. \n Data : {1}", content.Length, content);
        }
        BeginReceive(handler);
    }

【讨论】:

  • 队列的目标是将要发送的数据排入队列。我在外部类(使用服务器的那个)中做到了这一点。感谢您花时间帮助我
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-02-22
  • 1970-01-01
  • 2014-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多