VB.Net正确关闭在后台工作程序上运行的异步套接字服务器

时间:2012-03-21 05:39:46

标签: vb.net sockets asynchronous backgroundworker

我有一个名为SocketSvr的类,它处理异步套接字服务器。它是通过我的主窗体中的BackgroundWorker调用的。基本上,我只是希望它在主窗体的文本框中显示我的套接字数据信息,并在主窗体上有一个服务器启动/停止按钮来执行这些操作。

以下是Form1.vb的代码:

Public Class Form1

    Dim WithEvents Socketsvr As New SocketSvr

    Private Sub ToggleServerButton_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles ToggleServerButton.Click

        If ToggleServerButton.Text = "Stop Server" Then
            ToggleServerButton.Text = "Start Server"
            Socketsvr.StopServer()
        Else
            ToggleServerButton.Text = "Stop Server"
            Socketsvr.StartServer()
        End If
    End Sub

    Private Sub UpdateOutput_Event(ByVal sender As Object, _
        ByVal text As String) Handles Socketsvr.UpdateOutput
        Me.ServerOutputTextbox.AppendText(text + vbCrLf)
    End Sub

End Class

以上非常简单,基本上调用了StartServer()或StopServer()函数。下面的事件是一个Raised事件,我用它来通过后台进程中调用的事件来更新文本框。

以下是Socketsvr.vb的一些代码 - 我删除了不相关的代码,以尽量减少对帖子的抨击。

Public Sub StopServer()
    bw.CancelAsync()
    allDone.Set()
End Sub 'StopServer

Public Sub StartServer()
    bw.WorkerSupportsCancellation = True
    bw.RunWorkerAsync()
End Sub 'StartServer

Private Sub bw_DoWork(ByVal sender As System.Object, _
    ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bw.DoWork
    ' Data buffer for incoming data.
    Dim bytes() As Byte = New [Byte](1023) {}

    ' Establish the local endpoint for the socket.
    Dim ipHostInfo As New IPHostEntry
    ipHostInfo.AddressList = New IPAddress() _
        {New IPAddress(New [Byte]() {127, 0, 0, 1})}
    Dim ipAddress As IPAddress = ipHostInfo.AddressList(0)
    Dim localEndPoint As New IPEndPoint(ipAddress, 8888)

    ' Create a TCP/IP socket.
    Dim listener As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)

    ' Bind the socket to the local endpoint and listen for incoming connections.
    listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1)
    listener.Bind(localEndPoint)
    listener.Listen(100)
    While True
        ' If cancellation is pending, shut down server
        If bw.CancellationPending Then
            Out("Server stopped at " + DateTime.Now.ToString())
            Exit While
        End If
        Out("Server started at " + DateTime.Now.ToString())
        ' Set the event to nonsignaled state.
        allDone.Reset()

        Try
            ' Start an asynchronous socket to listen for connections.
            listener.BeginAccept(New AsyncCallback(AddressOf AcceptCallback), listener)
        Catch ex As Exception
            MessageBox.Show(ex.ToString, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Try

        ' Wait until a connection is made and processed before continuing.
        allDone.WaitOne()
    End While
End Sub

我不确定在运行StopServer()函数时如何正确关闭来自客户端的请求。在上面的代码中,我为后台进程放置了一个取消队列。有趣的是,一旦运行StopServer(),它将再接受一个连接,然后停止接受。

如果我从上面的代码中删除以下行:

listener.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1)

- 当我第二次尝试启动服务器时它崩溃了(抱怨我无法重用套接字,显然)我的猜测是我需要在后台工作者的bw.CancellationPending调用中添加一些内容吗?

任何见解都将不胜感激,如果我需要更多信息,请告诉我。

1 个答案:

答案 0 :(得分:3)

我真的不喜欢MSDN的示例代码,因为它使用了一个让你觉得有必要的while循环;如果有必要,那么你实际上需要听另一个线程。幸运的是,您不需要使用BackgroundWorker,并且您可以轻松地执行while循环之外的所有操作,而无需锁定UI线程。

首先,创建一个名为Shared的{​​{1}}方法(与其他共享回调和方法一起使用),它将执行Accept(Socket)。另一种方法更容易,因为它将在两个地方运行,您可能稍后发现需要捕获异常。主Socket.BeginAccept中的某个位置,在绑定Form并收听后,您将首次调用IPEndPoint,并提供用于收听的套接字。

我还建议创建一个Accept方法,其中套接字是Receive(Socket)中获得的新连接客户端。这也将改善组织,因为代码不仅仅是AcceptCallback,例如捕获异常。

BeginReceive方法中,在您执行AcceptCallback后,您将再次致电Receive。这将在AcceptAccept之间不断循环。为了打破这个循环以停止监听,只需调用AcceptCallback方法,将套接字字段设置为Nothing,并捕获这些异常:

  • Socket.Close(与关闭无关)
  • SocketException(因为关闭是一种处置)
  • ObjectDisposedException(偶尔的竞争条件会在无效(Nothing)检查通过的情况下发生,但在此之后,Socket将被处理并设置为null)

在你的NullReferenceException方法中,记得将Socket设置为Nothing(关闭套接字后),这将表示套接字已被丢弃。在运行任何方法之前,您还应检查监听器是否为Nothing(这将是您的StopServerAccept方法),确保在偶尔的比赛中捕获AcceptCallback条件。

这是关于NullReferenceExceptions方法的基本模型。不过,这是与其余代码类似的模型。

Accept