Session 13

TCP/IP networking using the Indy Components. (Chat client/server)

Note: These projects are using 3rd party networking components (Indy) which are not supported with the free Turbo C++ 2006 version. In the examples on this page, the client and server were built with RAD Studio 2010, which includes the Indy components in the IDE. Some slight modifications were made to the code to support XE 3 and later versions of RAD Studio. You can download the project for XE 3 here. (It should work in XE 4 without modifications. I'm not sure about XE or XE 2, though.)

Session #13

  1. Overview of Internet Direct (Indy) Components

    Rather than write a book on these components, I'm just going to point to some references online. Realize that most of the code samples are written in Delphi, but you should be able to follow along. Also, some of the examples are a few years old, but that isn't a problem since socket programming hasn't changed much over the years.

    There are over 100 components included with Indy and they are arranged among 6 categories:

    All of the components give you a very nice object-oriented way of dealing with networking. The most basic components are the TCP client and server components which allow you to do almost anything. There are higher level components (such as FTP, SMTP and HTTP) which provide concrete implementations for those protocols. I'm going to demonstrate a very simple client/server chat program using the lower-level TIdTCPClient and TIdTCPServer components. These will be used to implement the client and server, respectively.

    There will be a single server that will service an unlimited number of clients. In the screen shots below, there is one server and 3 clients. This demo is intentionally kept very simple so as to not confuse you with a lot of non-network-related stuff. However, given this simple demo, you could easily extend it into a very usable chat system.

    Server
    The server lets you start the server on any available port. All messages from clients will be shown in the window. If the server wants to send a message to all of the clients, it can enter text and then broadcast it. This is really about all there is to the server right now. The output is pretty sparse, but it could very easily be enhanced. Adding information about which client sent which message could be displayed, as well as a list of all connected clients. These are all trivial to add to this server (and are exercises for the student). Since the server doesn't do much, it's actually easier to code up than the clients (which, in the real world, would provide all kinds of features for the users).

    Here are some screen shots from 3 clients that are all connected and chatting, albeit, not saying much. Notice that the chat system supports Unicode characters (for non-English languages). I'll talk about this a little at the end. The images show English, Spanish, and Russian (for Prof. Volper!), although all languages are supported.

    Client #1Client #2Client #3

    Project files (RAD Studio 2010) (XE 3) - to build it yourself. Includes both client and server projects. This is from RAD Studio 2010 and won't build with the free Turbo C++ 2006.


  2. The Protocol

    Because this is a very simple demonstration, the protocol for the chat protocol is equally simple. In fact, there are only two types of messages that can be sent between the client and server.

    Client messages to the server: (Client --> Server)

    1. SEND - This is the primary message. When a client wants to send a message to all of the other clients, it sends a SEND command to the server. The format is:
      	SEND message_text
      
      Examples:
      	SEND Hello
      	SEND I am not a great fool, so I can clearly not choose the wine in front of you.
      	SEND ¿Qué hora es?
      
      The server takes the message and broadcasts it to all connected clients.

    2. CMD - This is how the client communicates with the server. This command is not meant to be broadcast to other clients.
      	CMD command
      
      Example:
      	CMD QUIT
      
      Currently, only the QUIT command is supported and even that isn't really needed.

    Server messages to the clients: (Server --> Client)

    1. MESG - This is how the server broadcasts messages to the clients.
      	MESG CLIENT_ID message_text
      
      Examples:
      	MESG 127.0.0:1234 Hello
      	MESG Server I am not a great fool, so I can clearly not choose the wine in front of you.
      	MESG Nigel ¿Qué hora es?
      
      The CLIENT_ID is sent so that when the clients receive the message they know which client sent it. This string can be anything that uniquely identifies the sender. In this demo, the CLIENT_ID is simply the IP address and port of the sender. The server uses a CLIENT_ID of Server for simplicity.


  3. The Server

    The server component is named TIdTCPServer and should be the fourth component under the Indy Servers section. Depending on the exact version of Indy you are using, this position could change. You can always search for it and find it that way.

    The server really doesn't do much besides routing the messages between the clients. It doesn't have to be visual. However, developing a program without any visual feedback (even if only text-based) is impossible. When I create software, I always have lots of output during the development stage. How else can you tell if things are working correctly? For a chat server, it is nice to be able to see the messages that are going between the clients. (Yes, in a real-world situation this could be a privacy issue, but, on the Internet, there is no privacy anyway. Get used to it.)

    Briefly, this is how the server works:

    That's pretty much it in a nutshell. Of course, there are lots of details handled by the component, so you won't usually have to deal with them. For example, each client connection is in its own thread. The list of clients that the server maintains is actually a TThreadList, which is simply a (thread-safe) list of threads (clients). Look at the source code to get a feel for how this works. Pay close attention to the two events mentioned (OnConnect and OnExecute).


  4. The Client

    The client component is named TIdTCPClient and should be the first component under the Indy Clients section.

    Briefly, this is how the client works:

    That's it in a nutshell. However, there is one thing that makes this procedure problematic. That is the infinite loop that is waiting for messages. If you've read the documents about the Indy Components, you'll remember that they use blocking functions. In other words, while the client is waiting for messages from the server (via the OnConnected event handler which never returns), the client will seem to "lock up". This isn't good. There are several ways to deal with this, such as dealing with the messages in a background thread. For a very robust and full-featured chat client, that would be a good way to go. However, for this simple chat client, there is a component which solves this blocking stuff for us. It's called the AntiFreeze component (TIdAntiFreeze). You'll find this component in the Indy Misc category in the component palette. Simply adding this component to your form will allow it to respond to other Windows messages while the OnConnected event handler is blocked. It's a nifty component that works great.


  5. Unicode for Non-English Languages

    Well, some of you have heard this before: I lost 2 hours of my life again... Unicode! Sure, it's easy once you figure it out (or Google for it.) I just assumed that everything in Indy was already being done in Unicode. But, it wasn't (which is correct and by design). Once I figured that out and found the one-liner that I needed, all was well again. Since this is a networked chat program, I thought it would be nice to support languages other than just English, especially when it wasn't hard to do. You just have to set the encoding to something that will handle other code pages:

    In the server's OnConnect event handler:

    AContext->Connection->IOHandler->DefStringEncoding = TIdTextEncoding::UTF8;	
    
    In the client's OnConnect event handler:
    tcpClient->IOHandler->DefStringEncoding = TIdTextEncoding::UTF8;
    

    You'll see an #ifdef in the code where you can turn this on and off. I left it in because it shows how little actually had to change and you might want to experiment with it. You wouldn't know how much time I wasted on that Unicode stuff by looking at that small amount of code. Well, now you don't have to do it! Oh, by the way... You're Welcome!.

    Newsgroup article that explained what I was missing. BTW, the guy that responded (Remy Lebeau, a.k.a. Gambit) works on the project and has been answering newsgroup questions for probably 10 years or more. He's helped me on some sticky issues in the past.

Exercises

As you can see, this is a very bare-bones chat client/server program. However, it shows the fundamentals of using two of the Indy components. Other things that you can experiment with include:

  1. Giving names to the clients. Currently, the IP:Port (e.g. 127.0.0.1:1234) of the client is displayed. This was so you can distinguish between the clients that were chatting. In a real chat program, when a client connected they would be able to choose a name (or handle) that they want to be known as. The server would have to make sure that these names were unique among the connected clients. You could do this by adding another command type for when the client connects. Something like:
    HELLO Name	
    
    that would be sent to the server upon connecting. The server would welcome the client (with some return string) and then broadcast the client's name to all of the other connected clients.

  2. Related to above, have the client display a list of chat users. Right now, when a client connects or disconnects, the other clients are not notified about the new client. New behavior: Each time a new client connects or disconnects, the server would have to send the updated list to each client. The list would be displayed at all times. You would want to update the protocol with a new command from the server, maybe something like:
    USERS user1 user2 user3 user4 ...	
    
    This command contains the names of all connected clients.

  3. Have each client's text be in a different color/font. Right now, the text that is sent is displayed in BLUE and the incoming text is displayed in RED. Allow the client to configure this.

  4. Allow client's to send private messages to selected users. Right now, any message sent by a client is sent to all other clients. Let the client select one (or more) other clients to send a message to. The client would have to display a list of connected clients so that one could be select.

  5. Code cleanup - You'll notice that there's a lot of duplicated code/functionality between the client and server programs. They are separate projects, but both need to do string parsing and message passing. This code could easily be factored out into a shared library or something. It's not a big deal with this very simple demo, but you can see that if you were to make both the client and server more robust and full-featured, this code duplication would get worse. There are also many places where errors could occur in the programs, yet there is very little error checking. Such is the case with simple demo programs.