Home arrow Delphi-Kylix arrow Page 3 - A Real World Client Server Application in Delphi
DELPHI-KYLIX

A Real World Client Server Application in Delphi


The example we used in the previous article is fine, but it can never be a real world application. It is not thread safe, and it does not identify all connected clients clearly enough. In this final part of the series, we will modify the application we have created in the earlier parts so that it can be used in the real world.

Author Info:
By: Leidago
Rating: 5 stars5 stars5 stars5 stars5 stars / 17
December 26, 2006
TABLE OF CONTENTS:
  1. · A Real World Client Server Application in Delphi
  2. · The Client
  3. · The Server
  4. · The Complete Server Code

print this article
SEARCH DEVARTICLES

TOOLS YOU CAN USE

advertisement
A Real World Client Server Application in Delphi - The Server
(Page 3 of 4 )

On the server side of things, we are not going to use the server application that we created earlier. Instead we are going to create a new  application that will use a different TCP/IP component. I want a new application because I want us to use the OnExecute method to power the server. You will recall from previous articles that there are two methods of programming an indy server, Command Handlers and OnExecute methods.

In our previous example we saw how to create and implement  a server application using the Command Handler method. So create a new  application and drop a idTCPServer component from the Indy Servers tab. Also drop a memo component.

First, we need to write a class that will collect client information as they connect and use that information when writing to the client:

public

            IP: String;

            cname: String;

            procedure SendResponse(const Clientname: String;
const AResponse: String);

        end;

This gives us two levels of differentiating between clients, the IP address and the client's nickname. So, if you are running a chat or IRC server, and need to ban a user, you will have two avenues in which to do it.

Next, we need to use another class to write thread safe messages. We will use the TWriteResponse class that we created earlier in the client section of the article. You already know what that class does, so I will not be explaining it here.

The next thing we need to do is make the methods of the context class available to the ClientInfo class that we created earlier. This makes it possible to (for example) add all connected clients to a context list, among other things.  So add this line to the TForm1 class definition:

constructor Create(AOwner: TComponent);override;

Then in the implementation section of the form add the following:

constructor TForm1.Create(AOwner: TComponent);

    begin

        inherited Create(AOwner);

        idTCPServer1.ContextClass := TClientinfo;

    end;

This code basically transfers all the procedures and methods of the Context class to our newly created ClientInfo class. Next, we need to fill the two variables contained within the ClientInfo class. This is done when the client connects. So click on the OnConnect event of the idtcpserver component and add the following code:

procedure TForm1.IdTCPServer1Connect(AContext: TIdContext);

begin

