tcpClient.State vb.net 中的模拟(相对于 vb6.0)

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

我在 VB6.0 中为 Winsock 对象 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) 

现在我正在使用 vb.net 并且想做同样的事情。 但是 TcpClient 对象现在没有 .state 方法! 只有 cpClient.Connected 但它 returns Boolean 所以只有是或不是。怎么才能像VB6.0一样呢?


使用 我做了这个:

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

问题是:开始时status2是"Unknown",如果我是第一次连接status2是"Established".如果我断开连接,它就会变成 "TimeWait"。但如果我再连接一次,它就会保持 "TimeWait"。它永远不会改变它的价值。

TcpClient 及其基础 Socket 似乎都没有这样的实现,因此我编写了三个扩展方法来为您实现。

他们所做的是使用 IPGlobalProperties class and its GetActiveTcpConnections() method 迭代计算机上每个活动的 TCP 连接,然后将您的连接与 TcpClient 的本地和远程 IP 地址进行匹配。

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

现在要获取连接状态,您有以下三种选择:

  1. 正在获取 TcpClient 的状态:

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

  1. 正在获取 TcpListener:

    的状态
    Dim Status As String = yourListener.GetStatus().ToString()
    

  1. 获取指定 IP 地址的连接状态:

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

重要提示:

  • 如果你想要状态的名称(例如Established),ToString()调用是必要的,否则你将得到它的枚举值,这只是一个普通的Integer(例如 3)。

  • 获取 TcpClientTcpListener 的状态只有在它们及其基础 Sockets 都没有被处置时才有效。如果您希望获得 disconnected TcpClient/-Listener 的状态,您必须使用 选项号。 3 并通过其确切的 IP 地址和端口获取它。

    • 断开的 TCP 连接实际上也很有趣,因为它们在关闭后仍然存在并且有一段时间的状态。断开连接状态的示例是 CloseWaitTimeWait.

阅读更多:


编辑:

要解决连接在 CLOSE_WAITTIME_WAIT 状态下滞留的问题,您可以将基础 Socket 设置为滞留 0 秒,然后处理它。这样做会导致套接字通过发送 RST(重置)数据包而不是 FIN 来执行所谓的 "hard close"(FIN 告诉套接字连接已关闭,它应该进入 CLOSE-/TIME_WAIT)。此方法强制关闭连接,此后不会有 late/retransmitted 数据包映射到您的应用程序。

实际上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.

这可以用一个简单的扩展方法来完成(把它放在 Extensions 模块中):

''' <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

然后就这样调用它:

yourClient.ForceClose()