0
votes

I wrote a simple async TcpClient. Here the relevant part of the code:

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

On another application I have a TcpListener that waits for connections. After a successful connection, the server sends some data to the client. The data is received correcly from ReadAsync. Then if I try to send more data from the server, the client waits forever in the second call for ReadAsync.

I'm pretty sure the server is working because I receive the SendCallback with the correct bytes sent.

Am I using ReadAsync wrongly?

UPDATE

I add here the complete code of my server:

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());
        }
    }
}
1
I have just tested the code and it works as expected. Are you sure that there is no problem in server ? - lucky
"I wrote a simple async TcpClient" -- why? Why not just get the NetworkStream from the TcpClient and use the async methods on that? And why did you write the code the way you did, such that the ConnectAsync() method won't return until the connection has been closed? That's a very confusing design. Not to mention that it would be better for your Receive() method to follow convention (i.e. have ...Async in the name), and to not mix the EAP model with the TPL model. But mostly, if you want help with your code, you need to provide a good minimal reproducible example that reproduces the problem. - Peter Duniho
@PeterDuniho I wrote it in that way because I'm still learning, and I based it upon the Microsoft example: msdn.microsoft.com/it-it/library/bew39x2a(v=vs.110).aspx. I don't know what are EAP and TPL models. I'm going to to a Google search for them. Thanks for your hints. - Mark
@Rainman updated the question with the server code. - Mark

1 Answers

1
votes

I reviewed your server code and I made some modifications. Firstly, there is a variable that named "queue". I didn't understand its purpose, because you aren't enqueuing it anywhere and you are trying to dequeue it within infinite while block in the BeginReceive method. That while statement blocks main thread and prevent to receive data from client, prevent accepting other clients and other operations that it should complete. If you enqueue it from somewhere and you want to send dequeued data to tcp client, you can do it after received completed and don't use infinite while loop for main thread. I provided modified methods like this;

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