Link to home
Start Free TrialLog in
Avatar of dudup
dudup

asked on

Simple client/server application continued

Hi,

I took the example that was made by TheRealLoki from this question :

https://www.experts-exchange.com/questions/21842452/Simple-client-server-application.html

How do I enumerate (get information) of all connected clients ??

Suppose I want to write the _ID of all connected clients.

Another strange thing is, I put "Label2" in the fServerMain form.

Then I changed two of the events, added this line :

Label2.Caption := IntToStr(IdTCPServer1.Threads.LockList.Count);

I just want to print the number of connected clients.

procedure TfServerMain.IdTCPServer1Connect(AThread: TIdPeerThread);
    var
        ID_: string;
    begin
        Label2.Caption := IntToStr(IdTCPServer1.Threads.LockList.Count);
        ID_ := AThread.Connection.Socket.Binding.PeerIP + ':' + IntToStr(AThread.Connection.Socket.Binding.PeerPort);
        AThread.Data := TClientSpecificData.CreateWithSettings(ID_);
        LogMessage(lmtInformation, ldNone, ID_ + ' Connected');
    end;

procedure TfServerMain.IdTCPServer1Disconnect(AThread: TIdPeerThread);
    begin
        if assigned(AThread.Data) then
        begin
            (AThread.Data as TClientSpecificData).Free;
            AThread.Data := nil;
        end;
        Label2.Caption := IntToStr(IdTCPServer1.Threads.LockList.Count);
        LogMessage(lmtInformation, ldNone, AThread.Connection.Socket.Binding.PeerIP + ':' + IntToStr(AThread.Connection.Socket.Binding.PeerPort) + ' Disconnected');
    end;

But then, the server got stuck after a client got disconnected.

Any idea why this happen ?

Thanks in advance.
Avatar of TheRealLoki
TheRealLoki
Flag of New Zealand image

locklist is not just a list, it's a command, you need to unlock it after
you should also use a message to update the count, it is not wise to use events
here's the ocde you need, i probably should have thought of it and put it in the main demo i wrote for you
I put a TListbox (lbConnections) and a TLabel (lConnectionCount) on the server form

  WM_DisplayConnectionCount = WM_user + 101;

    Procedure DisplayConnectionCount(var Msg:TMessage);Message WM_DisplayConnectionCount;

Procedure TfServerMain.DisplayConnectionCount(var Msg:TMessage);
    var
        i: integer;
    begin
        with IdTCPServer1.Contexts.LockList do
         try
            lbConnections.Items.BeginUpdate;
            lbConnections.Items.Clear;
            lConnectionCount.Caption := IntToStr(Count) + ' connections';
            for i := 0 to Count - 1 do
            begin
                if assigned(TIdContext(Items[i]).Data) then
                  lbConnections.Items.Add( TClientSpecificData(TIdContext(Items[i]).Data).ID)
                else
                  lbConnections.Items.Add( 'new' );
            end;
         finally
            lbConnections.Items.EndUpdate;
            IdTCPServer1.Contexts.UnLockList;
         end;
    end;

procedure TfServerMain.IdTCPServer1Connect(AContext: TIdContext);
    var
        ID_: string;
    begin
        ID_ := AContext.Connection.Socket.Binding.PeerIP + ':' + IntToStr(AContext.Connection.Socket.Binding.PeerPort);
        AContext.Data := TClientSpecificData.CreateWithSettings(ID_);
        LogMessage(lmtInformation, ldNone, ID_ + ' Connected');
        PostMessage(fServerMain.Handle, WM_DisplayConnectionCount, 0, 0);
    end;

procedure TfServerMain.IdTCPServer1Disconnect(AContext: TIdContext);
    begin
        if assigned(AContext.Data) then
        begin
            (AContext.Data as TClientSpecificData).Free;
            AContext.Data := nil;
        end;
        LogMessage(lmtInformation, ldNone, AContext.Connection.Socket.Binding.PeerIP + ':' + IntToStr(AContext.Connection.Socket.Binding.PeerPort) + ' Disconnected');
        PostMessage(fServerMain.Handle, WM_DisplayConnectionCount, 0, 0);
    end;
Avatar of dudup
dudup

ASKER

So, inside the execute thread we may not access the GUI in the main form. If we want to do this, we have to post message and capture it from the main thread.

Because later on, I would like to display progress bar for every process that are submitted by the clients :

CLIENT_1: 10%
CLIENT_2: 15%
CLIENT_3: Done
CLIENT_4: 95%

To do this, inside the execute thread, I will check the progress and then post message to the main form. Catch the message, and update the progress bar list.

Is this a good plan ?

Multithreading is also new to me :)
ASKER CERTIFIED SOLUTION
Avatar of TheRealLoki
TheRealLoki
Flag of New Zealand image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
btw, if you are using Indy 9 change the "DisplayConnectionCount" procedure to

