4

I am trying to setup a very basic single client/server system, so I can send from my PC a string to an android tablet. The tablet does something with the string and then sends back a string to the PC. The code:

server side (tablet)

procedure TForm1.Button1Click(Sender: TObject); // start the server
begin
  IdTCPServer1.Bindings.Clear;
  IdTCPServer1.Bindings.Add.SetBinding('xxx.xxx.x.x', 4405); // for the xxx's the IP adres of the tablet 
  IdTCPServer1.Active := True;
end;

procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var s:string;
begin
  s := AContext.Connection.IOHandler.ReadLn(); 
  Try
    ... do something with the string
    AContext.Connection.IOHandler.Writeln('ready');
  Except
    AContext.Connection.IOHandler.Writeln('Error');
  End;
  AContext.Connection.Disconnect;
end;

Client side: (desktop)

procedure TFMain.FormCreate(Sender: TObject);
begin
  IdTCPClient1.host := 'xxx.xxx.x.x';  // xxx's the IP address of the tablet
  IdTCPClient1.Port := 4405;
end;

procedure TForm1.Button1Click(Sender: TObject);
var s:string;
begin
  s := 'some string';
  IdTCPClient1.Connect;
  IdTCPClient1.IOHandler.Writeln(s);
  if IdTCPClient1.IOHandler.ReadLn='ready' then Button1.text := 'ready'
                                           else Button1.text := 'failed';

  IdTCPClient1.Disconnect;
end;