with TClientinfo(AContext) do

        begin

            if (Connection.Socket <> nil) then

            IP :=Connection.Socket.Binding.PeerIP;

            cname := Connection.IOHandler.ReadLn;

            if cname <> ''then

            begin

            connection.IOHandler.WriteLn('Welcome '+ cname);

            end

            else

            //Client did not send a name...

            begin           

            connection.IOHandler.WriteLn('You did not send a
name. Please send a name next time you try to connect!');

            connection.Disconnect;

            end;

end;        end;

All that happens here is that the server takes the client's IP address:

            IP :=Connection.Socket.Binding.PeerIP;

and the client's name:

            cname := Connection.IOHandler.ReadLn;

If the client does not send a name, it sends a message to the client and disconnects it:

            if cname <> ''then

            begin

            connection.IOHandler.WriteLn('Welcome '+ cname);

            end

            else

            //Client did not send a name...

            begin           

            connection.IOHandler.WriteLn('You did not send a
name. Please send a name next time you try to connect!');

            connection.Disconnect;

            end;

Now let's get down to the meat of the server program. We are still using the custom protocol that we discussed in the previous article. The protocol contains three commands, Aquote, Adate and Quit. So in order for the client to get a response from the server it will need to send one of these commands. We are going to set the OnExecute method to handle the client request. So double click on the OnExecute event of the server component and add the following code:

procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);

var

thedate,request,cmd,response,AFormat,sentfrom:string;

i,j:integer;

arr:Array[1..6] of String;

begin

A

//the request from the client is received in the request var

request:=acontext.Connection.IOHandler.ReadLn;

B

// we need to break the request up into command(cmd) and sender
(sentfrom)

i:=pos('@',request);

j:=pos(';',request);

cmd:= Copy(request,1,i-1);

sentfrom := Copy(request,i+1,j-i-1);

C

  //Check which command the client sent

 if cmd='aquote' then begin

 Randomize;

  arr[1]:='I still miss my ex, but my aim is improving';

  arr[2]:='Last night I was looking up at the stars and I was
wondering, where the heck is my ceiling?';

  arr[3]:='Do Roman paramedics refer to IVs as fours?';

  arr[4]:='I can resist everything except temptation.';

  arr[5]:='There is one thing I would break up over and that is
if she caught me with another woman. I wouldn''t stand for
that.';

  arr[6]:='I''ve often thought that the process of aging could be
slowed down if it had to go through Congress.';

  i:=1+ Random(6);

 //Send the response to the client...

TClientinfo(AContext).SendResponse(sentfrom,arr[i]);

    End

D

    else

    if cmd='adate' then begin

    AFormat := 'yyyy-mm-dd hh:nn:ss';

    thedate:=FormatDateTime(AFormat, Now);

    TClientinfo(AContext).SendResponse(sentfrom,thedate);

    End

E

    else

    if cmd='quit' then begin

    TClientinfo(AContext).Connection.Disconnect;

    end

    else

F

    begin

    TClientinfo(AContext).SendResponse(sentfrom,'Unknown
Command');

    end;

end;

I've divided the code up into alphabetical sections for easy reference. Part A basically receives the request from the client. The request comes in the form

cmd@fromname;

So for example a client requesting a quote will sent a request like so:

aquote@joeblogg;

We need the name of the sender because we will sent the quote back to him or her at some stage. Section B is responsible for breaking up that request into command and sender's name. It does that by using Delphi's pos() and copy() functions:

i:=pos('@',request);

j:=pos(';',request);

cmd:= Copy(request,1,i-1);

sentfrom := Copy(request,i+1,j-i-1);

Now we have a command and a sender's name in separate variables. Now all we need to do is check which command the client sent and then respond to the client using the SendResponse() procedure. This is what sections C through E will do:

  //Check which command the client sent

 if cmd='aquote' then begin

 Randomize;

  arr[1]:='I still miss my ex, but my aim is improving';

  arr[2]:='Last night I was looking up at the stars and I was
wondering, where the heck is my ceiling?';

<snip>

 //Send the response to the client...

TClientinfo(AContext).SendResponse(sentfrom,arr[i]);

End

    if cmd='adate' then begin

    AFormat := 'yyyy-mm-dd hh:nn:ss';

    thedate:=FormatDateTime(AFormat, Now);

    TClientinfo(AContext).SendResponse(sentfrom,thedate);

    <snip>

    else

    if cmd='quit' then begin

    TClientinfo(AContext).Connection.Disconnect;

    end

When a client sends a command that is not part of our protocol it gets the following response as written in section F:

    begin

    TClientinfo(AContext).SendResponse(sentfrom,'Unknown
Command');

    end;

Throughout the response you must have noticed the

TClientinfo(AContext).SendResponse();

The SendResponse procedure has the following code:

procedure TClientInfo.SendResponse(const Clientname: String;
const AResponse: String);

    var

        List: TList;

        Context: TClientInfo;

        I: Integer;

    begin

       // FContextList is inherited from TIdContext

        List := FContextList.LockList;

        try

            for I := 0 to List.Count-1 do

            begin

                Context := TClientInfo(List[I]);

                if Context.cname = clientname then

                begin

                    try

        Context.Connection.IOHandler.WriteLn(AResponse);

  except

                    end;

                    Exit;

                end;

            end;

        finally

            FContextList.UnlockList;

        end;

        Self.Connection.IOHandler.WriteLn('this server cannot
find the client you sent the message to.');

    end;

All that this procedure does is compare the client name with the names on the internal list of the ClientInfo class. The list is contained by the Context class whose methods we have transferred to our ClientInfo class. So in effect, the ClientInfo object is now maintaining it. So all we need to do is search for the client name  on the list, and if we find it, we send the response to the client  

List := FContextList.LockList;

        try

            for I := 0 to List.Count-1 do

            begin

                Context := TClientInfo(List[I]);

                if Context.cname = clientname then

                begin

                    try

        Context.Connection.IOHandler.WriteLn(AResponse);

  except

otherwise we send an error message back to the client:

Self.Connection.IOHandler.WriteLn('this server cannot find the
client you sent the message to.');

And that is all it takes to write a fully functional, and more importantly thread safe, client server application.


blog comments powered by Disqus
DELPHI-KYLIX ARTICLES

- Loading an XML Document into the DOM
- Delphi Wrapper Classes and XML
- Delphi and the DOM
- Delphi and XML
- Internet Access: Client Service
- Finishing the Client for an Internet Access ...
- The Client for an Internet Access Control Ap...
- User Management for an Internet Access Contr...
- Important Procedures for an Internet Access ...
- Server Code for an Internet Access Control A...
- Constructing the Interface for an Internet A...
- Building a Server Application for an Interne...
- Building an Internet Access Control Applicat...
- Client Dataset: Working with Data Packets an...
- Using the Client Dataset in an N-Tiered Appl...

Dev Articles Forums 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Contact Us 
Site Map 
Privacy Policy 
Support 



© 2003-2012 by Developer Shed. All rights reserved. DS Cluster 2 - Follow our Sitemap
Popular Web Development Topics
All Web Development Tutorials