In the first part of the article we took a brief look at the methods used to exchange information between different parts of the application. This part will deal with development of the simple chat system for Intranets.
You will find an example (very primitive but complete) enclosed with MsgConnect application. It's called SendNote. As the name says, this application lets you send the text notes to the recipient. You can use this example as a reference material for this text.
Let's start from the beginning. We decided to use MsgConnect as it is boasts to be cross-platform and message-oriented. To deliver the message we need 3 things -
- component to send the message
- component to deliver the message
- component to receive the message
Unlike most ways of data exchange, where sender and receiver parts are different components, MsgConnect uses the same component set for sending and receiving messages.
As described in the "Getting Started" section of MsgConnect documentation you need just 3 components - Messenger, Transport and Queue.
Messenger is the core of MsgConnect -it does all message processing. Like WindowsR messaging subsystem, which is thread-based, Messenger class was designed to be the only Messenger component in the thread. In other words, only one instance of Messenger class must be created for each thread. You can create more, but this (a) doesn't make much sense and (b) complicates logic of your application.
Transport is used to deliver the message to the recipient. There are currently 4 "remotable" transports, based on Memory-Mapped Files, plain TCP, HTTP and UDP. There are plans for other transports (namely IrDA and Bluetooth) later. The transport takes the message from the Messenger, packs it (also optionally applies encryption, compression and integrity checking) and transfers it to the recipient. The recipient application must also have an active instance of the transport of the same class. This means that if you send a message using Socket transport, it is necessary to have Socket transport active on the other (recipient) side.
Queue is used to dispatch incoming messages in a handy way. It can be treated as a mailbox in the post office. As you send the letter to the P.O. Box, you send the binary message to the queue. Probably mailbox would be a better name for the queue object, but we won't change the name now. Each messenger can have as many queues as you like. The queues are identified by symbolic name. There are certain limitations on the queue names, and they are described in documentation.
How does the messenger know where to send a message? When you ask Messenger to send something, you give it the address of the recipient. The address contains 3 parts:
- Transport identifier. This can be the name of the transport component or transport class name as described in documentation
- Destination address. This address is specific to each transport.
- Destination queue name.
And the last important question - does MsgConnect guarantee delivery and the order of messages? Well, distributed systems are different from single-process in the way when and how these systems are started and stopped and what external links they maintain. In other words, it is not possible to guarantee that the remote system will receive and process the message. This is beyond MsgConnect control. But still delivery is guaranteed in the following way: if you use one of SendMessage*() methods (and not PostMessage()) to send a message, you will get some notification about success or failure of delivery.
As for the order of delivery - due to bi-directional nature of MsgConnect (if the connection exists, it can be used to send messages in each direction completely independently of each other) there exists a possibility that 2 messages scheduled for delivery to one recipient at the same time will be delivered not in the order in which they were put to the queue. Such situation can happen only when the same transport is used as both client and server (i.e. initiates and accepts connections at the same time). This is called p2p transport mode for SocketTransport class. Solution is easy, but we will deal with this question in the next part of the article - right now we don't need this.
Now, when we know the basics, let's start building our first MsgConnect-powered application, simple chat.
First we create Messenger object, Queue object and SocketTransport object. In different development environments this can be done either in code or by placing a component in Designer.
Then we need to link Queue and SocketTransport objects to Messenger by setting their Messenger property to the added instance of Messenger class. In languages which don't support properties, this is done by calling SetMessenger() method
Set Transport properties (as shown in the sample projects): MessengerPort (14583 in our sample), TransportMode (p2p in our case because our chat sends and receives messages via the same transport) and of course Active property. MessengerPort is where the SocketTransport waits for incoming connections and TransportMode defines how the transport behaves when it is activated.
Messages are received by application either (a) using OnUnhandledMessage event of the Queue class or (b) using MessageHandler objects. Use of MessageHandler objects is a handy way to put handler for different messages in different methods. For our simple chat with one message we use OnUnhandledMessage event. So we need to create a handler method for OnUnhandledMessage event. This method will be called when the message arrives.
As said before, MsgConnect emulates Windows messaging subsystem and the Messenger object is used within a thread. We send a message using one of PostMessage() or SendMessage*() methods. Remember to call this methods within the same thread in which Messenger object was created (Java code base of MsgConnect doesn't have this restriction).
To send a message to recipient in our chat application, the user must enter the text, sender name (it is not used to dispatch messages and is just informational) and recipient address. Here comes the tricky part: in our chat application we let the user enter computer name or IP address. They are later used without translation as a message recipient address. In more complex applications one will lookup the user names in the list or do some other name->address translation (we will use this when doing Instant Messenger application).
The next thing to do is to develop a protocol. Protocol is the sequence of calls or messages, used to exchange information between two parties of connection. In our simple chat we use a very primitive protocol that consists of just one message - all the data being sent is included in this message.
So we have something to send. How do we actually send the message? Remember that unlike Windows messaging, MsgConnect can transfer binary data sized up to 2Gb.
To send a message we use an instance of Message class (record in C++ and Delphi). We fill integer properties - Param1, Param2 and MsgCode. MsgCode has the same meaning as in Windows messaging; it is used to find the right handler in array of MessageHandlers within a destination Queue.
Then we need to attach our application data. We are sending the name of the sender and the body of the message. We pack them to one string and attach to Message class.
A bit more lyrics now. MsgConnect can send binary data within a message sent using SendMessage*() methods in one or both directions. In other words, you can attach some data; recipient will process them and attach the resulting binary data to the reply. Remember that in most cases you send the data only in one direction so by default BinDataType property of the Message class/record is bdtConst. If message recipient should replace the associated data with it's own data and send it back, you need to set BinDataType property to bdtVar. bdtVar tells the Messenger to put the data together with reply when it is packed.
Ok, we have created the message and we are ready to send it. Which of PostMessage()/SendMessage*() methods should we use? This depends on the way we build our application.
The simplest way is of course PostMessage(). It puts a message to the message queue and returns immediately. In this case the message is sent in one direction only and the message result is not returned. However there's one drawback in this approach - your application never knows whether the message was actually sent. This is like a pager - you can send a message but unless you ask your partner to call immediately you don't know whether the message was delivered. And even worse - if the partner doesn't call you, this doesn't mean he didn't receive a message.
So SendMessage*() looks like a better option. There are 3 similar SendMessage*() functions - SendMesage(), SendMessageCallback(), SendMessageTimeout(). They all send a message and try to wait for result, but the details of their behavior differ.
SendMessage() doesn't return until the message result is returned or error happens. During the call to this method windows messages (where applicable) are not processed and your user interface is not updated (unless you send messages from another thread, but this is beyond this our current topic).
SendMessageTimeout() is similar to SendMessage() with the difference that SendMessageTimeout() returns unconditionally either when reply is received or when timeout expires. Actually, SendMessage is implemented via SendMessageTimeout().
While SendMessageTimeout() is useful in certain cases, we need some other solution. This is SendMessageCallback() method. It sends a message and returns immediately. The application must call Messenger's DispatchMessages() method then.
Like in Windows the application must call DispatchMessages() method to dispatch the received messages and call proper handlers. DispatchMessages() is used to receive both original messages and replies. When reply is received by the application, next call to DispatchMessages will call the callback function.
Such scheme requires that the application (a) calls DispatchMessages() from time to time and (b) continues it's operations after some timeout expires. This requirement causes the use of timer. The timer calls a function that is used to call DispatchMessages() and also count the time elapsed since the message was sent.
In our simple chat we use a timer as mentioned above. The callback function receives notification about the delivery and puts a record to the history memo box.
Now we have all parts of the application described. Nothing will help better than a sample code, so you can see what we have ended with in SendNote sample.