Can you spot the mistake? (Shell, TCPSocket, ServerSocket)

Server DataAvailable event:

Var loopx As Boolean = True

While loopx = True
  Print(Me.ReadAll(Encodings.UTF8).ToText)
  Print("")
  Var payload As String = Input
  If payload = "killme" Then
    loopx = False
    Exit
  End If
  Me.Write(payload + chr(0) + EndOfLine)
  Me.Flush
Wend

Quit

Client DataAvailable event:

Var sh As Shell = New Shell
sh.ExecuteMode = shell.ExecuteModes.Synchronous

Var received As String = Me.ReadAll.ToText

sh.Execute(received.ToText + EndOfLine)
Var result As String = sh.ReadAll.ToText
Me.Write(result + chr(0) + EndOfLine)
Me.Flush

The server app is running on my MacBook and client app is running on my Raspberry Pi in my office (behind NAT/Firewall). Everything is running fine so far, I can update my Raspberry or reboot it. That’s exactly what I was looking for and I did it on my own without having to pay for it, without compromising my security in my office by opening or forwarding ports or exposing my Raspberry Pi to the internet.

But there is a problem. When I enter for example “ls -la” to display all files and folders in the current directory, I have to press [Enter] twice in order to see the results of “ls -la”. What I can say so far, is that the result is immediately sent to the server after pressing the [Enter] key once, which means that there is something wrong with my server code (because it will only display the results after pressing the [Enter] key once more).

I would be very happy if someone had an idea. :slight_smile:

Wild guesses okay? I’m thinking that the (buffer?) doesn’t have the response (yet) prior to your .ReadAll, and then you issue a 2nd request and the prior requests results are there in the buffer. If you move the variable ‘result’ outside of this block where it can persist longer, then maybe that could help?

This may be way off base - it’s late, but I hope it’s some help.

It’s somewhat odd to have a null byte in the middle of a string. Perhaps it’s supposed to come after the end-of-line?

If the server is expecting a null-terminated string then it would stop reading after the first null byte. The first EOL would therefore appear at the beginning of the second message rather than the end of the first. Hence, you’d need to send a second message to make the first get noticed.

It makes sense but it doesn’t work. I created a property and stored the results in the property of TCPSocket class. Same result.

As far as I understood, I also tried your solution but it doesn’t seem to work. Same result as before.

I agree, get rid of the chr(0). They have a special meaning when inserted into a string and becomes a terminator.

The other thing to do is see what EndOfLine is returning on your raspberry pi. Remember EndOfLine can be different depending on the platform.

I suspect that what’s happening here is the first command sent is

"ls -al" + chr(0) + EndOfLine.Linux

And then

Chr(10) + chr(0) + EndOfLine.Linux

If both of these get truncated at the chr(0) then what you are saying makes complete sense.

Oh, and in this line:

sh.Execute(received.ToText + EndOfLine)

You don’t need the EndOfLine to execute the command.

Server:

Var loopx As Boolean = True

Print(Me.ReadAll(Encodings.UTF8).ToText)
Print("")

While loopx = True
  Var payload As String = Input
  If payload = "killme" Then
    loopx = False
    Exit
  End If
  Me.Write(payload)
  Me.Flush
  Print(Me.ReadAll(Encodings.UTF8).ToText)
  Print("")
Wend

Quit

Client:

Var sh As Shell = New Shell
sh.ExecuteMode = shell.ExecuteModes.Synchronous

Var received As String = Me.ReadAll.ToText

sh.Execute(received.ToText)
sh.Poll
Var result As String = sh.ReadAll.ToText
Me.Write(result)
Me.Flush

With this code, I still have the same problem.

Do me a favor and get rid of the two calls to ToText in the client. The data comes in as a string and you are needlessly converting to the Text type and back to String twice.

In terms of why it takes two commands to get a result, the Input method blocks the main thread. That means that after a command is sent and it starts waiting for the next command, no data can come in from the socket either.

So when you call me.Flush followed by Me.Readall on the server, those happen immediately probably while the socket is still sending. Then you immediately start waiting for more input and the app effectively hangs.

You might want to make it so the app doesn’t call Input until after you’ve received a response or a sufficient timeout has occurred.

