Ruediger R. Asche
Microsoft Developer Network Technology Group
Created: July 29, 1994
Revised: June 1, 1995 (redesigned class definitions; incorporated information on MFC sockets)
Click to open or copy the files in the CommChat sample application for this technical article.
This article describes the architecture of COMMCHAT.EXE, a Microsoft® Foundation Class Library (MFC) application that utilizes those CCommunication and CProtocol objects.
This article is first in a series of technical articles that explore network programming with Visual C++™ and the Microsoft® Foundation Class Library (MFC). The series consists of the following articles:
"Communication with Class" (introduction and description of the CommChat sample)
"Garden Hoses at Work" (named pipes)
"Power Outlets in Action: Windows Sockets" (Windows® Sockets)
"Aristocratic Communication: NetBIOS" (NetBIOS)
"Plugs and Jacks: Network Interfaces Compared" (summary)
The CommChat sample application illustrates the concepts and implements the C++ objects discussed in these articles.
Communication between computers seems to be the final frontier of personal computing. Although we know how to do several things at the same time (or practically at the same time) on one computer or harness all kinds of sophisticated hardware to run with a PC, the entire area of communicating between computers is incredibly convoluted, and there is little help for incorporating networking into an application.
Recent technologies, such as Microsoft OLE and remote procedure calls (RPC), provide mechanisms that will make remote computing entirely transparent to application programmers; however, it will still be necessary at times to address communication between distinct computers on a lower level of abstraction than that provided by those mechanisms. This article is one of a series that provides a fairly pragmatic approach to networking. I will sort out the different levels of abstraction and provide MFC-compatible C++ classes to encapsulate communication.
This article first introduces networking from several points of view, then describes the approach taken to weave communication strategies into C++ objects, and eventually describes COMMCHAT.EXE, an MFC application that utilizes those objects.
It is my hope that after having read this series of articles, you'll have a better understanding of the nature of network programming and perhaps take the classes I provide and put them into your applications to make them network-aware.
A short note about terminology is in order here: In this article series, I frequently use phrases such as "communication between two machines." Although we do deal with communications between physically different computers in this article, communication strategies are generally more generic. With named pipes as well as sockets and NetBIOS, communications can be established and maintained between processes that are executed between different machines as well as processes that are executed on the same computer.
Any code that tries to use communication objects (in fact, any code that does anything at all) must be associated with a process. Thus, a process can be seen as the smallest unit that a communication object can address. If you think about it for a while, you'll find that the term "another machine" is pretty shady. After all, in the era of multitasking operating systems, a given machine can handle several unrelated things concurrently. Therefore, if I say "communication with another machine," don't I sound like the jerk who yells, "I want to talk to the party?" at a cocktail party? You bet.
To be precise, I would have to say "communications between different processes that may or may not reside on different machines." But that sounds too awkward, so bear with me and my incorrectness when I use the term "different machines" to indicate "different processes that may or may not be executed on different machines." After all, with the increasing degree of connectivity between machines, the terms "process" and "machine" are becoming more and more blurred and interchangeable.
On the standard type of PC, there are only three ways to physically connect two computers to pass information—by connecting them with either a serial cable or a parallel cable or via a network connection. And there are only two ways for two computers to communicate: via passing data back and forth or via calling each other's routines—though the latter strategy can be viewed as nothing but a variation of the first. The latter strategy would involve protocols such as remote procedure calls or, on an even higher level, OLE.
In this article series, we will look only at data-exchange strategies, and we will look only at networks (thus, no serial or parallel communication, although the class hierarchy that I devised is flexible enough to incorporate those strategies).
I have always admired engineers who understand networking. They juggle acronyms like crazy whenever they open their mouths—there is TCP/IP, there is NFS, there is NDIS, XNS, NetBIOS, NetDDE™, LMAPI, SMB, IPX, and so on. And miraculously, they know how to make a PC exchange data with a UNIX® machine and how to pull huge amounts of data from a mainframe computer while also determining how to route data in a network most efficiently. This is pretty amazing, considering the fact that in a network all there is between the computers is a cable.
The secret to understanding networking is to think of networking in abstraction layers. (I am actually very far from understanding networks—to be honest, this series of articles documents my learning of network technology in much the same way that Nigel Thompson's series "OLE for Idiots" documented his learning about OLE.) There is a very famous diagram called the ISO/OSI specification (see "OSI Reference Model" in Chapter 15 of the Windows NT Resource Kit, Resource Guide [Development Library, Product Documentation, SDKs, Resource Kits, Windows Resource Kits]). This diagram tries to define networking in terms of abstraction layers, with the highest level dealing with users and logical connections and the lowest level dealing with physical connections. In this article series, we will roughly work our way down from the higher levels to the lower ones with some detours on our way.
Each of the network application programming interfaces (APIs) that we'll examine will in some way reflect a network communication strategy or, more precisely, a layer somewhere in between the hardware (which views networking roughly as a flow of electrical current through a metal cable) and the user (who views networking as a collection of machines that miraculously know about each other and can talk to each other without getting confused). Most APIs do not easily fit into the diagram (in other words, they cover functionalities that span layers in the abstraction hierarchy). The higher in this hierarchy you work, the less dependent you generally are on particular hardware, but the lower you work, the more flexible you are. So you win some and you lose some, depending on where you are.
Historically (that is, back in the days where there was no such thing as communication provided by an operating system), all APIs that dealt with interactions with machines on all levels were provided through extension libraries. For example, the Microsoft LAN Manager API contained functions that dealt with the organization of the network (for example, enumerating machines on a network, connecting logical drives, or accessing remote printers), as well as all functions that explicitly sent data back and forth between machines (such as creating and accessing named pipes or mail slots).
Back then, a network was a pretty monolithic gizmo; that is, you bought the entire packet, which basically tied you to specific network protocols, as well as communication mechanisms, and a specific view of the world (that is, a specific view of how machines are organized in a network).
Things have come a long way since then. The Win32® API defines certain communication strategies (such as named pipes, sockets, or RPC) that have to be supported by all implementations, regardless of what network software, hardware, or protocols are installed. This requirement at times causes severe headaches in developers; for example, named pipes (which were originally defined as part of the Microsoft LAN Manager software) relied on a specific intermediate protocol called SMB (or Service Message Blocks), which not all network vendors adopted. In order to make it possible for computers to communicate with each other even if they were connected with each other in a non-LAN Manager network, named pipe functionality had to be provided by different means.
Although the distinction between what functionality comes from the network software and what comes from the operating system tends to become more and more blurred, I would like to define the network-specific APIs as those API sets that deal with logical connections. These APIs allow you to programmatically log on or off the network, add a connection to a network resource, or manage remote print jobs. In other words, you would use these APIs mainly to implement something like a command shell or network browsing dialogs (which we will do in a later article) or the like.
In the case of the Microsoft LAN Manager, this API is basically implemented through a set of functions whose names are preceded by either the prefix WNet or Net.
If you do need to use the LAN Manager API, here is a word of caution: A certain machine may not be hooked up to a LAN Manager, although it runs in a network. There are several different models of networks out there on the market, with their major distinctions being how the machines are visible on the net. The Microsoft LAN Manager has the notion of a hierarchical trusted domain model with one dedicated server acting as the domain controller (a machine that monitors the users on the domain and keeps track of resource usage), but any number of other servers being present in the same domain. Other network systems have a totally different notion of how the network is organized. Thus, if the primary network on your machine is the Microsoft LAN Manager, you will be able to use the LAN Manager API, whereas if your primary network is, say, the Novell® NetWare® software, you may not be able to use that API, although you will still be able to, for example, communicate to a machine on the LAN Manager network via named pipes. (The architecture that makes this possible is one case in point of the melting pot that makes networking so much fun—or so painful, depending on your point of view.)
CommChat, the sample application that accompanies the articles in this series, may look a little bit disappointing to you because it looks pretty much like a number of applications that you already know. In its mature version, it will be a mixture of CHAT.EXE, an application that is being shipped with Microsoft Windows and Windows NT™ and that is by default entered into the Accessories program group, and TERMINAL.EXE, a terminal emulator, with a few fortifications.
In fact, right before I finished this article, I discovered that chat applications seem to be the "Hello, World" type of application that everybody under the sun starts out with when writing network applications. (See, for example, Ray Patch and Alok Sinha's article, "Designing the Client Portion of a LAN Manager Client Application," in the Microsoft Systems Journal, May/June, 1991.)
CommChat first lets you select a communication type to start out from. (The communication types that I provide are the three interfaces: named pipes, NetBIOS, and sockets.) Then you can initiate either a chat conversation or a file transfer.
So why reinvent the wheel? Isn't there a more efficient use of company money than to do something that has already been done?
Usually there is; but the argument that saves me here is that COMMCHAT.EXE is merely a front-end that demonstrates and tests the underlying class hierarchy. My goal was to define a general communication base class, CCommunication, and then successively implement derived classes for all of the possible communication techniques, such as named pipes, NetDDE, serial communications, sockets, NetBIOS, and so forth. We can then plug each of these classes into CommChat and test the functionality of each class. You can study the way CommChat uses the communication classes to incorporate those classes into your own application.
I implement three derived classes: a named pipe class (as discussed in the article "Garden Hoses at Work"), a class based on NetBIOS (as discussed in "Aristocratic Communication: NetBIOS"), and a class that is provided by MFC (as discussed in "Power Outlets in Action: Windows Sockets").
The base class from which we will derive our communication classes—CClientCommunication—is itself derived from CFile. Treating communications like files is a fairly common practice; for example, the Windows NT architecture implements named pipes and serial communications as files, allowing applications to open and use a serial port or a named pipe using the Win32 file access API calls. After all, both files and communication objects can be viewed as "things to which you write or from which you read data," right?
The CClientCommunication object and its derivatives support the following subset of the CFile class member functions: Open, Close, Read and Write. There is one class derived from CClientCommunication, which is CServerCommunication. The added functionality of CServerCommunication over CClientCommunication consists of three member functions: AwaitCommunicationAttempt, CancelCommunicationAttempt, and Duplicate. Note that both CClientCommunication and CServerCommunication are virtual base classes.
The reason for this derived class is that almost all types of communication take the form of client-server interaction; that is, when one process (the "server") is ready to establish a communication, it merely waits for another process (a "client") to explicitly ask the server to open a communication. A server application creates and uses instances of a class derived from CServerCommunication, whereas a client process uses objects of a class derived from CClientCommunication. The AwaitCommunicationAttempt member puts an object on the server side in a state in which the server can respond to a client's request to open up a communication. This state is frequently referred to as a "listening" state. CancelCommunicationAttempt is called to puts a server object out of the listening state.
Don't be too confused about the server-client distinction. In most cases, the distinction between the client and the server is meaningless as soon as a communication is established; both sides are generally equal. The only difference between the client and the server is that the client asks for the communication and the server is ready to be asked. As in any cocktail party, each participant is potentially both a client and a server, and the distinction is valuable only in getting a conversation going.
The communication objects are designed to allow data to be sent back and forth between two machines. This is analogous to two people at a cocktail party who both have the physical ability to speak and to hear. However, a communication is more than that: The two people also need to know how to talk and to listen if they are to communicate at all. An ordering of read and write operations that makes sense to both sides is one instance of what is called a protocol. To quote Comer from his book Internetworking with TCP/IP, "Protocols . . . give the formulas for passing messages, specify the details of message formats, and describe how to handle error conditions." (Comer 1991, 3) Because it is so important for a meaningful communication, we'll define a second class hierarchy for our communication objects, CProtocol and its derivatives. As in communication objects, each protocol has two parts: a client part and a server part (derivatives of the CClientProtocol and CServerProtocol virtual base classes).
Consider the case of transferring a file from machine A to machine B. Independent of the technique we use to do the physical transfer of bytes from A to B, we also need to define at what time A should send data and B should receive it, as well as how to communicate to B when the file transfer is over. In the case of an online conversation between A and B, we could define a CB radio-style conversation (for example, A first says something and then says "Over" before B can say something, which also terminates with "Over"); or we could require that the other side confirm everything we say, as is common in aviation communication; or we could implement a real-time strategy in which both sides can listen and talk concurrently.
If you have ever used a Chat application or your neighborhood bulletin board front-end, you have probably realized that the application requires you and the user of the other machine to comply with your own protocol. Typically, you type something to which the other side responds at the same time that you are typing something new, and while you see the response, you realize that you should probably reply with a different message than you are currently typing, and start erasing what you just typed in order to start a new response. The result is a fairly unstructured conversation that could have been enhanced by some kind of protocol (such as waiting for the other person to type "Stop" before replying).
Protocols are fairly common in the computer world. A wide range of them are available for all kinds of purposes; some of those protocols (such as the Kermit file-transfer protocol) have been adapted for a wide range of hardware. The design of a protocol depends heavily on the purpose its associated communication serves. I strongly encourage you to experiment with a number of different protocols for your specific needs to determine which one works best.
When a CProtocol object is created, its constructor is passed two CCommunication objects (one that handles the inbound communication and one that handles the outbound communication) that were created when a communication was established. CommChat never uses the communication objects directly; instead, it performs the reads and writes via a protocol object that syncs up the client and server appropriately.
Three derivatives of CProtocol are provided in CommChat: CSelectorProtocol, CChatProtocol, and CFileTransferProtocol. Once more, the modularity of C++ should make it easy to replace any protocol implementation with other ones, if necessary. We will discuss the implementations of those protocols in the next section.
Please note that the distinction between protocols and communications is not confined to this particular application and is crucial to a clean application design. Using both protocols and communication objects is a fairly universal approach. For example, I reuse communication objects and protocols in my article series that begins with "Windows NT Security in Theory and Practice" in the Development Library.
After all the words, let us look at a graphic now. (I love diagrams with a lot of squares and arrows in them.) Figure 1 shows how the CProtocol and CCommunication objects fit into the picture.
Figure 1. Communication object hierarchy
Note that CServerNamedPipe is derived from both CClientNamedPipe and CServerCommunication. At the same time, both CServerCommunication and CClientNamedPipe are derived from CClientCommunication. In other words, there is a circle in the derivation graph. The reason for this somewhat convoluted class hierarchy is that each server communication class is a superset of the corresponding client class, that is, the functionality that the CClientCommunication class provides is the common functionality that applies to all communication objects, client or server. Likewise, the implementation of Close, Read, and Write (Open must be handled a little bit differently) is the same for both client and server ends of named pipes. In order not to implement the common functionality in two places, I decided to derive CClientNamedPipe and CServerCommunication from CClientCommunication using virtual inheritance so that objects that are derived from CClientNamedPipe and CServerCommunication can use both CClientNamedPipe's implementations of the Close, Read and Write members and provide the added server functions AwaitCommunicationAttempt and CancelCommunicationAttempt.
CommChat is a fairly straightforward MFC application. There is one document for each active chat communication with a single view attached to that communication. All the code relevant to the communication is associated with the view and can be found in the file COMMCHVW.CPP. A file transfer is not associated with a document. When the user selects File Transfer from the application's menu, a new communication object is created through which the file transfer will be accomplished.
As can be expected in larger applications, most of the design work went into the user interface. Each chat communication is visualized by a static splitter window, with the upper pane being an edit view into which text can be entered and the lower pane a read-only edit view in which the text typed on the other side will be displayed. For the upper pane of the static splitter window, I chose CWriterView, a class I derived from CEditView, and for the lower pane, I selected CReaderView, another CEditView derivative. The main difference between CReaderView and CWriterView is that CReaderView is linked to a read-only edit box so that you can see but not edit what the other side types, and CWriterView notifies its parents of changes in the edit control so that the contents of the edit box can be sent over to the network.
In order to build the splitter window, I followed the instructions that come with MFC, but the hard part was to process the edit control change notifications from the edit view. After all, whenever the user types something into the upper pane of the splitter window, the text is supposed to be reflected in the lower pane of the other user's splitter window. Thus, some CWnd object needs to have an ON_EN_CHANGE entry in its message map to process the notifications, but what window is that? The Windows architecture dictates that an edit control send notifications to its parent, but in the plain splitter window implementation, the parent of the panes is the splitter window, whose message map I did not have access to.
After experimenting with this for a while, I found that all I needed to do was to derive a class from CSplitterWnd that does not do anything but propagate the change notifications from its edit views back to the view. The new class is called csubsplit; the definition and implementation can be found in the files SUBSPLIT.H and SUBSPLIT.CPP, respectively. In SPLITFRM.H, all I needed to do was to change the definition of m_wndSplitter from CSplitterWnd to csubsplit, and change the line
m_wndSplitterWnd = new CSplitterWnd
in SPLITFRM.CPP to
m_wndSplitterWnd = new csubsplit
The only remaining problem was that MFC requires that the ID of the edit control whose notifications are to be processed be known at compile time, but since the edit control is embedded in the CEditView-derived objects from which the other edit views are derived, how can I figure out what the IDs are? I could have taken Dale Rogerson's advice and abandoned the ON_COMMAND entries in the message map altogether while processing all the command notifications in the OnCommand member function, but I thought this was a little too much work for one special case.
A little rummaging in the MFC source code revealed that the IDs of the panes in splitter windows are assigned as
AFX_IDW_PANE_FIRST + (16*column) + row
so I defined ID_SENDBOX as AFX_IDW_PANE_FIRST + 1 and used ID_SENDBOX as the ID for the ON_EN_CHANGE notification. According to the MFC development team, this formula will not change, so it is safe to use.
Although the current implementation of MFC is not multithreading-compatible, MFC applications can still benefit from multithreading, as long as you ensure that no MFC member functions are called concurrently from multiple threads. CommChat demonstrates how to use multiple threads in the following way: There is the main application (primary thread), as well as a new thread that is spawned so that the server can wait for a client to request a communication. As soon as a communication is established, this secondary thread dies. Note that in the current version of CommChat, only one communication can be active at any time; for some communication strategies, this might be too big a limit. I might enhance the application in the future to allow multiple concurrent conversations.
As soon as a communication is established, a new document is created that is associated with a new single splitter view. As explained before, this new splitter view is composed of a reader view and a writer view. As the reader view is created, it spawns yet another thread that constantly listens to the associated CChatProtocol object for new content.
As explained in the article "Using Multithreading and C++ to Generate Live Objects," the PostMessage call can conveniently be used to uncouple threads. CommChat demonstrates how this can be done: When either of the nonprimary threads wants to inform the application of anything (the server-listening thread when a client has requested a communication, and the reader thread when the communication has been interrupted), that thread posts a message back to the main thread, which can then react accordingly.
Each of the functionalities provided by CommChat—file transfer and online chat—is implemented by one object class: CFileTransferProtocol and CChatProtocol, respectively. Both of these are derived from the generic CProtocol definition, which in turn is derived from CFile. The implementation of the CProtocol object and its derivatives contain definitions for the standard CFile member functions Open, Close, Read, and Write. As mentioned before, the application never interacts with the communication objects directly, but rather with the protocol objects. This way, the application does not have to worry about the details of the communication at all.
We will see later that protocols may be fairly tightly coupled with specific communication objects. For example, certain objects, as provided by the operating system or its extensions, may already support protocol functionalities, which makes writing the protocol easier. By logically separating the protocol from the implementation of the communication object, we have introduced additional flexibility to either delegate more work to the communication object or implement the protocol ourselves, if applicable. This is very similar to the way Windows user-mode graphics-device drivers work: On sophisticated hardware, a driver may either let the hardware render complicated graphics or disassemble complex graphics calls into easier pieces; the Windows graphics engine only knows that the driver and the hardware together implement a graphics feature.
One additional derivative of CProtocol, CSelectorProtocol, has been provided to select communication types from the same communication objects. We will discuss the three protocol objects next.
The CChatProtocol class has two derived classes: The CClientProtocol class defines the client side of a chat communication, and CServerChatProtocol implements the server side.
For the sake of the upcoming discussion, let me emphasize one thing again to make sure you do not get confused: I will use the terms client/server and sender/receiver frequently. These terms are not the same. The client is the machine that asks for a communication, and the server is the machine that responds to it. After a communication is established, both sides serve as senders and receivers at the same time (at that point, each side runs two threads, one for the inbound and one for the outbound communication); thus, when I talk about sender and receiver, I am talking about either of the two machines, whereas client and server refer to the machine requesting or responding to the communication attempt, respectively.
A chat communication basically consists of the steps listed below. Let us assume that a user on machine BEAKER attempts to establish a chat communication with remote machine GNORPS using the New command from CommChat's File menu. This automatically makes BEAKER the client and GNORPS the server.
But wait, there is a problem here: How does the receiver know how many bytes the message is in size and when to display the whole thing?
Once more, it all depends on the protocol. We have several options here: We could either require that the sender first inform the receiver about the number of characters it is about to send before sending them—just as the protocol did when it sent the machine name from the client to the server—or the sender could merely terminate the broadcast with a special termination character that the receiver uses to determine when to fill its own receiver edit box.
In the current version of CommChat, the only communication objects available are named pipes, and named pipes offer a variation that relieves the protocol from having to worry about byte counting. (Please see the discussion in "Garden Hoses at Work" for details.) This gives us a hint that protocols and communication strategies may be fairly tightly coupled.
Another possibility would have been to use a line-feed termination as the separator, thereby forcing a line-based protocol onto the user. I encourage you to play with CChatProtocol for a while; you could use a special character sequence (such as <stop>) as a terminator if you wanted to. As a side effect, a protocol along those lines would also force the user to use a certain protocol.
All of this happens without the application knowing what steps are involved to keep a conversation going—aren't protocols great?
A file transfer protocol has to satisfy slightly different requirements than a chat protocol. In its simplest implementation, all the protocol needs to do is let the sender take one byte of the file to transfer after the other, send it down the communication line, and let the receiver stuff all the bytes back into a local file.
CFileTransfer does things a little bit differently. Here is an outline of how a file transfer works using the CFileTransferObject.
The first two steps are the same as in the chat protocol. However, in the third step, the two communication objects are used to create an object of type CFileTranferProtocol. As with CChatProtocol, the client sends its machine name to the server, which then prompts the user to respond as to whether a file is to be received. Upon positive acknowledgment, both sides display a File Open or Save dialog box (using, no surprise, the MFC CFileDialog object) to select the locations of the source and target files, respectively. Inasmuch as either side can now reject the transfer via the Cancel button in the File dialog box, both sides have to reconfirm their wish to do the transfer. The client (which in this case is the same as the sender) does so by calling the member function LastMinuteCheckSender, and the server does so by calling LastMinuteCheckReceiver. These functions exchange Boolean values with each other to determine whether both sides are still sure about the transfer—if either sends FALSE, both members will return FALSE to indicate that one of the two sides rejected the transfer.
After that check, a file transfer is fairly straightforward: The client sends the size of the file to the server, allocates a file-mapping object for the file, reads the file into the mapping objects, and sends the entire mapping object over. The client reads the result into a local buffer and writes the buffer to disk.
You will notice that the very same communication objects are used for both chat and file transfer communications. This poses an interesting question: If the server waits for clients to connect, how can it (the server) figure out whether a file transfer or chat communication was established?
The solution I adopted was to build a very tiny protocol object, CSelectorProtocol, that does not respond to the Open, Close, Read and Write messages, but instead is built temporarily only to help the server decide whether to open a communication for chat or file transfer. The CSelectorProtocol object has two exposed member functions: SetCommunicationType and GetCommunicationType.
SetCommunicationType is called by the client to tell the server whether to open a chat communication (CHATTYPE) or a file transfer communication (TRANSFERTYPE). When the user selects File New or File Transfer a File from the menu, a temporary CSelectorObject is built, the SetCommunicationType member is called, and the protocol object is destroyed.
Likewise, when the server has returned from its AwaitCommunicationAttempt calls, indicating that a client has connected, it first creates a temporary CSelectorProtocol object, calls the GetCommunicationType member function to determine the type of communication, and eventually destroys the protocol object.
In the current implementation, all this object does is write either t or c from the client to the server to indicate file transfers or chat communications, respectively.
As we will see later on, there are 1001 ways for a network operation to fail—a network server may shut down or a telephone line fail, data may get lost or corrupted on its way through the cable, or heavy network congestion may cause a transmission to time out. At all times, a network-aware application must be prepared to react to unforeseen transmission problems.
In the case of a layered architecture such as CommChat (where the application routes an I/O request to a protocol, which in turn passes the request on to the communication objects), it is not always easy to decide who should react to which error conditions. For example, when one side has terminated a connection, the entire communication should be shut down; whereas in the case of a transmission time-out, the input/output (I/O) operation should be retried in a manner transparent to the application or the protocol. And in the case of a buffer overflow, the protocol could try to break down a transmission into smaller chunks.
The best way to handle this problem of reacting appropriately to error conditions comes built into Win32: structured exception handling. If an error condition from the physical read or write operation raises an exception, it is up to any layer involved to handle the exception or to pass it on to the next layer. We will look at the use of exception handling when we discuss the individual communication strategies.
If the user of CommChat wants to chat with another machine or transfer a file, he currently must enter the names of all the machines that might run CommChat at the same time in a local initialization file, COMMCHAT.INI. When the user selects either a communication or a file transfer, CommChat will successively read all the names of the potential communication partners from that initialization file and prompt the user to indicate whether a communication or file transfer should be established.
This is kind of awkward. When I discussed this application with Nigel Thompson, his eyes started to glow, and he said, "You know, Ruediger, you should really implement a cool network browsing dialog that would display all the machines on the network in a list box, and each machine would tell you whether it is available for a communication or whether it is busy chatting with somebody else, and so on."
Actually, this is a cool idea, but the work involved in building such a box is not trivial. The techniques to browse the network for machines are exposed and known. (In fact, Craig Link from Microsoft Product Support Services has written such a sample dialog box using the Win32 Software Development Kit (SDK); it's called SERVENUM. (See the Knowledge Base article Q118327 for more details.) However, it is not trivial to provide a custom hook for figuring out information about other nodes on the network. All hell could break loose if you tried to establish a temporary communication to every node in order to figure out whether the other node was ready to open up a conversation—mostly because those micro-communications can easily bring the network to its knees.
I will postpone working on this issue for a while, but I will definitely do the work—it is very worthwhile.
I found the combination of C++ and networking to be an extremely satisfying and rewarding one. This is because networking has a lot to do with abstraction and the building of abstraction layers on top of each other, and this can be very naturally woven into an encapsulation framework such as C++. Splitting up the task of communication between two machines into an application front-end, a communication layer, and a protocol layer very naturally reflects the problem of harnessing a communication on a physical (communication type) and logical (protocol) level.
This article has not discussed the implementation of the communication object at all—deliberately so, because, as I indicated earlier, I intend to provide an entire hierarchy of communication objects based on the different communication strategies that exist. The "Garden Hoses at Work" article discusses named pipes as the first communication object to be plugged into COMMCHAT.EXE.
If you need to incorporate network communication into your application, you could use the implementations of the communication classes and customized protocol objects that are tailored to your needs. You can also use the communication objects directly without going through a protocol, but most likely you will need to define some kind of protocol to get your own communication in order.
I can't say that my design of CommChat is an application of rocket science. I could have added a number of things to make the application more user-friendly and snazzy (for example, the option to maintain several communications at the same type in several MDI windows). Recall that the application was mainly designed as a test for the communication objects, not as a meaningful application in itself. For other applications of communication objects, see the article series that begins with "Windows NT Security in Theory and Practice."
However, CommChat itself is easy to extend; for each new network functionality you want to implement, you could add another protocol object and extend the CSelectorObject to discriminate among even more types of communications.
Baker, Steven. "An Overview of Network Programming Interfaces for Windows and Windows NT." Microsoft Systems Journal 11 (November 1993). (MSDN Library Archive, Books and Periodicals, Microsoft Systems Journal, 1993 Volume 8, November 1993 Number 11)
Comer, Douglas E. Internetworking with TCP/IP. Vol. 1, Principles, Protocols, and Architecture, 2d ed. Englewood Cliffs, NJ: Prentice-Hall, Inc., 1991.
Patch, Ray, and Alok Sinha. "Designing the Client Portion of a LAN Manager Client-Server Application." Microsoft Systems Journal 3 (May/June 1991). (MSDN Library Archive, Books and Periodicals, Microsoft Systems Journal, 1993 Volume 8, November 1993 Number 11)