【问题标题】:Delay when displaying a message received by a Telnet client显示 Telnet 客户端收到的消息时的延迟
【发布时间】:2020-10-09 22:57:51
【问题描述】:

我正在尝试在 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

【问题讨论】:

    标签: vb.net winforms telnet system.net.sockets


    【解决方案1】:

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

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

    您还必须在收到响应后通过关闭流和TcpClient 来释放资源。始终通过Using 语句创建一次性对象以保证这一点。

    同步示例


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

    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
    

    异步示例


    您可能希望使用异步操作,因为您正在开发 WinForms 应用程序,并且我认为您不想阻塞 UI 线程。这里需要调用TcpClientAsync方法和读/写流:

    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
    

    代码 sn-ps 中的UpdateStatus 只是一种将响应附加到文本框的方法..

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

    【讨论】:

    • 您好,我已经尝试过您提出的示例,但我无法使用其中任何示例连接到 Telnet 服务器。对于同步示例,在文本框中添加了以下消息:Unable to read data from the transport connection: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.,在控制台中我得到Exception thrown: 'System.IO.IOException' in System.dll
    • 通过异步,我得到的是 ConnectionRefused 消息和抛出的异常:'System.Net.Sockets.SocketException' in mscorlib.dll. 不过,在 Wireshark 中,我看到以太网数据包以及来自 Telnet 服务器的同步和异步示例的答案。此外,我仍然可以连接 TeraTerm 和以前的 VB 代码。
    • @Marco 你好!确保 Msg2 ControlChars.CrLf 而不是一个结尾。此外,在某些情况下,编码可能是一个问题,所以如果Encoding.UTF8 不适合您,请尝试Encoding.ASCII。除此之外,经过测试并且可以正常工作。祝你好运。
    • 感谢您的回答。对于Connect 函数中的同步示例,我将Return sr.ReadToEnd 替换为Return sr.ReadLine()。我会尝试你对异步示例的建议。
    猜你喜欢
    • 2021-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-21
    • 2018-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多