procedure TfServerMain.DisplayConnectionCount(var Msg: TMessage);
    var
        i: integer;
        FoundDisconnection: boolean;
    begin
        FoundDisconnection := True;
        with IdTCPServer1.Threads.LockList do
         try
            lbConnections.Items.BeginUpdate;
            lbConnections.Items.Clear;
            lConnectionCount.Caption := IntToStr(Count) + ' connections';
            for i := 0 to Count - 1 do
            begin
                if assigned( TIdPeerThread(Items[i]).Data) then
                  lbConnections.Items.Add( TClientSpecificData(TIdPeerThread(Items[i]).Data).ID)
                else
                begin
                    lbConnections.Items.Add( 'Disconnecting...' );
                    FoundDisconnection := True;
                end;
            end;
         finally
            lbConnections.Items.EndUpdate;
            IdTCPServer1.Threads.UnLockList;
         end;
         if FoundDisconnection then
           PostMessage(fServerMain.Handle, WM_DisplayConnectionCount, 0, 0);
    end;
oops, top line should read
        FoundDisconnection := False;
Avatar of dudup

ASKER

Loki, could you please posted the source and form of the server part ?

If not, please the complete server Execute procedure. I cant get it to work.

Thanks
Avatar of dudup

ASKER

Here is my server execute that is taken from your demo :

procedure TfServerMain.IdTCPServer1Execute(AThread: TIdPeerThread);
    var
        i: integer;
        S, S2: string;
        InCmd: string;
    begin
// send the ID command and the user's unique "ID"
        try
    // Main Command Loop
            while AThread.Connection.Connected do
            begin
// check if user is logged in
                InCmd := ReceiveStringWithLogging(AThread);
                case (AThread.Data as TClientSpecificData).ClientStage of
                    csNone:
                    begin //LOGIN username password
                        if (pos('LOGIN', uppercase(InCmd)) = 1) then CMD_LOGIN(AThread, copy(InCmd, pos(' ', InCmd) + 1, maxint))
                        else SendStringWithLogging(AThread, 'ERROR Not logged in. Can not use "' + InCmd +'" command');
                    end;
                    csLoggedIn:
                    begin
                        if InCmd = 'PING' then  SendStringWithLogging(AThread, 'PONG') //Note: we do not show logging for this, becaue we want it as fast as possible
                        else if InCmd = 'JUMP' then SendStringWithLogging(AThread, 'Whee!')
                        else if (pos('TIMER ', InCmd) = 1) then CMD_TIMER(AThread, copy(InCmd, pos(' ', InCmd) + 1, maxint))
                        else if (pos('COUNTDOWN ', InCmd) = 1) then CMD_COUNTDOWN(AThread, copy(InCmd, pos(' ', InCmd) + 1, maxint))
                        else if InCmd = 'FILE' then CMD_FILE(AThread)
                        else if (pos('PROCESS ', InCmd) = 1) then CMD_PROCESS(AThread, copy(InCmd, pos(' ', InCmd) + 1, maxint))
                        else if InCmd = 'QUIT' then AThread.Connection.Disconnect
                        else SendStringWithLogging(AThread, 'ERROR Unknown command "' + InCmd +'"');
                    end; // of ClientStage = csLoggedIn
                end; // of case notlogged in, or logged in
            end; // of while doing commands loop
        except
            on e: EIdSocketError do
            begin
                if pos('10053', E.Message) > 0 then
                  ThreadLogMessage(lmtInformation, ldNone, 'Client disconnected')
                else
                  ThreadLogMessage(lmtError, ldNone, E.Message);
            end;
            on e: exception do
            begin
                if pos('CONNECTION CLOSED GRACEFULLY', uppercase(e.Message)) > 0 then
                  ThreadLogMessage(lmtInformation, ldNone, 'Client disconnected gracefully')
                else
                  ThreadLogMessage(lmtError, ldNone, E.Message);
            end;
        end;
    end;
you are missing the "file progress" parts

    begin
        AThread.Connection.Tag := integer(AThread.Data); //store our data object here also, so that the file progress knows a bit more about us
        AThread.Connection.OnWorkBegin := (AThread.Data as TClientSpecificData).TCPClientInsideThreadWorkBegin;
        AThread.Connection.OnWork := (AThread.Data as TClientSpecificData).TCPClientInsideThreadWork;
        AThread.Connection.OnWorkEnd := (AThread.Data as TClientSpecificData).TCPClientInsideThreadWorkEnd;

apart from that, it's identical to my execute method
I've put the server and the client code on sourceforge for you
https://sourceforge.net/projects/internetdemos/
Avatar of dudup

ASKER

Thanks Loki!