处理异步套接字回调中的异常

时间:2015-05-07 19:09:19

标签: c# sockets exception-handling backgroundworker

我一直在努力让我的脑袋围绕套接字通信,我已经把一个小的Windows Forms应用程序作为测试。这基本上是一个客户端,它将连接到服务器,发送一些字节并断开连接。最终,我也会收到来自服务器的回复,但我现在大部分时间都将其删除了。

我可以说,这段代码工作正常,但由于在我的UI中点击按钮会发送数据,因此需要异步。我有一个名为SendDataToServer(byte[] bytesToSend)的方法,它连接到服务器并传输数据。在我的按钮单击事件处理程序中,我创建了一个将调用此方法的后台工作程序。

如果我的服务器没有启动并运行,我显然会收到套接字异常,当然还有其他原因导致在尝试连接和传输数据的过程中抛出异常。使用backgroundworker和异步套接字回调(ClientConnectCallback()ClientSendCallback()),什么是确保在我的UI中冒泡并正确显示任何异常的最佳方法?

现在我在异步回调本身的catch块中有MessageBoxes,但我想知道这是否真的是显示消息的地方,还是应该将它们传回?

以下是我的代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Windows.Forms;

namespace ClientTest
{
    public partial class MyForm : Form
    {
        private string ServerIpAddress = "x.x.x.x";
        private int ServerPort = 59176;

        public Socket ClientSocket;

        private static ManualResetEvent connectDone = new ManualResetEvent(false);
        private static ManualResetEvent sendDone = new ManualResetEvent(false);

        // state object for reading client data asynchronously
        public class StateObject
        {
            // client socket
            public Socket socket = null;
            // size of receive buffer
            public const int BufferSize = 1024;
            // receive buffer
            public byte[] buffer = new byte[BufferSize];
            // all bytes received get added to this
            public List<byte> bytes = new List<byte>();
        }

        public MyForm()
        {
            InitializeComponent();
        }

