Does this multiplexed Synapse echo server look reasonable? lazarus / 38-Networking and Web Programming ---------------------------------------------------------------------- snorkel · 2012-03-01 18:25 Hi, I am working on a project with Synapse and my first implementation used a one thread per connection model and after doing some research it seemed like a select solution would be best for my needs and I came up with this demo echo server using Synapse. It uses two generic lists of tsocket, one to hold the "server socket" (the one listening) and all the other sockets that get accepted by the server socket.  the second generic list holds sockets that are ready for reading(using the synapse canread function) It seems to work extremely well and I avoid all the context swaps of the threads.  It could even be modified to have a socket list thread pool of say 500 sockets per thread and then have a listening thread drop the accepted sockets in any of the threads where the socket count is less than max(500) and then have worker threads handle time consuming tasks like transferring a file. So to handle 2000 users you would only need 4 threads vs 2000. Code: [Select]unit Unit1; {$mode objfpc}{$H+} interface uses   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,blcksock,synsock,fgl; type    tsocklist = specialize TFPGList; type   { TForm1 }   TForm1 = class(TForm)    Button1: TButton;    Memo1: TMemo;    procedure Button1Click(Sender: TObject);   private     { private declarations }   public     { public declarations }   end; var   Form1: TForm1; implementation {$R *.lfm} { TForm1 } procedure TForm1.Button1Click(Sender: TObject); var    serversock,_sock,ready_sock,select_sock:TTCPBlockSocket;    slist:tsocklist;    slist_ready:tsocklist;    x:integer;    asock:TSocket;    data:string;    {**************************** Local Functions ******************************}    procedure init_server();    begin         slist:=tsocklist.Create;         slist_ready:=tsocklist.create;         serversock:=TTCPBlockSocket.create;         select_sock:=TTCPBlockSocket.create;         ready_sock:=TTCPBlockSocket.create;         with serversock do              begin                   EnableReuse(true);                   CreateSocket;                   SetLinger(true,10);                   bind('0.0.0.0','7800');                   listen;                   if serversock.LastError = 0 then                      slist.Add(serversock.Socket);              end;         memo1.Lines.add('Server Started');    end;    procedure faccept();    var       sock:tsocket;    begin          sock:=serversock.Accept;          if serversock.LastError = 0 then             begin                  slist.add(sock);                  select_sock.Socket:=sock;                  memo1.Lines.add(format('Accepted connection from %s:%d',[select_sock.GetRemoteSinIP,select_sock.GetRemoteSinPort]));             end;    end;    function handle_data(rsock:TTCPBlockSocket):boolean;    var       data:string;    begin         result:=true;         data:= rsock.RecvString(10);         if data <> '' then //send data back to client            rsock.SendString(data+CRLF)         else //client disconnected             result:=false;    end;    procedure handle_disconnect(rsock:TTCPBlockSocket);    begin         //remove the sock from the list and free          slist.Remove(rsock.socket);          memo1.Lines.add('Client disconnected');    end;    function fselect(inlist, readylist: tsocklist): integer;    var       z:integer;    begin          for z:= 0 to inlist.Count -1 do            begin                   select_sock.socket:=inlist.Items[z];                   if select_sock.CanRead(10) then                      readylist.Add(select_sock.socket);                   //Don't hog CPU                   sleep(0);            end;          result:=readylist.Count;    end;    {**************************** END Local Functions ******************************} begin      init_server();      while true do            begin                if fselect(slist,slist_ready) > 0 then                   begin                        for x:= 0 to slist_ready.Count-1 do                           begin                                //Handle the server socket                                ready_sock.socket:= slist_ready.Items[x];                                if ready_sock.socket = serversock.socket then                                   faccept()                                else                                    if not handle_data(ready_sock) then                                       handle_disconnect(ready_sock);                                //Don't hog CPU                                sleep(0);                           end;                        //clear the ready list                        slist_ready.Clear;                   end;            end;       serversock.free;       select_sock.free;       ready_sock.free; end; end. ---------------------------------------------------------------------- User137 · 2012-03-01 22:54 Because of this code: Code: [Select]data:= rsock.RecvString(10);If you have 500 clients on 1 thread, it would take 5 seconds to go through them all. Imagine the lag that would cause to the network ---------------------------------------------------------------------- snorkel · 2012-03-02 07:47 Actually it would not take 5 seconds because the socket is ready for reading at that point and the timeout would never be used.  If it's ready to read it returns immediately from the read. ---------------------------------------------------------------------- Michael Collier · 2012-03-02 16:30 Is it possible to simulate a high number of clients and see what happens? ---------------------------------------------------------------------- User137 · 2012-03-03 00:41 Quote from: snorkel on March 02, 2012, 08:47:55 amActually it would not take 5 seconds because the socket is ready for reading at that point and the timeout would never be used.  If it's ready to read it returns immediately from the read.This is only true if all those sockets actually have incoming data, and are constantly streaming it nonstop. But if even 1 client has connection issues or there's state of application that he doesn't need to send data, then the timeout should happen. ---------------------------------------------------------------------- snorkel · 2012-03-05 17:22 Well, That read you are referring to is after the select call has returned and that socket is ready to read.  That particular call is after select has returned. I think you are really referring to this: select_sock.CanRead(10) which is in the select call, so yes this could take up to 5 seconds on 500 sockets. It really depends on what your server is doing, if it's a heavily used web server select is probably not the best choice, but for a IM server where most of the clients are sitting around idle most of the time I thing it would be fine. I have since changed the code to use a modified version of the Synapse Groupcanread function which uses the OS select call, which has some limitations on windows.  On Linux select can handle 1024 sockets, but on windows it can really only handle 64 because select on win32 uses waitformultipleobjects internally and that is limited to 64 handles.  FD_SETSIZE can be increased but you could run into problems with the internal use of waitformultipleobjects. So what I am going to do is have a thread per 64 sockets and use a threadlist object to keep track of them, when the first one becomes full, it will spawn another one and if any of them reach zero terminate. So on a multi core CPU this should be pretty efficient for a IM type server. For example for 2000 clients it would use 31 threads, that's a lot more efficient than using 2000 threads. Memory usage should be dramatically less as well since you won't have 2000 database objects or whatever you might use in the threads. ---------------------------------------------------------------------- snorkel · 2012-03-05 17:28 Quote from: Michael Collier on March 02, 2012, 05:30:17 pmIs it possible to simulate a high number of clients and see what happens? When I get the final version all set I plan on writing a test app that will spawn a bunch of clients and have them randomly send and receive.  If I do the 64 sockets per thread I don't anticipate there being any issues. ---------------------------------------------------------------------- User137 · 2012-03-06 02:53 Would there really be any issues even if your app spawns a million threads, 1 per socket?