Socket aus anderem Thread schliessen

StehtimSchilf

Erfahrenes Mitglied
Hi Forum

Ich habe einen .NET Windows Service der auf einem Socket horcht:

Code:
        Dim smtpPort as Integer = 2525

        Dim ipHostInfo As IPHostEntry = Dns.GetHostEntry(Dns.GetHostName())
        Dim localEndPoint As New IPEndPoint(ipHostInfo.AddressList(0), smtpPort)

        Dim listener As New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)

        listener.Bind(localEndPoint)
        listener.Listen(10)

        'blockierend
        Dim socket As Socket = listener.Accept()

So das funktioniert bis jetzt eigentlich sauber (try-catch etc. weggelassen). Wenn ich nun meinen Service über die MMC restarte, dann erhalte ich Fehlermeldungen betreffend:
Normalerweise darf jede Socketadresse (Protokoll, Netzwerkadresse oder Anschluss) nur jeweils einmal verwendet werden

Also dachte ich mir, schliesse ich den Socket im Service-OnStop()-Event so etwas à la:
Code:
            'listener.Disconnect(False)
            listener.Close()
Doch damit erhalte ich nun eine Exception bei listener.accept():
System.Net.Sockets.SocketException: Ein Blockierungsvorgang wurde durch einen Aufruf von WSACancelBlockingCall unterbrochen
bei System.Net.Sockets.Socket.Accept()

Wie schliesse ich denn nun den Socket richtig? Wenn ich den Service nur Stoppe, 2,3 Sekunden warte und dann wieder starte, dann funktioniert es ohne Fehlemeldungen. Einfach der Restart hat er nicht so gerne. Daher möchte ich eben die accept() unterbrechen. Wie geht das nach Kochbuch?

cheerioh & thx
SiS
 
Ich denke das passt schon so. Mit dem Disconnect-Aufruf zwingst du die Accept-Methode, dass sie zurückkehrt. Da sie aber keinen Rückgabewert definieren kann, wirft sie eine Fehlermeldung.
Eine SocketException kann aber auch andere Ursachen haben; um diese herauszufinden liefert dir die ErrorCode-Eigenschaft der SocketException genauere Informationen. Informationen zum ErrorCode findest du hier. Also wenn bei dir der Wert WSAEINTR=10004 im ErrorCode drin steht, dann kannst du die Fehlermeldung einfach ignorieren bzw. weißt, dass ein anderer Thread die Disconnect-Methode (oder Ähnliche) aufgerufen hat.
P.S.: Bitte nimm das nicht 100%-ig für wahre Münze, habe nur MSDN gelesen, aber keine Erfahrung damit.
 
Zuletzt bearbeitet:
Hi Shakie

Danke für Deine Links. Wie es scheint lässt sich das Problem genau so "umgehen".

Code:
Try
   listener.Bind(localEndPoint)
   listener.Listen(10)

   ' Start listening for connections.
   running = True
   While running
      ' zzzzzzzzzzz....
      Dim socket As Socket = listener.Accept()

      Dim session As New SMTPSession(socket)
		
      ' do session stuff
		
   End While 'ready for next message

Catch e As Exception
   If System.Runtime.InteropServices.Marshal.GetLastWin32Error() = 10004 Then
	   ' ignore error of blocking operation WSACancelBlockingCall
   Else
      ' do error handler stuff
   End If
End Try

cheerioh
SiS
 
Zuletzt bearbeitet:
Ich würde die Fehlerbehandlung so gestalten:
Code:
Try
    ' ...
Catch ex As Net.Sockets.SocketException
    If ex.ErrorCode <> 10004 Then
        ' Fehlerbedhanlung für SocketExceptions
    End If
Catch ex As Exception
    ' Fehlerbedhanlung für andere Exceptions
End Try
Das erscheint mir hübscher als der Aufruf von Marshal.GetLastWin32Error. Und ich vermute der Zugriff auf die ErrorCode-Eigenschaft sollte schneller sein als der Aufruf von Marshal.GetLastWin32Error.
 
GetLastWin32Error dürfte kaum mehr Aufwand sein, aber möglicherweise nicht funktionieren. Der CLR-Host selbst ruft ja ständig Windows-Funktionen auf (schon alleine zwischen accept und dem Abfangen der Exception wohl einige), welche wiederum fs:[0x34] (Last Error Code) verändern. Deshalb macht ja auch Marshal.GetLastWin32Error auch nicht das, was GetLastError im unverwalteten macht
Code:
_GetLastError@0:
mov         eax,dword ptr fs:[00000018h]  
mov         eax,dword ptr [eax+34h]  
ret

sondern es wird eine Variable abgefragt, welche für jeden Thread den letzten von aussen aufgerufenen Errorcode enthält. Das funktioniert jedoch nur, wenn die Funktion, von welcher du den Errorcode wissen möchtest auch das Attribut SetLastError(true) hat. Nur dann speichert der CLR-Host den Code der Funktion. Bedenkt man nun, dass ErrorCode ein Property ist, so ist beides nur ein simpler Funktionsaufruf, welcher einen Wert einer Variablen zurückgibt. Performance dürfte daher praktisch identisch sein (abhängig vom jeweiligen Overhead), aber obs funktioniert kann man so nicht sagen. Marshal.GetLastWin32Error ist eigentlich (nur) für Verwendung nach P-Invoke gedacht.
 
Zurück