Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Visual CSharp .NET Programming (2002) [eng]

.pdf
Скачиваний:
58
Добавлен:
16.08.2013
Размер:
7.98 Mб
Скачать

return m_MsgIndex;

}

set {

m_MsgIndex = value;

}

}

Replying to a Message

In the interests of completeness, let's reply to a message selected by the user in the Get Messages ListBox. This time, however, instead of letting the user select a subject for the message, we'll automatically give it the subject 'REPLY'-so Figure 11.14 shows no room for the message reply subject.

Here's how we get the right MessageQueue in the msq array:

MessageQueue theQ = msq[this.MsgIndex];

Figure 11.14: The message sent in the box shown is automatically labeled REPLY.

Here's sending back the message with the REPLY subject line:

theQ.Send("You said, \"" + lstMessages.SelectedItem.ToString() + ",\"and I say: \"" + txtReply.Text + "\"", "REPLY");

Going back to the application that started this section, if you make sure that the queue that is joined is the one that was used to display messages in Figure 11.14, then when you click Retrieve, the reply to the original message will be displayed (Figure 11.15). The code for replying to the selected message is shown in Listing 11.8.

Figure 11.15: The Receive method takes the first message off the queue, regardless of what program sent it.

Listing 11.8: Replying to a Message

MessageQueue [] msq;

...

private void btnReply_Click(object sender, System.EventArgs e) { MessageQueue theQ = msq[this.MsgIndex];

theQ.Formatter = new BinaryMessageFormatter();

theQ.Send("You said, \"" + lstMessages.SelectedItem.ToString() + ",\"and I say: \"" + txtReply.Text + "\"", "REPLY");

txtReply.Text = String.Empty;

}

Messaging as Architecture

Of course, to really get power out of messaging, software needs to be sending messages and responding to them. (If the idea were to have a human select a MessageQueue, and then select a message, and then type out a response, we could use e-mail or instant messaging and save some programming time.)

I've put together a pair of small applications that simulate how messaging might be used as architecture to create a distributed application. Please bear in mind that these applications don't do anything much. In the real world, they might be performing extensive numerical calculations, crawling through text, or mimicking complex systems such as a tornado or the stock market. My simple applications simulate the parts of a more complex organization that might be constructed around message queuing.

One of my applications, which I've called 'Gigantic,' counts down from 100 to 1. The other application, dubbed 'Colossal,' counts up from 1 to 100. A target number (between 1 and 100) is supplied. The idea is that the application that gets to the target number first sends a message out saying that it is done. When the 'done' message is received, the other node stops working.

Gigantic, the (top-down) first node, serves as a kind of controller for the whole thing, since it sends out a message telling both nodes (itself and Colossal) to start. The start message includes the target number, as shown in Figure 11.16.

Figure 11.16: The initial program serves as a kind of controller, since it sends the message that starts the race.

Colossal, the (bottom-up) other node, is intended as the prototype for many distributed objects where something more complex than counting from 1 to 100 would be involved. The only action the user can take with respect to Colossal is to initialize it by clicking the Start button. The initialization code, shown in Listing 11.9, causes Colossal to go into standby mode, wait for a message, and display a Ready status indication (Figure 11.17).

Figure 11.17: After the other program has been initialized, it signals that it is asynchronously waiting in standby mode for the start message.

Listing 11.9: Initializing a 'Node' and Asynchronously Waiting to Peek

MessageQueue mq;

private void btnStartMe_Click(object sender, System.EventArgs e) { lblResults.Text = String.Empty;

string qpath = @".\Private$\Race"; if (!MessageQueue.Exists(qpath))

MessageQueue.Create (qpath); mq = new MessageQueue(qpath); mq.Purge();

mq.Formatter = new BinaryMessageFormatter();

mq.PeekCompleted += new PeekCompletedEventHandler(MyPeekCompleted);

lblResults.Text = "Status: Ready"; mq.BeginPeek();

// Can do stuff here while waiting for asynch event to complete

}

I picture the competition between Gigantic counting down and Colossal counting up as a kind of race, so I named the message queue Race. The code shown in Listing 11.9 uses the familiar syntax to create or join that queue.

