VB.Net Правильно завершить работу асинхронного сервера сокетов, работающего в фоновом режиме. - PullRequest
1 голос
/ 21 марта 2012

У меня есть класс под названием 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 Ответ

3 голосов
/ 21 марта 2012

Мне не очень нравится пример кода MSDN, потому что он использует цикл while, который заставляет вас думать, что это необходимо;если бы это было необходимо, то вам действительно нужно было бы прослушивать другой поток.К счастью, вам не нужно использовать BackgroundWorker, и вы можете легко сделать все вне цикла while, не блокируя поток пользовательского интерфейса.

Сначала создайте метод Shared (чтобы продолжитьс другими общими обратными вызовами и методами) под названием Accept(Socket), который будет делать Socket.BeginAccept.Другой метод проще, потому что он будет выполняться в двух местах, и позже вы можете обнаружить необходимость перехвата исключений.Где-то в вашем основном Form, после привязки IPEndPoint и прослушивания, вы добавите свой первый вызов к Accept, предоставляя сокет, используемый для прослушивания.

Я также рекомендовал бы создать Receive(Socket) метод, где сокетом является вновь подключенный клиент, полученный в AcceptCallback.Это также улучшит организацию, потому что будет больше кода, чем просто BeginReceive, например, для перехвата исключений.

В вашем методе AcceptCallback после выполнения Receive вы снова вызовете Accept.Это будет постоянно цикл между Accept и AcceptCallback.Чтобы разорвать этот цикл и прекратить прослушивание, просто вызовите метод Socket.Close, установите для поля сокета значение Nothing и перехватите следующие исключения:

  • SocketException (что на самом деле не связано с Close)
  • ObjectDisposedException (так как Close - распоряжение)
  • NullReferenceException (случайное состояние гонки произойдет, когда пройдет проверка на ничтожность (Nothing), но сразу после этого Socket будетбыть уничтоженным и установленным в null)

В вашем методе StopServer не забудьте установить Socket в Nothing (после закрытия сокета), что будет сигнализировать о том, что сокет был удален.Вы также должны проверить, что слушатель не является Nothing, прежде чем запускать какие-либо методы на нем (которые будут вашими Accept и AcceptCallback методами), убедившись, что перехватывает NullReferenceExceptions для этого случайного состояния гонки.

Вот базовая модель того, как будет выглядеть ваш метод Accept.Эта модель похожа на остальную часть кода, однако.

Private Shared Sub AcceptCallback(ar As IAsyncResult)
    Dim listener As Socket = DirectCast(ar.AsyncState, Socket)

    If listener Is Nothing Then Return

    Try
        Dim newClient As Socket = listener.EndAccept(ar)

        Receive(newClient)
    Catch ex1 As SocketException
        Return
    Catch ex2 As ObjectDisposedException
        Return
    Catch ex3 As NullReferenceException
        Return
    End Try

    Accept(listener)
End Sub
...