Search code examples
.netvb.nettcpvb6vb6-migration

tcpClient.State analogue in vb.net (versus vb6.0)


I used next code in VB6.0 for Winsock object tcpClient:

Dim SConst(10) As String
Dim st As Integer

SConst(0) = "Closed"
SConst(1) = "Open"
SConst(2) = "Listening"
SConst(3) = "ConnectionPending"
SConst(4) = "ResolvingHost"
SConst(5) = "HostResolved"
SConst(6) = "Connecting"
SConst(7) = "Connected"
SConst(8) = "Closing"
SConst(9) = "Error"
st = tcpClient.state
TextBox1.Text = SConst(st) 

Now Im using vb.net and want to do something same. But there is no .state method now for TcpClient objects! There is only cpClient.Connected but it returns Boolean so only yes or not. How can I do it alike VB6.0?


Using Visual Vinsent's answer I made this:

Public Class Form1
    Dim status1 As String
    Dim status2 As String

Private Sub Btn_Connect5001_Click(sender As Object, e As EventArgs)_
                   Handles Btn_Connect5001.Click
        ' Create TcpClient and Connect to
        tcpclnt2 = New TcpClient     
        Try
            tcpclnt2.Connect("192.168.1.177", 5001)
        Catch
        End Try
    End Sub

    Private Sub Btn_Disconnect5001_Click(sender As Object, e As EventArgs)_
                     Handles Btn_Disconnect5001.Click
        ' Close TcpClient
        tcpclnt2.Close()
    End Sub

Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        ' Check status every 300ms
        status2= New IPEndPoint(IPAddress.Parse("192.168.1.177"),5001).GetStatus().ToString()

        TextBox1.Text = Dns.GetHostName + Environment.NewLine +
             "port 1  " + status2 + Environment.NewLine
    End Sub
End Class

The problem is: At start status2 is "Unknown", if I connect for the first time status2 is "Established". If I disconnect it became "TimeWait". But If I connect one more time it stay "TimeWait". It never change its value then.


