About the GUI Framework
This example is based on the "MFCConsole" framework. Between that
base and the FAQ-specific extensions, there are 1800 lines of generic
framework code, neatly separated from the specific parts of each example
based on it. You do not need to understand this framework to learn from
the examples, but if you are interested, you can find information about
the framework on the MFCConsole page.
The example framework has two main menu items: "Start Winsock Handler"
(Ctrl-W), and a generic "Action" item (Ctrl-A). When you give the
Start command, it calls DoWinsock() this is similar to
the console programs' DoWinsock() mechanism. The main difference
in the GUI version is that DoWinsock() doesn't call the network
handling functions directly: it just creates the object that does the
real work. Then you give the Action command to do whatever specific
thing the example does.
The Unique Bits
The unique parts of this example are in the CAsyncClientWnd class. That
class derives from CNetworkDriver, a generic interface that the framework
calls when you give the Start and Action commands mentioned above.
The Start() function in this example looks up the server's
address and establishes the network connection to the server. Then the
Action() function sends a packet for the server to echo back,
and reads/verifies the reply. If the Action command is given again,
another send/read sequence occurs. Then when the user closes the window,
the connection is gracefully shut down.
That's more or less what the simpler basic blocking client does. The
tricky bit, though, is that Winsock never blocks
when all of this is going on. Whenever Winsock can, it does what we ask
it to immediately, but most everything on the network takes time. So,
rather than stop the program while the network is busy, Winsock lets us
go back to handling the UI while it waits for the network operation to
complete. When it does, Winsock sends us a window message to tell us the
result. This means we can handle both networking and the UI in a single
thread, but it does make the program harder to write.
Here's a detailed breakdown of the run sequence:
- The user gives the example framework's Start command. The
example framework pops up a dialog box to get the server's address
and port number.
- If the user hits OK in that dialog, the framework calls
DoWinsock() , which creates CAsyncClientWnd, the window
object that contains the async client code.
- The framework calls
CNetworkDriver::Start() , which our
subclass overrides. It just saves the address and port given,
and does a DNS lookup on the address. The lookup almost always
takes some time, so we usually just exit Start() without
actually opening the connection to the server.
- When Winsock gets the host IP back from DNS, it calls our
OnWinsockLookup() message handler. If it was a successful
lookup, we call EstablishConnection() .
EstablishConnection() creates the socket, marks it as an
asynchronous I/O socket, and attempts the connection. Again, this
almost always takes some time, so we exit without having connected,
and wait for Winsock to tell us whether it could connect or
not.
- When the server accepts the connection, Winsock calls our
OnWinsockNotify() message handler. This is the core of the
asynchronous I/O handling: this message handler gets called for
connections, disconnections, "data ready to read" events, and "okay
to write" events. It also gets called for errors that happen
asynchronously, like "connection refused by server". This time
through, we get an FD_CONNECT notification, which just causes us to
print a message saying that we're connected. Notice that we also get
an FD_WRITE notification, which simply means Winsock is ready to
handle send() s.
- Once the connection is established, the user can give the Action
menu command. In this program, that means "send the echo request".
We give the echo packet to Winsock, which it will deliver to the
server as soon as it can. Meanwhile, we go back to waiting for
window messages. (Note that the
SendEcho() function isn't as
smart as it might be: it's possible for send() to only queue
up part of the buffer we give it. It's much rarer than the similar
case with recv() but it can happen.)
- The server will receive the data and send it back. This will
cause Winsock to send us an FD_READ notification. We read in the
reply and check it, almost exactly like we do in the other clients'
ReadReply() functions.
- Because this client is event driven, we can allow the to
user give the Action command as many times as they want. Goto line
7. :)
- When the user gets tired of bouncing packets off the server,
he can exit the program. This is another tricky part of the
example, because we have to close the connection down gracefully
before we exit. Like many other operations with asynchronous
sockets,
shutdown() only starts the shutdown process:
we have to wait for an FD_CLOSE notification to know that the
connection is really shut down. Meanwhile, we ignore window
close requests.
- Eventually the FD_CLOSE notification arrives. If we see that the
UI is waiting for a close notification message, we send it. The UI
then says "okay, I can shut down now". It does, and that's the end
of our little adventure.
The Code
This client program has about 330 lines of code,
compared to about 150 in the basic
blocking client. That's the tradeoff for getting a program that
handles both the GUI and the network in a single thread without either
blocking the other.
The project package (33 KB) is
a complete Visual C++ 5.0 project. It includes everything you need to
build the sample.
License
Note that this example program is released under a different license than the other example programs. It's a
BSD-style license, which means you can do anything you want with the
example so long as you don't sue me or my employer if it prints 500
pages of "Microsoft sux!" on your customer's high-speed laser printer.
|