        private void ClientConnectCallback(IAsyncResult asyncResult)
        {
            try
            {
                // retrieve the socket from the state object
                Socket client = (Socket)asyncResult.AsyncState;

                // complete the connection
                client.EndConnect(asyncResult);

                // signal that the connection has been made
                connectDone.Set();
            }
            catch (SocketException sockEx)
            {
                // if the server isn't running, we'll get a socket exception here
                MessageBox.Show(sockEx.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            catch (ObjectDisposedException)
            {
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void ClientSendCallback(IAsyncResult asyncResult)
        {
            try
            {
                // retrieve the socket from the state object
                Socket client = (Socket)asyncResult.AsyncState;

                // complete sending the data to the server
                int bytesSent = client.EndSend(asyncResult);

                // signal that all bytes have been sent
                sendDone.Set();
            }
            catch (ObjectDisposedException objDispEx)
            {
                Debug.WriteLine(objDispEx.Message);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        public void SendDataToServer(byte[] bytesToSend)
        {
            try
            {
                connectDone.Reset();
                sendDone.Reset();

                ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                IPAddress ipAddress = IPAddress.Parse(ServerIpAddress);
                IPEndPoint remoteEndPoint = new IPEndPoint(ipAddress, ServerPort);

                ClientSocket.BeginConnect(remoteEndPoint, new AsyncCallback(ClientConnectCallback), ClientSocket);
                connectDone.WaitOne();

                ClientSocket.BeginSend(bytesToSend, 0, bytesToSend.Length, 0, new AsyncCallback(ClientSendCallback), ClientSocket);
                sendDone.WaitOne();
            }
            catch (ObjectDisposedException objDispEx)
            {
                Debug.WriteLine(objDispEx.Message);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                ClientSocket.Shutdown(SocketShutdown.Both);
                ClientSocket.Close();
            }
        }

        private void buttonSendDataToServer_Click(object sender, EventArgs e)
        {
            BackgroundWorker bwSendDataToServer = new BackgroundWorker();
            bwSendDataToServer.DoWork += bwSendDataToServer_DoWork;
            bwSendDataToServer.RunWorkerCompleted += bwSendDataToServer_RunWorkerCompleted;
            bwSendDataToServer.RunWorkerAsync();
        }

        private void bwSendDataToServer_DoWork(object sender, DoWorkEventArgs e)
        {
            byte[] bytesToSend = new byte[100];
            SendDataToServer(bytesToSend);
        }

        private void bwSendDataToServer_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            Debug.WriteLine("BackgroundWorker has completed.");
        }
    }
}

1 个答案:

答案 0 :(得分:2)

“如何举报异常?”是一个非常广泛的问题。很难确定特定的建议在您的情况下最好,因为有很多选项可以解决问题。

那就是说,你应该总是遵循一些恕我直言的一般规则,这些可以帮助指导你的设计:

  
      
  1. 不要使用UI阻止I / O线程。
  2.   

这意味着肯定会改进您的代码示例,因为您在等待用户确认错误消息时延迟用于调用回调的任何I / O线程。

  
      
  1. 遵守“关注点分离”的一般OOP原则。
  2.   

换句话说,即使呈现错误消息的线程不是I / O线程,在类中使用用户交互逻辑仍然不是一个好主意,其主要目的是处理网络I / O

当然有其他有用的编程规则,但我认为考虑到您当前的代码示例,上述两个是最相关的。那么,如何解决这些问题?


至少,我会更改您当前的实现,以便MyForm类中没有任何I / O代码。 MyForm类用于用户界面;它不应该涉及网络I / O的机制,而应该将该工作委托给其他类。

现在,其他课程仍然需要与MyForm进行通信。但它应该以独立于MyForm的方式这样做,即不与该类绑定(或“耦合”)。在报告异常方面,一种常见的方法是声明在发生异常时引发的事件。

这将解决第二点,但第一点仍然是一个问题。可以使用Control.BeginInvoke()方法在表单对象中执行事件处理代码来解决它,以便该代码在UI线程中执行,并且相对于I / O线程异步(即{{ 1}}方法不会在返回之前等待调用的代码完成。)

将所有这些放在一起,你可能会得到类似这样的东西:

BeginInvoke()


现在,所有这些说:正如我所提到的,有很多方法可以解决基本问题。上面的替代方法是使用class ExceptionReportEventArgs : EventArgs { public Exception Exception { get; private set; } public ExceptionEventArgs(Exception exception) { Exception = exception; } } class MyNetworkClient { public event EventHandler<ExceptionReportEventArgs> ExceptionReport; private void OnExceptionReport(Exception exception) { EventHandler<ExceptionReportEventArgs> handler = ExceptionReport; if (handler != null) { handler(this, new ExceptionReportEventArgs(exception)); } } // For example... private void ClientConnectCallback(IAsyncResult asyncResult) { try { /* code omitted */ } catch (SocketException sockEx) { // if the server isn't running, we'll get a socket exception here OnExceptionReport(sockEx); } catch (ObjectDisposedException) { } catch (Exception ex) { OnExceptionReport(ex); } } } partial class MyForm : Form { private MyNetworkClient _client; public MyForm() { InitializeComponent(); _client = new MyNetworkClient(); _client.ExceptionReport += (sender, e) => { BeginInvoke((MethodInvoker)(() => MessageBox.Show(e.Exception.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error))); }; } } / async模式,它提供了一种机制,以易于阅读,命令式的方式干净地编写异步逻辑,同时仍允许正确必须在UI线程中执行的代码和不在UI线程中执行的代码之间的转换。

我不会在这一点上详细介绍 - 有很多例子,并且需要对你发布的代码进行更大规模的返工 - 但基本的想法是使用例如await,它具有内置NetworkStream方法来处理I / O,并允许异常从I / O方法“冒泡”。

在这种方法中,您不需要async事件。相反,上面的ExceptionReport类将提供控制器类(例如您的MyNetworkClient对象)可以调用以启动I / O的async方法。如果发生异常,调用堆栈中的每个MyForm方法都可以处理(如果需要)异常,但随后重新抛出它(即使用普通的async语句),一直回到UI代码,异常将在UI线程中引发,并可以在那里呈现给用户,而不会阻塞I / O线程。


我认为一般来说,关键是要牢记通常的规则并坚持下去。最终,您会发现代码更易于编写和理解,并且在您选择的UI框架(例如Winforms)中表现更好。