The programs run fine when run on localhost or on two different PCs in the home network. However, when installing the server side on the tablet, clicking button1 on the desktop will result in a 'connection closed gracefully'. It is really frustrating, since it does work normal on two PCs, but not on PC to android tablet. I searched a long time on the internet and maybe it has to do with a difference in string endings or there might also be a problem with threads in (XE5's implementation in) Android. Any help on how to get the above code running or a different way (through IP) to communicate between a PC and a tablet using XE5 is highly appreciated.

Gerard

Added later: With wireshark I tried to check what information is being send. When running between two desktops, which behaves normally, I get communication of which I assume the following applies to the programs at hand (server=192.168.2.11, client=192.168.2.10).

197 9.209090000 192.168.2.10    192.168.2.11    TCP 66  56699 > ds-admin [SYN] Seq=0 Win=8192 Len=0 MSS=1460 WS=256 SACK_PERM=1
198 9.212694000 192.168.2.11    192.168.2.10    TCP 66  ds-admin > 56699 [SYN, ACK] Seq=0 Ack=1 Win=8192 Len=0 MSS=1460 WS=256 SACK_PERM=1
199 9.212764000 192.168.2.10    192.168.2.11    TCP 54  56699 > ds-admin [ACK] Seq=1 Ack=1 Win=65536 Len=0
200 9.213041000 192.168.2.10    192.168.2.11    TCP 111 56699 > ds-admin [PSH, ACK] Seq=1 Ack=1 Win=65536 Len=57
201 9.217302000 192.168.2.11    192.168.2.10    TCP 119 ds-admin > 56699 [PSH, ACK] Seq=1 Ack=58 Win=65536 Len=65
202 9.217304000 192.168.2.11    192.168.2.10    TCP 60  ds-admin > 56699 [FIN, ACK] Seq=66 Ack=58 Win=65536 Len=0
203 9.217349000 192.168.2.10    192.168.2.11    TCP 54  56699 > ds-admin [ACK] Seq=58 Ack=67 Win=65536 Len=0
204 9.217543000 192.168.2.10    192.168.2.11    TCP 54  56699 > ds-admin [FIN, ACK] Seq=58 Ack=67 Win=65536 Len=0
207 9.225011000 192.168.2.11    192.168.2.10    TCP 60  ds-admin > 56699 [ACK] Seq=67 Ack=59 Win=65536 Len=0

On the other hand when doing the same thing with the android tablet as a server, I get the following (server=192.168.2.8, client=192.168.2.10):

116 4.792467000 192.168.2.10    192.168.2.8 TCP 66  56407 > ds-admin [SYN] Seq=0 Win=8192 Len=0 MSS=1460 WS=256 SACK_PERM=1
117 4.802487000 192.168.2.8 192.168.2.10    TCP 66  ds-admin > 56407 [SYN, ACK] Seq=0 Ack=1 Win=14600 Len=0 MSS=1460 SACK_PERM=1 WS=64
118 4.802571000 192.168.2.10    192.168.2.8 TCP 54  56407 > ds-admin [ACK] Seq=1 Ack=1 Win=65536 Len=0
119 4.802857000 192.168.2.10    192.168.2.8 TCP 111 56407 > ds-admin [PSH, ACK] Seq=1 Ack=1 Win=65536 Len=57
120 4.808816000 192.168.2.8 192.168.2.10    TCP 60  ds-admin > 56407 [FIN, ACK] Seq=1 Ack=1 Win=14656 Len=0
121 4.808860000 192.168.2.10    192.168.2.8 TCP 54  56407 > ds-admin [ACK] Seq=58 Ack=2 Win=65536 Len=0
122 4.808944000 192.168.2.10    192.168.2.8 TCP 54  56407 > ds-admin [FIN, ACK] Seq=58 Ack=2 Win=65536 Len=0
123 4.812517000 192.168.2.8 192.168.2.10    TCP 60  ds-admin > 56407 [ACK] Seq=2 Ack=58 Win=14656 Len=0
124 4.813608000 192.168.2.8 192.168.2.10    TCP 60  ds-admin > 56407 [RST, ACK] Seq=2 Ack=58 Win=14656 Len=0
125 4.813609000 192.168.2.8 192.168.2.10    TCP 60  ds-admin > 56407 [RST] Seq=2 Win=0 Len=0
126 4.814570000 192.168.2.8 192.168.2.10    TCP 60  ds-admin > 56407 [RST] Seq=2 Win=0 Len=0

This is the first time I see this kind of information, but it seems that in the working desktop-desktop situation line 200 corresponds to the client command IdTCPClient1.IOHandler.Writeln(s) and line 201 corresponds to the server command AContext.Connection.IOHandler.Writeln(...). In the desktop-tablet configuration the client sends the string to the server (line 119), but there is no [PSH, ACK] back. Instead there is a [FIN, ACK] response. This seems to point to something going wrong in the server-side statement in Android: s := AContext.Connection.IOHandler.ReadLn(); Any ideas what this could be and how to fix it?

Added later again: When putting a try/except statement around the line s := AContext.Connection.IOHandler.ReadLn(); then the exception 'Connection closed gracefully' is raised. When debugging delphi says: Project test.apk raised exception class segmentation fault (11) and goes to the following function in the unit IdIOHandler rev 1.123 (2/8/05).

function TIdIOHandler.ReadLn(AByteEncoding: IIdTextEncoding = nil
  {$IFDEF STRING_IS_ANSI}; ADestEncoding: IIdTextEncoding = nil{$ENDIF}
  ): string;
{$IFDEF USE_CLASSINLINE}inline;{$ENDIF}
begin
  Result := ReadLn(LF, IdTimeoutDefault, -1, AByteEncoding
    {$IFDEF STRING_IS_ANSI}, ADestEncoding{$ENDIF}
    );
end;

The Call Stack at that moment is (first item has an arrow in front, rest a blue bullet, except the last 3 items, which have a gray bullet in front):

Idiohandler.TIdIOHandler.ReadLn(0x797e8cb4,nil,nil)
Umaintablet.TFCalibration_tablet_side.IdTCPServer1Execute(0x74124e70,0x769d76a0)
Idcustomtcpserver.TIdCustomTCPServer.DoExecute(0x76414480,0x769d76a0)
Idcontext.TIdContext.Run(0x769d76a0)
Idtask.TIdTask.DoRun(0x769d76a0)
Idthread.TIdThreadWithTask.Run(0x766bd258)
Idthread.TIdThread.Execute(0x766bd258)
System.Classes.ThreadProc(0x766bd258)
System.ThreadWrapper(0x766bd300)
:4003F3DC __thread_entry
:4003EAC8 pthread_create
:00000000 ??
Gerard
  • 135
  • 1
  • 10
  • `ReadLn()` and `WriteLn()` use the same line endings on all platforms, so that is not the problem. "Connection closed gracefully" means the connection was intentionally closed. If you are getting the error on the client, the server closed the connection on its end while the client was still sending/receiving data on its end. Offhand, I don't see a problem with the code. I would suggest using a packet sniffer, such as Wireshark, to see what is actually happening on the network. My guess would be that the tablet closed the connection prematurely before all of the data was sent to the client. – Remy Lebeau Dec 10 '13 at 22:42
  • Thank you for your response. I added the relevant (I think) Wireshark results to the question. You seem to be right in that the tablet closed the connection prematurely. There might be something going wrong with the AContext.Connection.IOHandler.ReadLn statement on the android server side. Any ideas? – Gerard Dec 11 '13 at 12:24
  • Yes, the log involving the tablet server does suggest an error is occurring before `ReadLn()` completes (or maybe even before `ReadLn()` is called). There is likely an uncaught exception being raised, which `TIdTCPServer` handles internally by closing the connection. Did you verify whether `OnExecute` is being triggered at all? How about `OnConnect`? Try putting `ReadLn()` in a `try/except` to see if it is the actual culprit. Also have a look at the `OnException` event, too. – Remy Lebeau Dec 11 '13 at 16:54
  • Putting try/except around the server Readln() gives the the exception 'Connection closed gracefully' in the On exception event. However, if I put a breakpoint at the Readln(), a message with the 'Connection closed gracefully' appears already on the client side before the Readln() is executed. Putting a try/except around IdTCPClient1.Connect does not seem to generate an exeption event. However, if I test the boolean IDTCPClient1.connected right after the command IdTCPClient1.Connect, the result is false in case of a connetion to the tablet, but true in case of a connection to the other desktop – Gerard Dec 11 '13 at 19:49
  • Try binding to a [higher port](http://en.wikipedia.org/wiki/Ephemeral_port) such as 49152. – Marcus Adams Dec 11 '13 at 20:09
  • No, the higher port gives the same result. I have to make a correction on my previous message: The boolean IDTCPClient1.connected in fact seems to be true after connecting. It just becomes not connected, because I used breaking point, but apparently in the mean time the connection is broken. I noticed this by not using a breaking point, but changing some text on the form depending on if it was connected or not. So I am still in the dark. Anyone? – Gerard Dec 11 '13 at 20:57
  • Your server is encountering a socket disconnect before it has a chance to call `ReadLn()`. Since `ReadLn()` is the first thing your `OnExecute` handler does, that means `OnExecute` is not being called at all. The only way I can see that happen is if an `OnConnect` event handler is performing I/O that fails. Are you doing anything with the socket in the `OnConnect` event? `TIdTCPServer` does not perform any I/O on a newly connected client prior to triggering the `OnConnect` event. If you don't have an `OnConnect` handler assigned, then you should be seeing `ReadLn()` raise the disconnect error. – Remy Lebeau Dec 11 '13 at 21:13
  • When the disconnect exception is raised in the server, what does the call stack look like? If the debugger is not telling you when the exception is raised, then put a breakpoint in the `OnException` event. Either way, I would need to see the call stack that lead to the exception being raised in order to tell you what is actually failing. – Remy Lebeau Dec 11 '13 at 21:15
  • What you describe about `Connected()` on the client side is expected behavior in this situation. `Connected()` performs a read operation to determine the socket state. So `Connect()` is successfully connecting to the server, then the server is encountering the error before it can read the client's input so it disconnects right away, and since `Connected()` is the first read on the client side it is detecting the server's disconnect, which is why it returns false. If you remove the `Connected()`, `WriteLn()` or `ReadLn()` will raise a disconnect exception instead. – Remy Lebeau Dec 11 '13 at 21:17
  • @Remy: There is no OnConnect event, so the suspect is still the ReadLn function I think. In the question I added some more information regarding where the program jumps to when debugging (to unit IdIOHandler) and what the call stack was. Does that help you (and me)? – Gerard Dec 11 '13 at 23:18
  • @user2297042: You ruled out `ReadLn()` as the culprit when you said the client reports the error before `ReadLn()` is executed, and the exception appears in the server's `OnException` event (the `try/except` would prevent that). Now you are contradictig yourself saying `ReadLn()` is the culprit afterall. You can't have it both ways. Either the exception is raised before `ReadLn()` is called, or it is raised inside of `ReadLn()`. – Remy Lebeau Dec 11 '13 at 23:52
  • @user2297042: Your call stack shows a 3-parameter `ReadLn()` being called, but `TIdIOHandler` does not have a `ReadLn()` overload that takes 3 parameters (or 2 parameters if `0x797e8cb4` is actually the `Self` parameter). Your code is calling the 1-parameter `ReadLn()` (`STRING_IS_ANSI` is not defined on mobile), which you say is the code the debugger is jumping to. That makes sense only if the segfault (equivalent of an AccessViolation) is occurring while the compiler is preparing to call the 4-parameter `ReadLn()` inside of the 1-parameter `ReadLn()`. That sounds like a compiler bug to me. – Remy Lebeau Dec 11 '13 at 23:57
  • @user2297042: do you have the same failure if you call the 4-parameter `ReadLn()` overload directly? `s := AContext.Connection.IOHandler.ReadLn(LF);` or even `s := AContext.Connection.IOHandler.ReadLn(LF, IdTimeoutDefault, -1, nil);` If so, then at least the debugger should be able to jump further inside of `ReadLn()`. If not, then the problem has to be related to how the compiler is calling one overload from another overload, or in how it is setting up the parameter values for it. – Remy Lebeau Dec 12 '13 at 00:07
  • @user2297042: BTW, did you validate with the debugger that the `AContext`, `Connection`, and `IOHandler` pointers are all pointing at valid objects at the time the failure occurs? – Remy Lebeau Dec 12 '13 at 00:08
  • @Remy: I am sorry about the ambiguity of where the exception is occuring. Because of your remark that if there is no On Connect event, the ReadLn should raise the exception, I thought it must be the ReadLn then. However, it stays that if I put a breaking point on the ReadLn line, so before the ReadLn is executed, the client already shows the message 'connection closed gracefully'. If I do not debug, the try/except around the ReadLn does also give the 'connection closed gracefully' exception. With debugging there is the segmentation fault with the break at the ReadLn function of IdIOHandler. – Gerard Dec 12 '13 at 12:03
  • @Remy: I tried the ReadLn(LF) and ReadLn(LF, IdTimeoutDefault, -1, nil), but this does not change anything without debugging and with debugging, it seems that the opposite is occuring in that when I click Break at the segmentation fault, it does not jump to the IdIOHandler file anymore. Maybe there are two issues, one calling somewhere closed gracefully exception before the readLn and another with the paramters of the ReadLn? – Gerard Dec 12 '13 at 13:06
  • @Remy: Using the evaluate/Modify tool in Delphi I get for AContext, AContext.Connection, AContext.Connection.IOHandler at time of the ReadLn exception the following pointers: 0x70cc99a8, 0x766aec70, nil? – Gerard Dec 12 '13 at 13:44
  • Where does clicking Break jump to now? Is the `IOHandler` nil before the segfault, or just after? The `IOHandler` should not be `nil` when entering the `OnExecute` event (please check the `OnConnect` and `OnDisconnect` events as well). The `IOHandler` should only be nil if it got freed, which should not happen until after `OnDisconnect` exits. If it is nil before entering `OnExecute` then the `IOHandler` object must be getting freed prematurely. – Remy Lebeau Dec 12 '13 at 20:16
  • @Remy: With the LF and in debugging mode, it breaks to the ReadLn(LF) line in my server unit instead of the IdIOHandler unit. In the OnConnect event, as well as the start of the OnExecute event, the IOHandler is nil. Also AContext.Connection.Socket is nil. Do you know where the IOHandler object is/should be created or where it prematurely might be freed? Looks like we are getting close :). – Gerard Dec 12 '13 at 23:49
  • @user2297042: when `TIdTCPServer` accepts a new client, it creates a new `TIdIOHandler`, which is assigned to a new `TIdTCPConnection`, which is assigned to a new `TIdContext`, which is assigned to its own worker thread, which triggers the `OnConnect`, `OnExecute`, and `OnDisconnect` events and then destroys the `TIdContext`, which destroys the `TIdTCPConnection`, which destroys the `TIdIOHandler`. So the `IOHandler` should **NOT** be nil in the `OnConnect` and `OnExecute` events. Makes me think there might be an ARC reference count mismanagement occurring somewhere. – Remy Lebeau Dec 13 '13 at 02:48
  • @Remy: Do you think I should report this to embarcadero as a bug in their software? – Gerard Dec 13 '13 at 06:25
  • @user2297042: no, because Indy is not Embarcadero software, and ARC works when used correctly, so this is more likely an Indy bug. I just don't have the setup to debug Android projects, so I can't troubleshoot this myself without being able to trace through Indy's source code. Unless you want to try that yourself (put a breakpoint in `TIdListenerThread.Run()` in `IdCustomTCPServer.pas` and follow the lifetime of the `TIdIOHandler` object that `Accept()` returns). In the meantime, I will report this to Indy's bug trackers. – Remy Lebeau Dec 13 '13 at 07:07
  • BTW, which tablet are you using? And have you installed XE5 Update 2 yet? – Remy Lebeau Dec 13 '13 at 07:17
  • I am using a Nexus 10 and XE5 with update 1, Indy version 19.0.13476.4176 (the updatchecker says I am up to date?). Debugging this issue myself I will try, but I think it is better done by an Indy expert. So thank you for reporting this issue to the Indy bug trackers and many thanks in general for your time to help us. Gerard – Gerard Dec 13 '13 at 17:21
  • I installed update 2, but unfortunately the issue stays the same. I tried debugging further myself, but am not familiar enough with the Indy source code. I hope a solution is found soon, because I am stuck. – Gerard Dec 16 '13 at 00:16

1 Answers1

0

For the specific problem in which there only needs to be a single client, a TidSimpleServer object instead of TidTCPserver object does work on Android for me.

Gerard
  • 135
  • 1
  • 10