Note The MessageQueue also is purged so that any old messages that may be sitting in it don't lead to erroneous results.

Next, the event handler for the asynchronous MyPeekCompleted event procedure is added, and the BeginPeek method is invoked to start an asynchronous peek operation with no timeout:

mq.PeekCompleted += new PeekCompletedEventHandler(MyPeekCompleted);

...

mq.BeginPeek();

If this asynchronous code looks familiar, it should: it is based on the same asynchronous design pattern shown used with web services in Chapter 3, 'Windows Uses Web Services, Too!,' and to perform FileStream operations in Chapter 10, "Working with Streams

and Files."

The code in the MyPeekCompleted event handler, which gets invoked when a peek completes, is really pretty simple (see Listing 11.10).

Listing 11.10: Responding to the 'Peeked' Message

MessageQueue mq;

...

public void MyPeekCompleted (Object source, PeekCompletedEventArgs ar){ System.Messaging.Message msg = mq.EndPeek (ar.AsyncResult);

if (msg.Label == "DONE"){ return;

}

if (msg.Label == "START"){

CountUp (Convert.ToInt32(msg.Body)); return;

}

}

public void CountUp (int target){ this.lblResults.Text = "START: " + this.Text; if (target < 0)

target = 0; if (target > 100)

target = 100; int i = 0;

while (i <= target){

this.lblResults.Text += " " + i.ToString(); if (i == target){

mq.Send (this.Text, "DONE"); this.lblResults.Text += " Status: Not Ready";

break;

}

i++;

}

return;

}

A System.Messaging.Message object is created using the mq EndPeek event, with the AsyncResult object passed into the event handler:

System.Messaging.Message msg = mq.EndPeek (ar.AsyncResult);

Note that, in this code, I've used the fact that I know what MessageQueue is involved (and it is available as a class-level variable). The source parameter of the event procedure is the object that fired the event-e.g., the MessageQueue causing it-so source can be cast to MessageQueue and used like so:

