1

I have a problem that is driving me up the wall. I am trying to port the server side of an Indy 10 client/server application on Windows to Linux to save costs. The application was originally developed using Delphi 2010. I have since ported it to Lazarus/FreePascal and it works fine on Windows. Given that Lazarus/FreePascal is multiplatform and free, it is the ideal candidate for the job.

I've tried all I can to get the server application to work on Linux without success. The server just does not communicate with the connected clients. Nothing at all!

I then decided to go back to square one. I tried to get a very basic example to work on Linux. The relevant parts of the source code is as shown below

procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
  s: string;
  i: Integer;
begin
  with AContext.Connection.IOHandler do
  try
    WriteLn('Type an integer and Enter');
    s := ReadLn;
    try
      i := StrToInt(s);
      WriteLn(s + ' squared is ' + IntToStr(i*i));
    except
      WriteLn(s + ' is not an integer');
    end;
  finally
    Free;
  end;
end;

procedure TForm1.FormActivate(Sender: TObject);
var
  Binding: TIdSocketHandle;
begin
  {$IFDEF UNIX}
  Binding := IdTCPServer1.Bindings.Add;
  //Binding.IPVersion := Id_IPv4;   <----- Gives compilation error Error: Identifier not found "Id_IPv4"
  {$ENDIF}
  Binding.IP := '127.0.0.1';
  Binding.Port := 6501;
  IdTCPServer1.Active := True;
end;

end.

This is the program's project file squares.lpr

program squares;

{$mode objfpc}{$H+}
// The following line is is necessary for Linux thread support            
{$IFDEF UNIX}{$DEFINE UseCThreads}{$ENDIF}     

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Interfaces, // this includes the LCL widgetset
  Forms, uSquares
  { you can add units after this };

{$R *.res}

begin
  RequireDerivedFormResource := True;
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

When I try to connect to the server from the terminal using telnet, I get the following response

telnet 127.0.0.1 6501
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
7
Connection closed by foreign host.

As you can see, telnet connects to the server. But the server's first response after the client connects "Type an integer and Enter" does not show up. In addition, when I send a number to the server e.g "7" to be squared, telnet says "Connection closed by foreign host". So the telnet client also does not receive the server's responses at all. I'm using Indy svn version so it is not a question of an old Indy version.

So even this basic example does not work in Linux! I don't know how to solve this problem so I really need your help. In addition, if you have any material I can read on socket programming on Linux using Pascal, I'll really appreciate it.

I'm using Lazarus 0.9.31/FPC 2.4.4 and Indy 10.5.8 on Linux Mint.

JDaniel

Vibeeshan Mahadeva
  • 7,147
  • 8
  • 52
  • 102
JDaniel
  • 100
  • 4
  • 9

2 Answers2

2

Id_IPv4 is defined in IdGlobal.pas, make sure that unit is in your uses clause. Note that you are calling Bindings.Add() only if UNIX is defined, but you are attempting to access the Binding outside of the IFDEF block. You don't need the IFDEF block at all. Indy defaults to IPv4.

Regarding the communication issue, I see nothing wrong with the code you have shown, provided FreePascal is correctly calling TIdIOHandler.WriteLn() and not some console WriteLn() I/O routine. Can you show the client code?

Server-side, the only thing I can think of right now that might go wrong is a possible failure in Indy's TIdTextEncoding class when sending/receiving strings, if you have set the TIdIOHandler.DefStringEncoding property or the global GIdDefaultEncoding variable to a non-default encoding. On non-Windows systems, TIdTextEncoding uses the iconv library, and Indy's iconv support is known to be a bit buggy right now. On the other hand, Indy's default encoding is ASCII, which does not rely on iconv at all, so there should not be any failures using that.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks for the reply Remy. a) The client side is the telnet output above. I'm using telnet as a client to test the server so there is no client side code. b) I did not change the default encoding state at all since all this simple server has to do is to send the square of a number to the telnet client. Since this is the case, the problem lies elsewhere. I'll investigate the way FreePascal calls TIOHandler.WriteLn however and I'll keep you posted. – JDaniel Oct 16 '11 at 19:04
0

@Remy Lebeau. I found the answer. I was playing around with the text encoding until I got the code that works below:

procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var
  s: string;
  i: Integer;
