In this article, the second part of a series, we will continue discussing how TCP/IP works in tandem with client server applications. Then, we are going to use this knowledge to implement the server part of an example client-server application with Delphi. We are also going to look at the various components that are available to create network applications.
Building a Server Application in Delphi - Indy Servers (Page 4 of 5 )
We know from previous discussions that a server listens for client connections at a particular port and then responds to whatever requests are made by a client. Indy servers do exactly that and more. They set up a listener thread that is separate from the main application. This thread waits for connections from clients. When a client connects the server spawns a new thread to service that client. All the relevant events are executed within the context of that thread. So, what am I saying here? Basically, an indy server services many different clients simultaneously, in the same way that it would a single client.
Indy offers three types of servers:
TidTCPServer- See below.
TidUDPServer - Since UDP is connectionless, TIdUDPServer operates differently from TIdTCPServer. TIdUDPServer does not have any modes similar to TIdSimpleServer, but since UDP is connectionless, TIdUDPClient does have single use listening methods. TIdUDPServer when active creates a listening thread to listen for inbound UDP packets. For each UDP packet received, TIdUDPServer will fire the OnUDPRead event in the main thread, or in the context of the listening thread depending on the value of the ThreadedEvent property.
TidSimpleServer - TIdSimpleServer is for creating single use servers. TIdSimpleServer is intended to service a single connection at a time.
For the purposes of this article we are only going to discuss TidTCPServer:
As the drawing above shows, Indy servers are designed around threads and operate to some extent as Unix servers do. While Unix servers work directly with the stack with virtually no abstraction layers, indy does the opposite and distances the programmer from the stack. This is because indy uses a high level of abstraction and internally implements many details that can be automatic and transparent. This means that a lot of the code that you would have had to write is automatically done for you, simply by dropping a component on your Delphi form. While Unix forks a new process for each client connection, Indy simply creates a new thread for each new client. This gives indy all the advantages of processes and none of the disadvantages.
Because indy uses threads, you can connect hundreds or in some cases thousands of clients to your server application at a time. The belief that your system will stop functioning if you have hundreds of threads running at the simultaneously is not borne out by what I see on my system every day. If you press Control+Alt+Del and have a look at how many threads are currently running on your system without making a dent in your system's performance, you will be surprised.
Limits on number of threads
You will start to notice problems with your system when thread numbers reach around 900 or more. I've tested a custom chat application (created with Indy) with close to 990 clients connected and experienced problems on a Pentium III 900 MHz with 269 MB RAM. It is important to understand that the number of clients is not necessarily the same as concurrent threads. While each client is given an individual thread, a thread is only allocated while the client is connected. The problems associated with high volume server applications such as the chat server I mentioned before are addressed in Indy 10 by allowing other models in addition to threading of servers. Indy 10 is limited only by available memory to allocate sockets.