multisocket with lnet component - how do this lazarus / 38-Networking and Web Programming ---------------------------------------------------------------------- jolix · 2010-06-29 17:20 Hi, Solved questions posted on this forum, is time to next war. My application should open and talk with until maximum 32 sockets (32 different IP) each one is an tcp server electronic module. Those modules are placed in different places in my home and my garden. This amount of different sockets are to much to have 32 software modules, so the ideal is to have some sort of index for access those modules in circular way. Someone in this forum with knowledge enough is capable to elucidate me about it? Can i do it with LNet ? Thanks in advance Jo ---------------------------------------------------------------------- Almindor · 2010-07-03 13:23 Do I understand it right that you need 32 separate servers? If so you can make an array or list of them. You can still use the "visual" ones (if you use forms) but make sure to assign the form as parent otherwise it won't work. You can share the event callbacks on them too, just make sure to use the aSocket parameter to handle replies etc. ---------------------------------------------------------------------- jolix · 2010-07-04 12:33 Before anything else, many thanks Almindor. Back to the topic, yes, you have understood right, i have 32 TCP/modules with an unique IP/Port for each one. Additionally, i can configure destination IP/Port that is the IP of my PC. You have made some suggestions that i understand  it formally but not in practice, because my experience is on microcontrollers, not pc software. So how do i index sockets as you are you suggesting?, taking in account that all modules and consequetly, all sockets must remain open. About assign form as parent, since i work in the main form, is implicit that is parent!? Regards, Joli ---------------------------------------------------------------------- Almindor · 2010-07-05 09:25 Ok, before we go any further I need to know why you need 32 separate servers and not 1 server with 32 sockets in it. Do you need different port for each of the incoming connection? If you need 32 connections on same port then you're mixing things up a bit, and just need one server with 32 sockets in it. If that's the case then all you have to do with lNet is use the OnAccept event and "store" the socket (from the aSocket parameter in the procedure) into some list with some sort of identifier (you can use a TStringList.Objects or other lists/maps for this). If you really need 32 different ports opened then you need 32 TLTcp servers listening on them, best is to use an array. Specify what you need first tho before we get into code. ---------------------------------------------------------------------- jolix · 2010-07-05 13:24 Hi Almindor, QuoteOk, before we go any further I need to know why you need 32 separate servers and not 1 server with 32 sockets in it. Instead of trying to explain things for wich i'm very green, perhaps is more interesting to describe the system. For instance, in different places of my home, i will have tcp/ip modules to collect the temperature. Additionally, each one have one output to control air conditioner equipment (on/off). Each module is configured as a server and have one ip/port that i can modify. Through this socket, my program sends commands to inquire what temperature is or send commands to tell the module turn on/off air conditioner. Additionally, i can configure on each module the ip of my pc and port by wich the program will receive or listen. Even before test, because right now i have just one module, as a result of module beaviour, i suspect that i can have all modules with the same ip but with different ports. I will have shure about it, just in a few days from now. The question you put about, why not configure modules as a client, well is my program that allways assumes the iniciative in terms of stablish connection and communication too. My module just acepts connections and commands from my pc. Working like this i though the module is server. Tell me if i'm wrong. Regards, Jo ---------------------------------------------------------------------- jolix · 2010-07-06 11:13 Just one more thing in addition to my last post 5 July. The modules i talk about, doesn't support way to initiate connections, but only acepts connections requests from PC. Regards, Jo ---------------------------------------------------------------------- JohnvdWaeter · 2010-07-06 11:34 Than I read it as you have 32 little servers and you want one client to collect data from these 32 servers, right? Next, do the servers only send data when requested to? In that case you'll need only one tcpclient that for each little server  1: sets ipnr and port  2: opens a connection  3: request data  4: close connection If, for whatever reason, you want to keep the connections open, you'll need 32 tcpclients.... John ---------------------------------------------------------------------- Almindor · 2010-07-06 15:26 As JohnvdWaeter said If you really need 32 connections open in parallel you can use an array. Just don't forget to assign the parent property (use your form) and the events (you can share these if you want, e.g. just one procedure for receive events for all 32 clients). ---------------------------------------------------------------------- jolix · 2010-07-07 03:57 JohnvdWaeter, the answers to your questions is yes. Follow what you suggest, the only step i want to avoid, is the last one (Close Connection). To write the code for 32 clients i will need drag 32 tcp components to my form, or just enough one and declare 32 different names ? type         MyTcpSocket1: TLTCPComponent;         MyTcpSocket2: TLTCPComponent;         // next 3 to 31 socket declarations         MyTcpSocket32: TLTCPComponent; end; To use arrays as Almindor said, wich is without doubt more elegant, i don't know how to do it. You know any internet location where i can find one example? or maybe just a site where this question is discussed? Thanks by your help Jo ---------------------------------------------------------------------- JohnvdWaeter · 2010-07-07 07:27 Almindor certainly will know the best solution using an array of tcpcomponents. Every little server has his own ipnr and portnumber (on which it listens for incoming connections). ipnrs are different to each server, ports can be different but may be the same. Would be nice if the last part of the ipnr identifies the server. So, for example, for the servers you use 192.168.2.101 ... 192.168.2.132. In the common OnReceive-event you will know from what server the answer comes by checking the last two digits of the asocket.peeraddress. On the other hand, if this is a local network, I would not bother to use TCP but rather use connectionless UDP. This saves you the trouble programming around sudden disconnections (like power-problems, cable disconnected etc). And a single TUDPcomponent would be enough if your client polls all the servers. But that all depends on your network, the possibility of using UDP on the little servers and the frequency that you want the data to be read (every 10 milliseconds? once a minute? once an hour?). John ---------------------------------------------------------------------- jolix · 2010-07-07 12:31 Hi JohnvdWaeter, Yes, what i'm doing is exactally coincident with what you said. About ip segment, it is the same for server and client, because both is on the same local network. UDP is nothing not crossing my mind already, maybe with some compared advantages. I only have to modify the microcontroler firmware to be shure data is sent right. One additional advantage is that udp supports broadcasts wich is an interesting feature. Back to the TCP, the interval beetwen data packages will be, never less than 2 seconds. Sharing the Onreceive event is a thing i understand, but i still need array of tcpcomponent, right? and is the only way i have to know who send data!? About array of tcpcomponents, i realize that i should go several years back and try to renew forgoten things. Note that in my times, only a few guys talk about object oriented programming, borland turbo pascal 5. Mean while i hope Almindor give me some help on this. Thanks, Jo ---------------------------------------------------------------------- JohnvdWaeter · 2010-07-07 13:00 I still think UDP is the way to go, no problems with connect/disconnect/arrays etc. A single UDP-server with a single onReceive-event will do. But if you use TCP and want to stay connected, then yes, you will need 32 tcpclients, in an array or not. If in an array, use someting like (by head, not tested): Code: [Select]var   MyTCP = array [1..32] of TLTCPComponent; .... .... for n:=1 to 32 do  begin   MyTCP[n]:=TLTCPComponent.create(nil);   MyTCP[n].OnReceive := @MyReceive;   ...   ...  end; After this you can use them as MyTCP[n].connect, MyTCP[n].send etc John ---------------------------------------------------------------------- jolix · 2010-07-07 14:54 Hi JohnvdWaeter, This is exactly what i need. I will test it imediatly and let you know. But once I have more than one module, i will do real tests. I'm very grateful for your help. Regards, Jo ---------------------------------------------------------------------- Almindor · 2010-07-10 12:02 Don't forget that if you use visual lNet you need to parent the component too like so: MyClient.Parent := FormMain; // put main form as parent of my component You can also use the form in .Create so when you close the form you free the network components Tell us how it went. ---------------------------------------------------------------------- jolix · 2010-07-10 17:07 Hi, JohnvdWaeter and Almindor, I'm struggling with this and i'm not getting the expected result. First: var   MyTCP = array [1..32] of TLTCPComponent; The compiler give me error. It only accepts: var   MyTCP : array [1..32] of TLTCPComponent; Like that, compiler do the job without errors but app give a runtime error when it start in a message box: Project socket raised exception class "EClassNotFound" with message: Class "TLTCPComponent" not found Next, after click on OK button: Execution stopped procedure fcp_riseexception Finally opens LResources unit, pointing to a line where the problem is: Code: [Select]    try       //DebugLn('Form Stream "',ClassType.ClassName,'"');       //try       DestroyDriver:=false;       Reader := CreateLRSReader(Stream, DestroyDriver);       try         Reader.ReadRootComponent(Instance);       finally         Driver := Reader.Driver;         Reader.Free;         if DestroyDriver then           Driver.Free;       end; This code block is part of "function InitLazResourceComponent(Instance: TComponent;" Almindor , Where is supposed to place MyTCP.Parent := TfrmMain ? considering the code below, that certainly is not correct. Code: [Select]unit main; {$mode objfpc}{$H+} interface uses   Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,   StdCtrls, lNetComponents, lNet, ExtCtrls, ComCtrls; type     { TfrmMain }     TfrmMain = class(TForm)         btnConnect: TButton;         btnDisconnect: TButton;         ebxIP: TEdit;         ebxPort: TEdit;         GroupBox1: TGroupBox;         gbxCommands: TGroupBox;         Timer1: TTimer;         procedure btnConnectClick(Sender: TObject);         procedure btnDisconnectClick(Sender: TObject);         procedure MyConnect(aSocket: TLSocket);         procedure MyDisconnect(aSocket: TLSocket);         procedure MyReceive(aSocket: TLSocket);         procedure Timer1Timer(Sender: TObject);     private     public         Constructor Create(TheOwner: TComponent); override;         procedure FormKill(Sender: TObject);     end; var     frmMain: TfrmMain;     MyTCP : array [1..32] of TLTCPComponent; const     CR = #13;     LF = #10;     CRLF = CR + LF; implementation Constructor TfrmMain.Create(TheOwner: TComponent); var     x: integer; begin     inherited Create(TheOwner);     OnDestroy := @FormKill;     for x := 1 to 32 do begin         MyTCP[x] := TLTCPComponent.create(nil);         MyTCP[x].OnReceive := @MyReceive;         MyTCP[x].OnConnect := @MyConnect;         MyTCP[x].OnDisconnect:= @MyDisconnect;     end; end; end; Note that i suppressed all the code not significant right now. Please remember my oop knowledges is very rude. I have one program with only one socket running fine with LNet. Regards, Jo ---------------------------------------------------------------------- Almindor · 2010-07-11 10:00 Did you add lNet as a required package to your project? See project/project inspector [main menu];. In the "required packages" part do you see lNetVisual? If not add it (press the "+") and pick it from the "New required" tab (it's in the picklist there if it's installed in lazarus). This should fix the runtime problem you have. Also see if lNetComponents unit is in the uses section of your main unit (the one with form). If not, add it as well. Your create code seems fine (you can use TLTcpComponent.Create(frmMain) and can thus omit any freeing of the components at destroy). As for the parent, you can skip that part, I forgot I "fixed" this in a different way, so no need to bother ---------------------------------------------------------------------- jolix · 2010-07-11 11:31 Hi Almindor, Answering your questions: QuoteDid you add lNet as a required package to your project? See project/project inspector [main menu];.I did. The proof is i wrote a program with a single socket and it works ok. QuoteIn the "required packages" part do you see lNetVisual? If not add it (press the "+") and pick it from the "New required" tab (it's in the picklist there if it's installed in lazarus).Yes i see it. QuoteThis should fix the runtime problem you have. Also see if lNetComponents unit is in the uses section of your main unit (the one with form). If not, add it as well.Yes, as you can see in the code posted in my last post. Code: [Select]uses   Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,   StdCtrls, lNetComponents, lNet, ExtCtrls, ComCtrls; QuoteYour create code seems fine (you can use TLTcpComponent.Create(frmMain) and can thus omit any freeing of the components at destroy). What i understand about it is, since the objects are children of the Form wich have a constructor, the destructor frees all objects. I feel the solution is close, but they are some missed part some where. Same results with: Windows Vista Lazarus 0.9.26 fpc 2.2.2 lnet 0.63 XP Pro Lazarus 0.9.26 FPC 2.2.2 lnet 0.64 Regards Jo ---------------------------------------------------------------------- jolix · 2010-07-11 14:00 Hi JohnvdWaeter, Almindor, I decided do a simple test as showed below: 1. I've placed two TCP components on my form. 2. Rename these as, MyTCP1 and MyTCP2 respectively. 3. Assign to both the same tree events:    - MyReceive to OnReceive event    - MyConnect to OnConnect event    - MyDisconnect to OnDisconnect event Like thus, program works ok. So, one solution to my case is drag 32 TCP components to my form which is a very stupid thing to do, don't you agree? What special and different particularity lnet component have that prevents me from creating arrays of objects? I will try do the John example with buttons to verify if the issue is something related with example method or not. Additionally, i see on the internet that array of objects can be done with TList also with TObjectList. The question is, i already waste days trying google and seems me that every one uses a simple connection, situation that surprises me. So, i apologize to you guys to be so bored. Aware of the work needed to help me, I'll make a Lazarus clone of the server module, to facilitate your help, if you have the kindness and patience to continue. Regards, Jo ---------------------------------------------------------------------- jolix · 2010-07-11 14:09 In addition to my last post.. I missed since the begining that OnDisconnect event doesn't work. All others works fine and i'm surprised since i have doing things right and in the same way for all events, through the object inspector /events Tab. Regards Jo ---------------------------------------------------------------------- JohnvdWaeter · 2010-07-11 14:18 Jolix, QuoteNote that i suppressed all the code not significant right now. Maybe... the other code IS more significant than you think it is.... For example, in the OnReceive-event, you MUST read from the socket. If you do not, ondisconnect is not fired either... I don't see nothing wrong in the declaration and creation of the array. Note also that you created tcp CLIENTS and that it is a clients task to initiate a connection with a server. Only after that, data can be exchanged. John ---------------------------------------------------------------------- jolix · 2010-07-12 10:58 Hi JohnvdWaeter, Code: [Select]{******************************************************* *********************** * This is a JohnvdWaeter example to have 32 sockets based on LNet component. * Almindor is also involved trying to help *--------------------------------------------------------------------- --------- * Forum Topic: http://www.lazarus.freepascal.org/index.php/topic,9751.15.html *--------------------------------------------------------------------- --------- * Date:      2010/07/11 * Project:   My home Air Conditioner control * Test Fase: Adquire temperature of one sensor * Future:    Adquire temperature of 32 sensors with ON/OFF control * Comms:     Network TCP/IP based * * Cliente Software: *            IDE:       Lazarus 0.9.26 *            Compiler:  FPC 2.2.2 *            O.S.       XP / Vista ********************************************************************** ********} unit main; {$mode objfpc}{$H+} interface uses     Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,     StdCtrls, lNetComponents, lNet, ExtCtrls, ComCtrls; type     TfrmMain = class(TForm)         btnConnect: TButton;         btnDisconnect: TButton;         btnGetTemp: TButton;         btnExit: TButton;         btnSendACK: TButton;         ebxIP: TEdit;         ebxPort: TEdit;         gbxServer: TGroupBox;         gbxCommands: TGroupBox;         lblDebugList: TLabel;         lblIP: TLabel;         lblPort: TLabel;         lbxLog: TListBox;         StatusBar1: TStatusBar;         Timer1: TTimer;         procedure btnConnectClick(Sender: TObject);         procedure btnDisconnectClick(Sender: TObject);         procedure btnExitClick(Sender: TObject);         procedure btnGetPointersClick(Sender: TObject);         procedure btnGetTempClick(Sender: TObject);         procedure btnSendACKClick(Sender: TObject);         procedure MyConnect(aSocket: TLSocket);         procedure MyDisconnect(aSocket: TLSocket);         procedure MyReceive(aSocket: TLSocket);         procedure Timer1Timer(Sender: TObject);     private     public         Constructor Create(TheOwner: TComponent); override;         procedure FormKill(Sender: TObject);     end; var     frmMain: TfrmMain;     MyTCP : array[1..32] of TLTCPComponent;     NewMsg, OldMsg: string;     constates: integer; const     CR = #13;     LF = #10;     CRLF = CR + LF;     ACK = '00';     GET_TEMP = 'AE'; implementation {********************************************************************* ********* * Init ********************************************************************** ********} Constructor TfrmMain.Create(TheOwner: TComponent); var     x: integer;     s: string; begin     inherited Create(TheOwner);     OnDestroy := @FormKill;     for x := 1 to 32 do begin         MyTCP[x] := TLTCPComponent.create(nil);         MyTCP[x].OnReceive := @MyReceive;         MyTCP[x].OnConnect := @MyConnect;         MyTCP[x].OnDisconnect:= @MyDisconnect;     end;     NewMsg := '';     ebxIP.Text := '192.168.2.100';     ebxPort.Text := '1001';     btnConnect.Enabled := True;     btnDisconnect.Enabled := False;     ebxIP.Color := clScrollBar;     ebxPort.Color := clScrollBar;     constates := 0; end; {********************************************************************* ********* * Form Close ********************************************************************** ********} procedure TfrmMain.FormKill(Sender : TObject); begin end; {********************************************************************* ********* * Close by Button ********************************************************************** ********} procedure TfrmMain.btnExitClick(Sender: TObject); begin     Close; end; {********************************************************************* ********* * Open Connection by button ********************************************************************** ********} procedure TfrmMain.btnConnectClick(Sender: TObject); begin     constates := 1; end; {********************************************************************* ********* * Disconnect button ********************************************************************** ********} procedure TfrmMain.btnDisconnectClick(Sender: TObject); begin     if MyTCP[1].Connected then begin         MyTCP[1].Disconnect;         btnConnect.Enabled := True;         btnDisconnect.Enabled := False;         ebxIP.Color := clScrollBar;         ebxPort.Color := clScrollBar;         StatusBar1.Panels[1].Text :=  'Disconnected';     end; end; {********************************************************************* ********* * Get Temperature button *--------------------------------------------------------------------- --------- * Server Module should answer: 'E0XX'+#13#10, where XX teperature in ASCII Hex ********************************************************************** ********} procedure TfrmMain.btnGetTempClick(Sender: TObject); begin     MyTCP[1].SendMessage(GET_TEMP + CRLF); end; {********************************************************************* ********* * Send Acknowlodge: Server also reply ACK ********************************************************************** ********} procedure TfrmMain.btnSendACKClick(Sender: TObject); begin     MyTCP[1].SendMessage(ACK + CRLF); end; {********************************************************************* ********* * OnReceive event ********************************************************************** ********} procedure TfrmMain.MyReceive(aSocket: TLSocket); var s: String; begin     MyTCP[1].GetMessage(s);     if Length(s) > 3 then begin         lbxLog.Items.Add(copy(s,1, Length(s)-2));         s:='';     end; end; {********************************************************************* ********* * OnConnect event ********************************************************************** ********} procedure TfrmMain.MyConnect(aSocket: TLSocket); begin     btnConnect.Enabled := False;     btnDisconnect.Enabled := True;     StatusBar1.Panels[1].Text:='Connected to: '+ ebxIP.Text+ ':'+ ebxPort.Text; end; {********************************************************************* ********* * OnDisconnect event ********************************************************************** ********} procedure TfrmMain.MyDisconnect(aSocket: TLSocket); begin     btnConnect.Enabled := True;     btnDisconnect.Enabled := False;     StatusBar1.Panels[1].Text:='Disconnected from: '+ ebxIP.Text+ ':'+ ebxPort.Text; end; {********************************************************************* ********* * Try to Connect state machine *--------------------------------------------------------------------- --------- * This state machine is needed because server sometimes hangup and reset it self * during powerup. This is working well on the 1 socket program version *--------------------------------------------------------------------- --------- * Right now number of tries are infinite and just work with MyTCP[1]. * In the future we have finite number of tries for each one connection ********************************************************************** ********} procedure TfrmMain.Timer1Timer(Sender: TObject); var s: string; begin     case constates of         0:  begin             end;         1:  begin                 MyTCP[1].Connect( ebxIP.Text, StrToInt(ebxPort.Text));                 constates := 2;             end;         2:  begin                 MyTCP[1].SendMessage('00'+ CRLF);                 ebxIP.Color := clAqua;                 ebxPort.Color := clAqua;                 constates := 3;             end;         3:  if MyTCP[1].Connected then begin                 ebxIP.Color := clLime;                 ebxPort.Color := clLime;                 gbxCommands.Enabled := True;                 constates := 0;  // Connected. Stop state machine             end             else begin                 ebxIP.Color := clScrollBar;                 ebxPort.Color := clScrollBar;                 constates := 1; // Try again             end;     end; end; initialization   {$I main.lrs} end. Also i can send all files needed to compile, but i don't see how can do it. Regards, Jo ---------------------------------------------------------------------- jolix · 2010-07-12 14:01 Hi again, I tried to do an array of Buttons using the same methodology as for TLTCPComponent and works fine. So, this leads me to conclude that the component LNET has some peculiarity that prevents creating arrays. At least in the way suggested. Maybe Almindor wants to say something about it, while author of this component? Regards Jo ---------------------------------------------------------------------- JohnvdWaeter · 2010-07-12 15:29 I did not try your code, but every little server has the same ipnr? maybe the code is not complete, but they all must have different ipnrs... And: better show code for all 32 servers. You could pinpoint where the error occurs. Like, for example:  try   tcp[n].connect  except   on e:exception do   showmessage('error on #'+inttostr(n)+', '+e.message)  end; John ---------------------------------------------------------------------- jolix · 2010-07-12 17:17 John My little servers can eventualy have the same ip but different ports. I'm still waiting for arriving modules. By now as you can see on the code sent, i only work with one socket, MyTCP[1]. However, before advance to what you suggest, my problem is that my code doesn't even start. The compiler / debugger behavior is: "Project xxx created successfull" After, debbuger i think, lanches this message window: Project socket raised exception class "EClassNotFound" with message: Class "TLTCPComponent" not found After click OK, IDE lanches LResources unit on the editor and points to this line: Code: [Select]      try         Reader.ReadRootComponent(Instance); // <<< DEBUGGER POINTS TO THIS LINE       finally This lines are part of procedure FormDataToText(FormStream, TextStream: TStream);, inside of LResources unit. This happens just after compilation end and i never see my program form. I receive a message: Program stops. I even have commented those lines in my code: Code: [Select]    for x := 1 to 32 do begin         //MyTCP[x] := TLTCPComponent.create(nil);         //MyTCP[x].OnReceive := @MyReceive;         //MyTCP[x].OnConnect := @MyConnect;         //MyTCP[x].OnDisconnect:= @MyDisconnect;     end; The result is the same. So i think the issue have to do with some declaration or LCTCPComponent issue, as i said before, unless i have something wrong in the code. Remember i test this array of objects with buttons and works. Regards, Jo ---------------------------------------------------------------------- jolix · 2010-07-12 18:24 Hi again, Sorry to come again. This oop thing is very confused to me yet. (x is y, y as z...) As showed by the message error mentioned in my last post, the compiler could not instantiate: Code: [Select]MyTCP[x]:=TLTCPComponent(nil) if i understand it right. Now program runs as expected. Solution: In the type section: Code: [Select]MySocket : TLTCPComponet; MySocket is the name i given to the component on the Object Inspector. Inside of form constructor: Code: [Select]    for x := 1 to 32 do begin         //MyTCP[x]              := TLTCPComponent.create(nil); <<<< BAD WAY         MyTCP[x]              := MySocket.create(nil);  <<<<< GOOD WAY         MyTCP[x].OnReceive    := @MyReceive;         MyTCP[x].OnConnect    := @MyConnect;         MyTCP[x].OnDisconnect := @MyDisconnect;         MyTCP[x].OnError      := @MyError;     end; After "this battle won almost to the last man:)", i just wait for more modules to test more than one socket. I will test 32 servers with: - Same IP but different Ports. - Different IP's but same Port. When finish, i will notice. Meanwhile, i would like to thank you very much to you John and Almindor. Also my apologyses to be so boring. Regards, Jo