显示 Telnet 客户端收到的消息时的延迟

Delay when displaying a message received by a Telnet client

我正在尝试在 VB.NET 中实现 Telnet 客户端。我以 this 代码为例:

我正在实施的程序工作如下:

  1. 我单击“打开 Telnet”按钮以打开 Telnet 会话。
  2. 我在左边的文本框中写了一条指令(字符串),然后我点击Button1将消息发送到Telnet服务器(带有嵌入式以太网端口的电子板)。
  3. 左侧文本框中显示Telnet服务器发送的应答。

我在示例和我正在执行的实现中遇到的问题是消息显示延迟。例如,如果我发送字符串 1FFFFFF + vbCrLf,我应该会收到来自服务器的消息 Unknown HMI code!。我已经用 Wireshark 检查了消息是在我用 VB.NET 程序发送指令后由 Telnet 服务器发送的,但是只有当我再次单击 Button1 时它才会显示在右侧的文本框中时间(不管左边文本框里写的是什么)。

你能告诉我代码中是否遗漏了什么吗?

下面是我的代码:

Imports System
Imports System.IO
Imports System.Net.Sockets
Imports System.Security.Cryptography.X509Certificates
Imports System.Text
Imports System.Threading
Imports System.Net.Http
Imports System.Net.Security
Imports System.Net.IPAddress
Imports System.Net

Public Class Form1
    ' Create a TcpClient.
    Dim client As New TcpClient
    Dim stream As NetworkStream

    ' Function to write/read a TCP stream. 
    Shared Sub Connect(server As [String], message As [String])
        Try
            ' Translate the passed message into ASCII and store it as a Byte array.
            Dim data As [Byte]() = System.Text.Encoding.ASCII.GetBytes(message)

            ' Send the message to the connected TcpServer. 
            Form1.stream.Write(data, 0, data.Length)

            Console.WriteLine("Sent: {0}", message)

            ' Buffer to store the response bytes.
            data = New [Byte](256) {}

            ' String to store the response ASCII representation.
            Dim responseData As [String] = [String].Empty

            ' Read the first batch of the TcpServer response bytes.
            Dim bytes As Int32 = Form1.stream.Read(data, 0, data.Length)
            responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes)
            Console.WriteLine("Received: {0}", responseData)
            Form1.TelnetRx.Text += responseData + vbCrLf
            Form1.TelnetRx.Refresh()

        Catch e As ArgumentNullException
            Console.WriteLine("ArgumentNullException: {0}", e)
        Catch e As SocketException
            Console.WriteLine("SocketException: {0}", e)
        End Try

        Console.WriteLine(ControlChars.Cr + " Press Enter to continue...")
        Console.Read()
    End Sub

    ' Function to open a Telnet session.
    Public Function OpenTelnetSession(server As String, Port As Int32) As Boolean
        Dim ipAddress As IPAddress = Parse(server)
        client.Connect(ipAddress, Port)
        stream = Me.client.GetStream()
        Return True
    End Function

    ' Button to send a message.
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Connect("192.168.8.110", TCP_Order.Text + vbCrLf)
    End Sub

    ' Button to open the Telnet session.
    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles OpenTelnet.Click
        OpenTelnetSession("192.168.8.110", 10001)
    End Sub

    ' Button to close the Telnet session.
    Private Sub CloseTelnet_Click(sender As Object, e As EventArgs) Handles CloseTelnet.Click

    End Sub
End Class

因为您没有读取响应的整个缓冲区,您只是从中取出 256 个字节:

data = New [Byte](256) {} ' <-

您还必须通过关闭流和 TcpClient once you receive the response. Always create the disposable objects by the Using 语句来保证释放资源。

Synchronous Example


下面的示例以同步阻塞模式连接到端点,调用者线程被阻塞,直到从端点返回响应或抛出异常(例如连接超时)。

Private Function Connect(server As String, port As Integer, Msg As String) As String
    Using client As New TcpClient(server, port),
        netStream = client.GetStream,
        sr = New StreamReader(netStream, Encoding.UTF8)

        Dim msgBytes = Encoding.UTF8.GetBytes(Msg)

        netStream.Write(msgBytes, 0, msgBytes.Length)

        Return sr.ReadToEnd
    End Using
End Function

和来电者:

Private Sub TheCaller()
    Dim resp As String = Nothing

    Try
        Dim server = "192.168.8.110"
        Dim port = 10001
        Dim msg = $"1FFFFFF{ControlChars.CrLf}{ControlChars.CrLf}"
        
        resp = Connect(server, port, msg)
    Catch ex As ArgumentNullException
        resp = ex.Message
    Catch ex As SocketException
        resp = ex.SocketErrorCode.ToString
    Catch ex As Exception
        resp = ex.Message
    Finally
        If resp IsNot Nothing Then
            UpdateStatus(resp)
        End If
    End Try
End Sub

Asynchronous Example


您可能想使用异步操作,因为您正在开发 WinForms 应用程序,我认为您不想阻塞 UI 线程。这里需要调用TcpClient和read/write流的Async方法:

Private Async Function ConnectAsync(server As String,
                                    port As Integer, msg As String) As Task(Of String)
    Using client As New TcpClient
        Await client.ConnectAsync(server, port)

        Using netStream = client.GetStream,
            sw = New StreamWriter(netStream, Encoding.UTF8) With {.AutoFlush = True },
            sr = New StreamReader(netStream, Encoding.UTF8)

            Await sw.WriteLineAsync(msg)

            Return Await sr.ReadToEndAsync()
        End Using
    End Using
End Function

和一个 Async 来电者:

Private Async Sub TheCaller()
    Dim resp As String = Nothing

    Try
        Dim server = "192.168.8.110"
        Dim port = 10001
        Dim msg = $"1FFFFFF{ControlChars.CrLf}{ControlChars.CrLf}"
        
        resp = Await ConnectAsync(server, port, msg)
    Catch ex As ArgumentNullException
        resp = ex.Message
    Catch ex As SocketException
        resp = ex.SocketErrorCode.ToString
    Catch ex As Exception
        resp = ex.Message
    Finally
        If resp IsNot Nothing Then
            UpdateStatus(resp)
        End If
    End Try
End Sub

代码片段中的 UpdateStatus 只是一种将响应附加到文本框的方法。

Private Sub UpdateStatus(txt As String)
    StatusTextBox.AppendText(txt)
    StatusTextBox.AppendText(ControlChars.NewLine)
End Sub