begin
  with AContext.Connection.IOHandler do
  try
    {$IFDEF UNIX}
    DefStringEncoding := TIdTextEncoding.Default
    {$ENDIF}
    WriteLn('Type an integer and Enter');
    s := ReadLn;
    try
      i := StrToInt(s);
      WriteLn(s + ' squared is ' + IntToStr(i*i));
    except
      WriteLn(s + ' is not an integer');
    end;
  finally
    Free;
  end;
end;

procedure TForm1.FormActivate(Sender: TObject);
var
  Binding: TIdSocketHandle;
begin
  {$IFDEF UNIX}
  Binding := IdTCPServer1.Bindings.Add;
  Binding.Port := 6501;
  {$ENDIF}
  {IFDEF MSWINDOWS}
  IdTCPServer1.DefaultPort := 6501
  {$ENDIF} 
  IdTCPServer1.Active := True;
end;

This is the telnet response:

telnet 127.0.0.1 6501
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Type an integer and Enter
7
7 squared is 49
Connection closed by foreign host.

However, I would still like to know how I can set the global GIdDefaultEncoding variable so that I can do away with the DefStringEncoding := TIDTextEncoding.Default. Thanks for your assistance.

JDaniel
  • 100
  • 4
  • 9
  • You still don't need the IFDEF – Remy Lebeau Oct 16 '11 at 23:39
  • You still don't need the IFDEFs. The `Bindings` and `DefaultPort` properties work exactly the same way in Windows and Linux. As for `GIdDefaultEncoding`, you set it like any other variable, eg: `IdGlobal.GIdDefaultEncoding := encOSDefault;`, but it only takes effect if `TIdIOHandler.DefStringEncoding` is nil, which it is not by default, so you have to assign a value to it anyway. It does not make sense why using `TIdTextEncoding.Default` works, but the default assignment of `TIdTextEncoding.ASCII` does not. – Remy Lebeau Oct 16 '11 at 23:45
  • BTW, why are you calling `Free()` on the IOHandler? If you want to close the connection, call `AContext.Connection.Disconnect()` instead. If you want to keep the connection open so the client can send more commands, just exit the `OnExecute` event handler and let `TIdTCPServer` fire it again (it is fired in a loop for the lifetime of the connection). The assignment of the `IOHandler.DefStringEncoding` property should be done in the `OnConnect` event instead. – Remy Lebeau Oct 16 '11 at 23:49
  • Yes. I don't need it in the server OnExecute procedure but I do need it in the FormActivate procedure because Windows cannot compile the statements with "Binding" in them. I don't know why the default 'TIdTextEncoding.ASCII' does not work. It is a mystery to me. As for the rest, I'll update the code with your suggestions. – JDaniel Oct 16 '11 at 23:54
  • The `Bindings` collection works just fine in Windows. I have never had any compiler problems with it. What exactly does not work for you? – Remy Lebeau Oct 16 '11 at 23:57
  • FYI, the default assignment of the `TIdIOHandler.DefStringEncoding` property is not currently tied to the `GIdDefaultEncoding` variable. The more I think about it, it probably should be. Tat would make `GIdDefaultEncoding` more useful. I'll look into making that change during this coming week (I'm on the Indy development team). – Remy Lebeau Oct 16 '11 at 23:57
  • My mistake. You are right about the Bindings collection. It works fine on both Windows and Linux. I've removed all the $IFDEFs. The code works fine on both platforms without them. – JDaniel Oct 17 '11 at 00:19
  • Telnet works perfectly but I guess that I would have to explicitly state the encoding type in my GUI clients so that there may be a perfect handshake between the server and the client. In addition, I noticed that the default encoding on Linux handles French text (Win 1252) properly (at least using Telnet). On my Windows client and server applications, I had to change it to TIdTextEncoding.UTF8 in order to properly read French text. What do I need to do to make it fully Unicode compatible? – JDaniel Oct 17 '11 at 00:48
  • Where do I put the TIdIOHandler.DefStringEncoding property on the client side? Should it be in the OnBeforeBind event or in the OnConnected event? Thanks for your assistance. – JDaniel Oct 17 '11 at 01:09
  • You can use Win1252 on Windows as long as you have that language/codepage installed. In Indy, use `TIdTextEncoding.GetEncoding()` or better `CharsetToEncoding()` to access any installed charset/codepage (don't forget to free the returned `TIdTextEncoding` object when you are done using it). However, the best option for supporting Unicode is to use UTF-8. You can set the `DefStringEncoding` anytime you want before exchanging strings, such as after `Connect()` exits. Indy is largely not event-driven, so don't get in the habit of relying on events on the client side to drive your logic. – Remy Lebeau Oct 17 '11 at 02:07