public void MyPeekCompleted (Object source, PeekCompletedEventArgs ar){ MessageQueue mq = (MessageQueue) source;

...

However the MessageQueue is obtained, its purpose is to use its EndPeek method to obtain a look at the message at the front of the queue. If the message is labeled "DONE", then work ceases. If the message is labeled "START", then the count-up method is called, using the number passed in the body of the message:

if (msg.Label == "START"){

CountUp (Convert.ToInt32(msg.Body));

...

In the CountUp method, when the target is reached, a DONE message is sent:

mq.Send (this.Text, "DONE");

and the status message is put back to 'Not Ready':

this.lblResults.Text += " Status: Not Ready";

Moving back to the Gigantic controller node, you can see from Listing 11.11 that its initialization process is pretty much the same as a normal node, except that it also sends a START message using the number input in a TextBox:

mq.Send (txtNum.Text, "START");

In addition, the controller node is asynchronously waiting for a Receive (rather than a Peek), the difference being that Receive will delete the message from the queue, and Peek won't.

Listing 11.11: Starting the 'Race' and Waiting for an Asynchronous Receive

MessageQueue mq;

private void btnStart_Click(object sender, System.EventArgs e) {

lblResults.Text = String.Empty; string qpath = @".\Private$\Race"; if (!MessageQueue.Exists(qpath))

MessageQueue.Create (qpath); mq = new MessageQueue(qpath); mq.Purge();

mq.Formatter = new BinaryMessageFormatter(); mq.Send (txtNum.Text, "START"); mq.ReceiveCompleted += new

ReceiveCompletedEventHandler(MyReceiveCompleted);

mq.BeginReceive();

// Can do stuff here while waiting for asynch event to complete

}

Gigantic responds to messages in the MyReceiveCompleted event handler in a similar way to the Colossal node (see Listing 11.12). When a DONE message is received, a display is created that uses the text of the message to display the "winner," the queue is purged, and work stops. When the START message is received, the CountDown method is called.

Listing 11.12: Responding to the Received Messages

MessageQueue mq;

...

public void MyReceiveCompleted (Object source, ReceiveCompletedEventArgs ar){

System.Messaging.Message msg = mq.EndReceive (ar.AsyncResult); if (msg.Label == "DONE"){

this.lblResults.Text += " " + msg.Body.ToString() + " is done first.";

mq.Purge();

return;

}

if (msg.Label == "START"){

CountDown (Convert.ToInt32(msg.Body));

}

}

public void CountDown (int target){ this.lblResults.Text = "START: " + this.Text; if (target < 0)

target = 0; if (target > 100)

target = 100; int i = 100;

// Delay it a little, since it gets the message first System.Threading.Thread.Sleep(200);

while (i >= target){

this.lblResults.Text += " " + i.ToString(); if (i == target){

mq.Send (this.Text, "DONE"); mq.BeginReceive();

break;

}

i--;

}

}

Note I've added a short delay within the CountDown method by putting the thread to sleep for 0.2 of a second-System.Threading.Thread.Sleep(200)-because Gigantic, as the originator of the START message, seemed to have an unfair head start.

Within the CountDown method, when the target is reached, a DONE message is sent. In addition, the BeginReceive method is invoked another time-so that the DONE message can be responded to:

if (i == target){

mq.Send (this.Text, "DONE"); mq.BeginReceive();

break;

}

To experiment with Gigantic and Colossal, run both programs. Click Colossal's Start button to initialize it. When its display indicates that it is ready, enter a number in Gigantic and click Start. The display will indicate which node completed first, as shown in Figure 11.18.

Figure 11.18: Each program sends a message when it is done with its task; the first DONE message is displayed.

Messaging Architectures and Non-Determinism

You should be aware that application architectures that use message queues have some issues with non-determinism. There's really no way to say exactly when a given node will receive a given message, or even in what order nodes will receive a particular message. (This may depend to some degree on network loads.) So your applications should not depend on the timing or order of message delivery.

In addition, just like in real life, it is possible that messages may get dropped or lost. In a rigorous architecture that depends on message queues, you should plan for this and use a system that relies on acknowledgments and redundant messaging.

Conclusion

Although the logic of applications built around sending and receiving messages can be confusing, using .NET, message queues and messaging do not demand much in the way of programmatic infrastructure. The technology creates powerful opportunities for distributed and peer-to-peer applications. One can easily imagine scenarios in which applications are objects that interoperate solely using a messaging infrastructure.

This chapter touched very briefly on messages formatted using XML. Chapter 12, 'Working with XML and ADO.NET,' explores the exciting and important world of XML-and shows you how to effectively use data with your applications.

Chapter 12: Working with XML and ADO.NET

Overview

Understanding and serializing XML

The XmlSchema class and XSD schemas

XmlTextReader and XmlTextWriter classes

The XML Document Object Model (DOM)

XSL transformations (XSLT)

Understanding ADO.NET and databases

Connection strings

Managed providers and data components

Working with DataSets

Under the hood, the .NET Framework, .NET languages, and Visual Studio rely on XML as the underlying facilitator of interoperability, configuration, and much more. In large part, the developer has no great need to be concerned with these uses of XML—you certainly don’t need to know how to program XML to run the Visual Studio IDE—except that if the .NET programming structure finds XML the best tool to use for application integration, why not use it in your own applications as well?

Note For more information about many of the ways XML is used within the .NET Framework and development environment, look up “XML in Visual Studio” in online help.

A great deal of XML and XML-related capabilities are built into the .NET Framework. There are literally hundreds of classes you can use to generate and manipulate XML in C# .NET. This chapter provides an overview of some of the ways you can work with XML using

C# .NET. By starting the chapter in this book that treats data with a discussion of XML, I am emphasizing the fact that in today’s world it is an intelligent move to regard data from an XML-centric viewpoint whenever this is a plausible approach.

In the long run, you can’t do anything very sophisticated or useful without the ability to save and retrieve data between program sessions. While there are a variety of ways you could go about doing this—for example, reading and writing to files as explained in Chapter 10, “Working with Streams and Files”—for many developers today, input/output essentially means working with a database.

In effect, the great majority of real-world programs are designed to query databases to populate the programs’ objects. Programs that perform jobs such as managing accounts receivable or hotel reservations must also be able to save their work and pick up where they left off. In addition, users need to be able to query stored data and to update it.

This chapter is not concerned with programming databases—and, indeed, it would take at least an entire book to do justice to the topic. As a general matter of contemporary application architecture, a connectivity layer usually is interposed between an application and a database. The chapter’s primary focus is the tools available to help you create the database connectivity layer in .NET, and how to use XML to facilitate interoperability.

Understanding XML

Of all the tools, technologies, and languages that have emerged in the past few years, none has had a greater impact on the interoperability of applications and data than XML (short for Extensible Markup Language). XML is deceptively simple. It’s easy to understand, it can be read by both humans and machines, and constructing XML documents and schemas is easy. This simplicity belies a great deal of power and sidesteps the occasional complexity of

using XML.

As you probably know, HTML and XML are both markup languages, meaning that they consist of tags that describe content. That’s about where the similarity ends. HTML tags are fixed in nature (at least in each version of HTML) and used to describe the elements that make up an HTML page. HTML elements are usually visual. In contrast, XML tags are custom in nature and are used to describe data.

In other words, an HTML tag, such as <h1></h1>, is used to describe the appearance of the contents within the tag. (The <h1> tag means that the tag content is a level 1 heading.) The meaning of the <h1> tag is fixed, and it means the same thing to everybody.

On the other hand, an XML tag such as <phone_num></phone_num> identifies the contents as a phone number. XML tags can be used for anything that you might logically use when structuring data. You can invent your own XML tags and use them to mark data as you’d like. In other words, the meaning of an XML tag is not fixed for all users the way an HTML tag is. For example, in your XML a <name> tag might identify a first name, and in my XML the <name> tag might mean first, middle, and last names.

For example, in the last novel that Charles Dickens completed, Hard Times, the aptly-named teacher Thomas Gradgrind—only interested in realities, and a man of “facts and calculations”— asks his student Sissy Jupe, whom he refers to as “girl number twenty,” to define a horse. Sissy, put on the spot, is unable to produce a definition, so Gradgrind turns to one of his pet students, a boy named Bitzer:

“Bitzer,” said Thomas Gradgrind. “Your definition of a horse.”

“Quadruped. Graminivorous. Forty teeth, namely twenty-four grinders, four eye-teeth, and twelve incisive. Sheds coat in the spring; in marshy countries, sheds hoofs, too. Hoofs hard, but requiring to be shod with iron. Age known by marks in mouth.” Thus (and much more) Bitzer.

“Now girl number twenty,” said Mr Gradgrind. “You know what a horse is.”

Bitzer’s definition of a horse is actually a kind of pidgin XML. If rendered into XML, it might begin something like this:

<?xml version="1.0"?> <horse>

<legs> Quadruped

</legs>

<diet> Graminivorous

</diet>

<teeth>

<totalnumber>

40

</totalnumber>

<grinders>

24

</grinders> <eye-teeth>

4 </eye-teeth> <incisive>

12

</incisive>

</teeth>

...

</horse>

The point of this XML description of a horse is that almost anything can be described using XML. However, in the real world there is little use for data description—such as XML— without the ability to communicate. So a more realistic use of XML in connection with a horse would not be a description of where the horse fits into a Linnaean genus (which is essentially what Bitzer’s description is about: what kind of animal is this?) but rather one that has to do with horse transactions and affairs for specific horses. This XML description might include elements such as <name>, <sire>, <dame>, <weight>, <cost>, and so on. (And sure, while we’re at it, why not a <teeth> section?)

Business communities create meta-XML structures specific to that community. These structures, called schemas—in the same spirit that the tabular structure of relational databases are also called schemas (and convertible to and from database schemas)—are used to standardize XML communications. As long as you mark your data as elements following a specific schema, all participants will know what you are talking about.

Whether specified by an industry group or created on an ad hoc basis, schemas are themselves written in XML and saved as XSD files. (DTD, or Document Type Definition, and XDR, a proprietary Microsoft schema specification, are both older schema definition formats, replaced in .NET by XSD.) An example of an XSD schema is shown later in this chapter in Listing 12.1.

Note XSD is a standard specified by the World Wide Web Consortium (W3C; www.w3c.org). In keeping with the role of the W3C, it is vendor-neutral and not controlled by any one company.