Как обращаться с TCPListener Существующее соединение было принудительно закрыто удаленным хостом

У меня есть tcp-сервер, который использует TCPListener и асинхронный метод BeginAcceptTCPClient:

 Imports System.Net.Sockets
Imports System.Threading
Imports System.Net

Public Class TCPServer
    Private mPort As Integer
    Public Event IncomingMessage(ByVal Message As String, ByVal IP As String)
    'This signals threadpool threads to stop...
    Private mStopServer As ManualResetEvent
    Private mListener As TcpListener
    Public Sub New(ByVal Port As Integer)
        mPort = Port
        Start()

    End Sub

    Public Sub Start()
        Try
            If mListener Is Nothing Then
                mListener = New TcpListener(IPAddress.Any, mPort)
            End If

            mListener.Start()
            AcceptClients()
            mStopServer = New ManualResetEvent(False)
        Catch ex As Exception
            ExceptionHandling.LogError(ex)
        End Try

    End Sub

    Private Sub AcceptClients()
        Try
            Dim result As IAsyncResult = mListener.BeginAcceptTcpClient(AddressOf HandleAsyncConnection, mListener)
        Catch ex As Exception
            ExceptionHandling.LogError(ex)
        End Try

    End Sub

    Dim so As New Object
    Public Sub StopListening()
        Try
            mStopServer.Set()
            mListener.Stop()
        Catch ex As Exception
            ExceptionHandling.LogError(ex)
        End Try

    End Sub


    Private Sub HandleAsyncConnection(ByVal result As IAsyncResult)
        Try


            If Not mStopServer.WaitOne(0) Then
                Dim listener As TcpListener = DirectCast(result.AsyncState, TcpListener)
                If listener Is Nothing Then Exit Sub
                Dim client As TcpClient = listener.EndAcceptTcpClient(result)
                Trace.WriteLine("Connected to new client")
                ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf ProcessClient), client)
                AcceptClients()

            End If
        Catch ex As Exception
            ExceptionHandling.LogError(ex)

        End Try
    End Sub



    Private Sub ProcessClient(ByVal client As Object)

        Dim newClient As TcpClient = DirectCast(client, TcpClient)


        Try
            ' Buffer for reading data
            Dim bytes As Byte() = New Byte(32 * 1024) {}
            Dim clientData As New System.Text.StringBuilder()


            Using ns As NetworkStream = newClient.GetStream()
                ' set initial read timeout to 1 minute to allow for connection
                ns.ReadTimeout = 60000
                ' Loop to receive all the data sent by the client.
                Dim bytesRead As Integer = 0
                Do
                    ' read the data
                    Try
                        If mStopServer.WaitOne(0) Then Exit Sub

                        bytesRead = ns.Read(bytes, 0, bytes.Length)
                        If bytesRead > 0 Then
                            clientData.Append(System.Text.Encoding.UTF8.GetString(bytes, 0, bytesRead))
                            ' decrease read timeout to 1 second now that data is coming in
                            ns.ReadTimeout = 1000
                        End If

                    Catch ioe As IO.IOException
                        Trace.WriteLine(ioe.ToString)
                        bytesRead = 0
                        Dim bError() As Byte = Error400()
                        ns.Write(bError, 0, bError.Length)

                    End Try

                Loop While ns.DataAvailable
                ForwardData(clientData.ToString, newClient.Client.RemoteEndPoint.ToString)
                'Trace.Write(clientData.ToString())
                'Acknowledge success
                bytes = Ack200()
                ns.Write(bytes, 0, bytes.Length)
            End Using

        Catch ex As Exception
            ExceptionHandling.LogError(ex)

        Finally
            ' stop talking to client
            If newClient IsNot Nothing Then
                newClient.Close()
            End If
        End Try
    End Sub

    Private Sub ForwardData(ByVal Data As String, ByVal IP As String)
        RaiseEvent IncomingMessage(Data, IP)
    End Sub
    Public Function Ack200() As Byte()
        Return System.Text.Encoding.UTF8.GetBytes("Okay")
    End Function

    Public Function Error400() As Byte()
        Return System.Text.Encoding.UTF8.GetBytes("Error")
    End Function

End Class

Моя проблема в том, что нечасто я получаю исключение в методе HandleAsyncConnection: «Существующее соединение было принудительно закрыто удаленным хостом» прямо в методе EndAcceptTCPClient. В этот момент кажется, что TCPListener перестает слушать. Проблема в том, что я не могу легко проверить это, так как это происходит только на удаленной тестовой виртуальной машине и только раз в 24 часа или около того. Если бы я знал, как воссоздать ошибку с помощью тестового клиента, я бы смог это понять. Wireshark показывает, что пакет сброса [RST] отправляется примерно во время исключения. Поэтому мне либо нужно знать, как обрабатывать исключение, либо как воссоздать проблему с тестовым клиентом.


person notbono    schedule 05.08.2011    source источник


Ответы (1)


Вы можете воссоздать проблему, перезагрузив сервер или отключив службу, к которой вы подключены. По сути, все, что убивает процесс, с которым вы общаетесь, должно генерировать эту ошибку.

Чтобы справиться с этим, вам просто нужно поймать исключение и попытаться повторно подключиться к серверу, возможно, придется подождать, пока он снова не будет доступен.

Изменить: ваш код перестает слушать, потому что вы вызываете AcceptClients после того, как возникает исключение, поэтому вы никогда не вернетесь к функции BeginAcceptTCPClient. Кроме того, и я не уверен, как это работает с асинхронными вызовами, но мне это кажется бесконечной рекурсией. BeginAcceptTCP клиент вызывает делегата к HandlAsynConnection, который вызывает AcceptClients, что запускает цикл. На мой взгляд, вы должны получить ошибку переполнения стека после стольких подключений. Вы можете добавить предложение finally в свой блок try в HandleAsyncConnection и вызвать его там, чтобы оно всегда вызывалось в случае ошибки.

    Try


        If Not mStopServer.WaitOne(0) Then
            Dim listener As TcpListener = DirectCast(result.AsyncState, TcpListener)
            If listener Is Nothing Then Exit Sub
            Dim client As TcpClient = listener.EndAcceptTcpClient(result)
            Trace.WriteLine("Connected to new client")
            ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf ProcessClient), client)

        End If
    Catch ex As Exception
        ExceptionHandling.LogError(ex)
    Finally            
        AcceptClients()
    End Try
person Kratz    schedule 05.08.2011
comment
Я пытался убить своего тестового клиента во время подключения, и в этом методе он обычно не дает мне этого исключения. Вместо этого он генерирует исключение в ProcessClient, и сервер продолжает принимать клиентов. Кроме того, как повторно подключить сервер или снова запустить прослушиватель? Как узнать, когда он доступен. Моя первая попытка перезапустить сервер привела к исключению, что порт уже используется. - person notbono; 05.08.2011
comment
Я думаю, теперь видно в коде, почему сервер перестает принимать после ошибки, я обновил свой ответ. - person Kratz; 05.08.2011
comment
Да, это работает. Он пережил несколько таких исключений. Спасибо большое! - person notbono; 08.08.2011