Var loopx As Boolean = True

Print(Me.ReadAll(Encodings.UTF8).ToText)
Print("")

While loopx = True
  Var payload As String = Input
  If payload = "killme" Then
    loopx = False
    Exit
  End If
  Me.Write(payload)
  Me.Flush
  Var waitUntil As Integer = System.Ticks + 30
  While System.Ticks < waitUntil 
  Wend
  Print(Me.ReadAll(Encodings.UTF8).ToText)
  waitUntil = System.Ticks + 30
  While System.Ticks < waitUntil 
  Wend
Wend

Quit

Still not working.

please stop using (ever) .ToText it’s deprecated and the Text datatype is not equal to a string.

You can send the strings as commands using:

// Sender (socket): 
Var myCommand As String = "ls -la" + EndOfLine.Unix // Use unix to always use a known type
Socket.Write(myCommand)

// Reader (socket), DataAvailable event:
// You need a buffer as String property on the class
Var isDataAvailable As Boolean = (me.lookAhead(Encodings.UTF8) <> "")
If isDataAvailable Then
buffer = buffer + me.ReadAll(Encodings.UTF8)
Var lines(-1) As String = buffer.Split(EndOfLine.Unix)

If lines.count > 0 Then
Var lastIndex As Integer = lines.lastIndex
For i As integer = 0 To lastIndex
If i = lastIndex Then
// The last line did not had an ending EndOfLine, this is our new buffer
buffer = lines(i)
Exit For i
else
// This is a Line handle it, call a method or raise a custom event..
If line(i) = "ls -la" Then 
 // Your ls -la  command handle here
End If
end If
Next i 
End If

This is pseudocode but it should do the trick.

Var sh As Shell = New Shell
sh.ExecuteMode = shell.ExecuteModes.Synchronous

Var isDataAvailable As Boolean = (Me.Lookahead <> "")
If isDataAvailable Then
  buffer = buffer + Me.ReadAll(Encodings.UTF8)
  Var lines(-1) As String = buffer.Split(EndOfLine.UNIX)
  
  If lines.Count > 0 Then
    Var lastIndex As Integer = lines.LastIndex
    For i As Integer = 0 To lastIndex 
      If i = lastIndex Then
        buffer = lines(i)
        Exit For i
      Else 
        sh.Execute(lines(i))
        sh.Poll
        Var result As String = sh.ReadAll
        Me.Write(result)
        Me.Flush
      End If
    Next
  End If
End If

Still not working. Exactly the same result as before.

What is that part for ? i don’t see an endofline there
 just write
 ?

Me.Write(result)
        Me.Flush

I changed it to Me.Write(result + EndOfLine.UNIX), no difference at all.

Client:

Var loopx As Boolean = True

Print(Me.ReadAll(Encodings.UTF8))
Print("")

While loopx = True
  Var payload As String = Input
  If payload = "killme" Then
    loopx = False
    Exit
  End If
  Me.Write(payload + EndOfLine.UNIX)
  Me.Flush
  'Var waitUntil As Integer = System.Ticks + 30
  'While System.Ticks < waitUntil 
  'Wend
  Print(Me.ReadAll(Encodings.UTF8))
Wend

Quit

Still not working.

Well if you just keep looping nothing is gonna happen. is this a console application or desktop ?
Your loop is not ever calling anything to request the data from the system.

ISTM that this whole approach is wrong. If you try to do all this work inside the dataAvailable event handler, you have to send data, WAIT until it’s sent, WAIT until there’s data available, then read it (or reverse the sequence for the other end).

Inside an event handler, you have NO means of doing a WAIT. Your data available handler should do no more than resuming a thread where you do the real work. That thread will have started the I/O, then paused itself. It will then stay paused until an event handler resumes it. That could also be done by a timer’s event handler if you want to incorporate timeouts in the business, which in the real world you will want to.

1 Like

I removed the loop, it works now.

Print(Me.ReadAll(Encodings.UTF8))
Print("")

Var payload As String = Input

Me.Write(payload + EndOfLine.UNIX)
Me.Flush

Huge thanks to DerkJ!

1 Like

Good to hear :smiley: