1

I am trying to implement a Telnet client in VB.NET. I am following this code as example:

The program I'm implementing works as follows:

  1. I click the button "Open Telnet" to open the Telnet session.
  2. I write an instruction (string) in the text box on the left and then I click on Button1 to send the message to a Telnet server (an electronic board with an embedded Ethernet port).
  3. The answer sent by the Telnet server is displayed in the text box on the left.

Form for the Telnet client

The problem I'm having with both the example and the implementation I'm doing is that the messages are displayed delayed. For example, if I send the string 1FFFFFF + vbCrLf I am supposed to receive a message from the server saying Unknown HMI code!. I have checked with Wireshark that the message is sent by the Telnet server just after I sent the instruction with the VB.NET program but it is shown in the text box on the right only if I click Button1 a second time (no matter what is written in the text box on the left).

Could you please tell me if there is something I'm missing in the code?

Below is my code:

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
Marco
  • 391
  • 4
  • 18

1 Answers1

1

That because you are not reading the entire buffer of the response, you are just taking 256 bytes from it:

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

Also you have to free the resource by closing the streams and the TcpClient once you receive the response. Always create the disposable objects by the Using statement to guarantee that.

Synchronous Example


The example below connects to an endpoint in synchronous blocking mode, the caller thread is blocked until a response is returned from the endpoint or an exception is thrown (connection timeout for 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

and the caller:

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


You may want to use an asynchronous operation since you are developing a WinForms application, and I don't think you want to block the UI thread. Here you need to call the Async methods of the TcpClient and the read/write streams:

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

and an Async caller:

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

The UpdateStatus in the code snippets is just a method to append the responses into a TextBox..

Private Sub UpdateStatus(txt As String)
    StatusTextBox.AppendText(txt)
    StatusTextBox.AppendText(ControlChars.NewLine)
End Sub
  • Hello, I have tried with the examples you propose but I can't connect to the Telnet server with any of them. With the synchronous example the following message is added in the text box: `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.` and in the console I get `Exception thrown: 'System.IO.IOException' in System.dll`. – Marco Jun 22 '20 at 09:36
  • With the asynchronous what I get is a ConnectionRefused message and a Exception thrown: `'System.Net.Sockets.SocketException' in mscorlib.dll.` Nevertheless, in Wireshark I see the Ethernet packets with the answer from the Telnet server for both syns and async examples. Moreover, I still can connect with TeraTerm and with the former VB code. – Marco Jun 22 '20 at 10:05
  • @Marco Hello there! Make sure the `Msg` ends with **2** `ControlChars.CrLf` instead of one. Also, in some cases the encoding could be an issue, so try the `Encoding.ASCII` if the `Encoding.UTF8` doesn't work for you. Other than that, tested and works without any issues. Good luck. –  Jun 22 '20 at 14:44
  • 1
    thanks for your answer. For the synchronous example in the `Connect` function I made it work replacing the `Return sr.ReadToEnd` by `Return sr.ReadLine()`. I will try your suggestion for the asynchronous example. – Marco Jun 22 '20 at 15:00