Solution

  • Neither the TcpClient nor its underlying Socket appears to have such an implementation, so I wrote three extension methods which will get that for you.

    What they do is that they iterate every active TCP connection on the computer using the IPGlobalProperties class and its GetActiveTcpConnections() method, then they match your connection against the TcpClient's local and remote IP-address.

    Extensions.vb

    Imports System.Runtime.CompilerServices
    Imports System.Reflection
    Imports System.Net
    Imports System.Net.Sockets
    Imports System.Net.NetworkInformation
    
    Public Module Extensions
        ''' <summary>
        ''' Dynamically gets an object's property by name.
        ''' </summary>
        ''' <param name="Obj">The object which's property to get.</param>
        ''' <param name="PropertyName">The name of the property to get.</param>
        <Extension()> _
        Public Function GetProperty(ByVal Obj As Object, ByVal PropertyName As String) As Object
            Return Obj.GetType().InvokeMember(PropertyName, _
                                               BindingFlags.GetProperty _
                                                Or BindingFlags.IgnoreCase _
                                                 Or BindingFlags.Public _
                                                  Or BindingFlags.NonPublic _
                                                   Or BindingFlags.Instance _
                                                    Or BindingFlags.Static, _
                                               Nothing, Obj, Nothing)
        End Function
    
        ''' <summary>
        ''' Gets the status of a TCP connection.
        ''' </summary>
        ''' <param name="Client">The TcpClient which's status to get.</param>
        ''' <remarks></remarks>
        <Extension()> _
        Public Function GetStatus(ByVal Client As TcpClient) As TcpState
            If Client Is Nothing OrElse Client.Client Is Nothing Then Return TcpState.Unknown
            For Each TcpConnection As TcpConnectionInformation In IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections()
    
                If TcpConnection.LocalEndPoint.Equals(Client.Client.LocalEndPoint) AndAlso _
                     TcpConnection.RemoteEndPoint.Equals(Client.Client.RemoteEndPoint) Then
                    Return TcpConnection.State
                End If
    
            Next
            Return TcpState.Unknown
        End Function
    
        ''' <summary>
        ''' Gets the status of a TCP connection.
        ''' </summary>
        ''' <param name="EndPoint">The IPEndPoint (IP-address) of the TCP connection which's status to get.</param>
        ''' <remarks></remarks>
        <Extension()> _
        Public Function GetStatus(ByVal EndPoint As IPEndPoint) As TcpState
            If EndPoint Is Nothing Then Return TcpState.Unknown
            For Each TcpConnection As TcpConnectionInformation In IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections()
    
                If TcpConnection.LocalEndPoint.Equals(EndPoint) OrElse _
                     TcpConnection.RemoteEndPoint.Equals(EndPoint) Then
                    Return TcpConnection.State
                End If
    
            Next
            Return TcpState.Unknown
        End Function
    
        ''' <summary>
        ''' Gets the status of a TCP listener.
        ''' </summary>
        ''' <param name="Listener">The TcpListener which's status to get.</param>
        ''' <remarks></remarks>
        <Extension()> _
        Public Function GetStatus(ByVal Listener As TcpListener) As TcpState
            If Listener Is Nothing OrElse Listener.Server Is Nothing Then Return TcpState.Unknown
    
            'Per the source code, active listeners will always be in the "Listen" state:
            'https://referencesource.microsoft.com/#System/net/System/Net/NetworkInformation/SystemIPGlobalProperties.cs,51fa569e558be704
            If Listener.GetProperty("Active") = True Then Return TcpState.Listen
    
            Return DirectCast(Listener.LocalEndpoint, IPEndPoint).GetStatus() 'The listener is not in an active state.
        End Function
    End Module
    

    Now to get the status of a connection you have three options:

    1. Getting the status of a TcpClient:

      Dim Status As String = yourClient.GetStatus().ToString()
      

    1. Getting the status of a TcpListener:

      Dim Status As String = yourListener.GetStatus().ToString()
      

    1. Getting the status of a connection with the specified IP-address:

      Dim Status As String = New IPEndPoint(IPAddress.Parse("your IP-address here"), yourPortHere).GetStatus().ToString()
      

    Important notes:

    • The ToString() call is necessary if you want the status's name (for example Established), otherwise you'll get its enumeration value which is just a normal Integer (for example 3).

    • Getting the status of a TcpClient or TcpListener will only work if neither they nor their underlying Sockets are disposed. If you wish to get the status of a disconnected TcpClient/-Listener you have to use option no. 3 and get it by its exact IP-address and port.

      • Disconnected TCP connections can actually be interesting as well because they still exist after being closed and have a state for a little while. Examples of disconnected states are CloseWait or TimeWait.

    Read more:


    EDIT:

    To fix the problem with connections lingering in a CLOSE_WAIT or TIME_WAIT state you can set the underlying Socket to linger for 0 seconds and then dispose it. Doing so will cause the socket to perform a so-called "hard close" by sending an RST (reset) packet instead of FIN (FIN is what tells a socket that the connection is closed and that it should go into CLOSE-/TIME_WAIT). This method forces the connection to close and no late/retransmitted packets will be mapped to your application after that.

    It's actually mentioned on the MSDN documentation:

    If the l_onoff member of the linger structure is nonzero and l_linger member is zero, closesocket is not blocked even if queued data has not yet been sent or acknowledged. This is called a hard or abortive close, because the socket's virtual circuit is reset immediately, and any unsent data is lost. On Windows, any recv call on the remote side of the circuit will fail with WSAECONNRESET.

    This can be done in a simple extension method (put this in the Extensions module):

    ''' <summary>
    ''' Forces the specified TcpClient to close without sending FIN to the other endpoint. This will stop the connection from going into a TIME_WAIT, CLOSE_WAIT or FIN_* state.
    ''' </summary>
    ''' <param name="Client">The TcpClient to close.</param>
    ''' <remarks></remarks>
    <Extension()> _
    Public Sub ForceClose(ByVal Client As TcpClient)
        Client.Client.LingerState = New LingerOption(True, 0) 'Set the socket to linger, but for 0 seconds (causes the current connection to send an RST-packet and terminate).
        Client.Client.Dispose() 'Dispose the underlying socket instead of a graceful shutdown.
        Client.Close() 'Close the TcpClient.
    End Sub
    

    Then just call it like this:

    yourClient.ForceClose()