Detect errors with NetworkStream.WriteAsync

拥有回忆 提交于 2019-12-23 19:03:14

问题


If I kill my server after the call to Login is complete, no exception is thrown when the call to stream.WriteAsync(data, 0, data.Count()); is made and there is no indication of error in the returned Task.

How then, am I supposed to detect errors? Surely, there should be some indication that I tried to send data over a connection that had been hung up on.

Here is my latest code attempt:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

using log4net;
using System.IO;
using System.Threading;

namespace IntegrationTests
{
    public class Client
    {
        private static readonly ILog log = LogManager.GetLogger("root");

        static private ulong m_lastId = 1;

        private ulong m_id;
        private string m_host;
        private uint m_port;
        private uint m_timeoutMilliseconds;
        private string m_clientId;
        private TcpClient m_tcpClient;
        private CancellationTokenSource m_cancelationSource;

        public Client(string host, uint port, string clientId, uint timeoutMilliseconds)
        {
            m_id = m_lastId++;
            m_host = host;
            m_port = port;
            m_clientId = clientId;
            m_timeoutMilliseconds = timeoutMilliseconds;
            m_tcpClient = null;
            m_cancelationSource = null;
        }

        ~Client()
        {
            Disconnect();
        }

        /// <summary>
        /// Attempts to connect to the hostname and port specified in the constructor
        /// </summary>
        /// <throws cref="System.ApplicationException" on failure
        public void Connect()
        {
            Disconnect();

            m_tcpClient = new TcpClient();
            m_cancelationSource = new CancellationTokenSource();

            try
            {
                m_tcpClient.Connect(m_host, (int)m_port);
            }
            catch (SocketException e)
            {
                string msg = string.Format("Client #{0} failed to connect to {1} on port {2}"
                                          , m_id, m_host, m_port);
                throw new System.ApplicationException(msg, e);
            }

            if (m_tcpClient.Connected)
            {
                log.Debug(string.Format("Client #{0} connnected to the Component on {1}"
                                      , m_id, m_tcpClient.Client.RemoteEndPoint.ToString()));
            }
        }

        public void Disconnect()
        {
            if (m_cancelationSource != null)
            {
                m_cancelationSource.Cancel();

                // TODO - There needs to be some kind of wait here until the async methods all return!
                //        How to do that?
                //        Are we even supposed to be manually canceling? One would think TcpClient.Close takes care of that,
                //        however when deleting all cancelation stuff, instead we get exceptions from the async methods about
                //        using TcpClient's members after it was disposed.

                m_cancelationSource.Dispose();
                m_cancelationSource = null;
            }

            if (m_tcpClient != null)
            {
                m_tcpClient.Close();
                m_tcpClient = null;
            }
        }

        public void Login()
        {
            string loginRequest = string.Format("loginstuff{0}", m_clientId);
            var data = Encoding.ASCII.GetBytes(loginRequest);

            NetworkStream stream = m_tcpClient.GetStream();
            Task writeTask = stream.WriteAsync(data, 0, data.Count());

            // This will block until the login is sent
            // We want block until the login is sent, so we can be sure we logged in before making requests
            if( !writeTask.Wait((int)m_timeoutMilliseconds) )
            {
                // Error - Send timed out
                log.Error(string.Format("Client #{0} Timed out while sending login request to the Component"
                                      , m_id));
            }
            else
            {
                log.Debug(string.Format("Client #{0} sent login request to the Component"
                                       , m_id));
            }
        }

        public async void Read()
        {
            byte[] buffer = new byte[1024];
            MemoryStream memoryStream = new MemoryStream();

            NetworkStream networkStream = m_tcpClient.GetStream();
            Task<int> readTask = null;

            bool disconnected = false;

            try
            {
                while (!disconnected)
                {
                    readTask = networkStream.ReadAsync(buffer, 0, buffer.Length, m_cancelationSource.Token);
                    int bytesReceived = await readTask;

                    if (readTask.Status == TaskStatus.RanToCompletion)
                    {
                        if( bytesReceived <= 0)
                        {
                            disconnected = true;
                            continue;
                        }

                        memoryStream.Write(buffer, 0, bytesReceived);

                        // TODO - Handle parsing of messages in the memory stream

                        memoryStream.Seek(0, SeekOrigin.Begin);
                    }
                    else if (readTask.Status == TaskStatus.Canceled)
                    {
                        // Error - Read was cancelled
                        log.Error(string.Format("Client #{0} Read operation was canceled."
                                              , m_id));
                        disconnected = true;
                        continue;
                    }
                    else
                    {
                        // Error - Unexpected status
                        log.Error(string.Format("Client #{0} Read operation has unexpected status after returning from await."
                                              , m_id));
                    }
                }
            }
            catch (System.Exception e)
            {
                log.Error(string.Format("Client #{0} Exception caught while reading from socket. Exception: {1}"
                                       , m_id, e.ToString()));
            }
        }

        public async void MakeRequest(string thingy)
        {
            string message = string.Format("requeststuff{0}", thingy);
            var data = Encoding.ASCII.GetBytes(message);

            NetworkStream networkStream = m_tcpClient.GetStream();
            Task writeTask = null;

            try
            {
                writeTask = networkStream.WriteAsync(data, 0, data.Count(), m_cancelationSource.Token);
                await writeTask;

                if (writeTask.Status == TaskStatus.RanToCompletion)
                {
                    log.Debug(string.Format("Client #{0} sent request for thingy {1} to the Component"
                                           , m_id, thingy));
                }
                else if (writeTask.Status == TaskStatus.Canceled)
                {
                    // Error - Write was cancelled
                    log.Error(string.Format("Client #{0} Write operation was canceled while requesting thingy {1} from the Component"
                                          , m_id, thingy));
                }
                else
                {
                    // Error - Unexpected status
                    log.Error(string.Format("Client #{0} Write operation has unexpected status after returning from await, while requesting thingy {1} from the Component"
                                          , m_id, thingy));
                }
            }
            catch (System.Exception e)
            {
                log.Error(string.Format("Client #{0} Exception caught while requesting thingy {1}. Exception: {2}" 
                                       , m_id, thingy, e.ToString()));
            }
        }
    }
}

main:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using log4net;
using log4net.Config;

namespace IntegrationTests
{
    class Program
    {
        private static readonly ILog log = LogManager.GetLogger("root");

        static void Main(string[] args)
        {
            try
            {
                XmlConfigurator.Configure();
                log.Info("Starting Component Integration Tests...");

                Client client = new Client("127.0.0.1", 24001, "MyClientId", 60000);
                client.Connect();

                client.Read();
                client.Login();
                client.MakeRequest("Stuff");

                System.Threading.Thread.Sleep(60000);

                client.Disconnect();
            }
            catch (Exception e)
            {
                log.Error(string.Format("Caught an exception in main. Exception: {0}"
                                      , e.ToString()));
            }
        }
    }
}

回答1:


This behavior is by design of the TCP stack. See this post for explanation (xamarin but the same principles apply here). If you have control over both client and server you could create some polling mechanism.



来源:https://stackoverflow.com/questions/47294295/detect-errors-with-networkstream-writeasync

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!