Prerequisites
READ PART 1. That is upper case and said with emphasis.
This is rather long as it is so I must presume the reader has read Part 1.
The reader is expected to know how to create an MFC project
and the fundamentals of TCP/IP operations. (What is TCP/IP and its purpose, what
are the client/server roles, IP address, ports, and the like.) The reader
should be a well started Visual Studio beginner or maybe an intermediate VS
user or better, confident with dialogs and their controls, but is expected to
be a novice with asynchronous TCP/IP operation.
All Four Articles
Late Addition: This is a short summary of all four articles and a link to each.
I have completed four articles on asynchronous TCP/IP and Microsoft's class ASyncSocket. Part 1 describes the necessary concepts. Part 2 describes a single project that incorporates a server and a client in a single project and a single dialog. The user can walk through a transaction one step at a time. In parts 3 and 4 the Server and Client are separated into two projects residing in one solution. The Server and Client can run on separate computers. That project introduces the concept of multiple projects in a single solution. It introduces the concept of using source code from a separate directory. If you are not well versed in TCP/IP and with ASyncSocket, the first two articles are a must read. If you have not worked with multiple projects in one solution, or with
Additional Include Directories, Part 3 is must read. If that sentence looks weird, please read Part 3. Here is a link to each article.
Asynchronous TCP Part 1
Asynchronous TCP Part 2
Asyncsocket Part 3: The Server
Asyncsocket Part 4: The Client
Introduction
This is the second of a two part how to article. Part 1
describes the concepts of how to use the Windows class CAsyncSocket to
implement an asynchronous TCP/IP interface. It says away from the code as much
as possible. That article is a must read before this article. It is found
here.
This article presents the use of the class CAsyncSocket.
The purpose of the application was to provide an application to hold and
exercise class CAsyncSocket. In order to be able to see all the interactions
in the dialog it wound up with over sixty controls, and could use more.
Describing that many controls and the code to drive them is too much for an
article about CAsyncSocket. This article describes the three worker classes
and omits the support code.
Three classes are required to initiate and carry on a TCP/IP
conversation using Windows and CAsyncSocket. In this application they are
C_Server, C_Client, and C_Server_Send_Time_Socket. Class C_Server contains an
Accept() method, an CAsyncSocket::Accept() method, an OnAccept method, and an
CAsyncSocket::OnAccept() method. To ensure the names were completely obvious,
the methods in C_Server that might be confused with those of the base class are
all prefixed with Class_. The same applies to the other two classes.
Having read Part 1, we jump right into class C_Server.
Class C_Server::Class_Initialize()
There are three key methods in Class C_Server. Begin with
the initialize method.
bool C_Server::Class_Initialize()
{
m_winsock_status = AfxSocketInit();
m_winsock_status == 0 ? m_method_status = false : m_method_status = true;
if( m_method_status )
{
m_winsock_status = CAsyncSocket::Create(
m_port_number,
SOCK_STREAM,
FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE,
DEFAULT_IP_ADDRESS );
m_wsa_error = WSAGetLastError();
m_winsock_status == 0 ? m_method_status = false : m_method_status = true;
}
return m_method_status;
}
There are only two worker lines in this method, the
remainder is all support.
m_winsock_status = AfxSocketInit();
m_winsock_status = CAsyncSocket::Create(
m_port_number,
SOCK_STREAM,
FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE,
DEFAULT_IP_ADDRESS );
The first tells Windows to perform a bunch of initialization
for sockets operations. The seconds creates the socket. Almost all the work
is done by the base class leaving only the high level calls to make.
Note the calls to WSAGetLastError() to check for problems.
That must be done immediately after calls to the base class. If you are new to
TCP/IP in windows, WSA error code merits some time with Google. In the
following discussion the support code will not be described.
Class C_Server::Class_Listen()
After the socket operations are initialized, the next step
is to tell Windows to begin listening for a client that will request a TCP/IP
connection.
m_winsock_status = Listen();
This is the only worker code in this method. After it
returns, Windows has completed the TCP/IP initialization and is ready for a
client.
Here is where base class CAsyncSocket enables us to divert
from simple blocking TCP/IP operations. With synchronous operations the call
to Listen() would not return until Window has received a request from a
client. In asynchronous operations, Listen() returns with a status code
allowing the application do other things while Windows waits for a client
connect.
Class C_Server::Class_Accept()
This is the key method. Again, there are but two worker
lines of code.
mp_C_Server_Send_Time_Socket = new C_Server_Send_Time_Socket;
m_winsock_status = CAsyncSocket::Accept( *mp_C_Server_Send_Time_Socket );
First up is to create an instance of C_Server_Send_Time_Socket.
This is the object that will carry on all the conversations with the client.
It is customized for each particular application.
Very Important Paragraph
Next up, tell Windows that this is the object for
communicating with the client. To do that we call the Accept() method of the
base class and pass in our new object as the only argument. The base class and
Windows manage all the details. The responsibility to handle all communications
with the client is hereby assigned to the new object. That second line of code
is so simple, but so key to this process, you should pause and consider this a
few moments before continuing.
If your application expects a single client, this will work
for you. However, and this is a big however, if you will accept multiple
client connection, the C_Server class will need to do something with the
pointer to the new object it created and will need code to recycle and be
prepared for the next connection. For simplicity, this application will handle
one client only.
That is it. Much more simple than I expected.
Class C_Server::Class_Close()
The Close() operation is rather straight forward.
Class_C_Server::On*()
In the CodeProject article referenced in Part 1 and in other
places, I found this list of methods from the base class that are to have
over-ride methods. Here is their declaration from the dot H file.
virtual void OnAccept( int nErrorCode );
virtual void OnClose( int nErrorCode );
virtual void OnConnect( int nErrorCode );
virtual void OnOutOfBandData( int nErrorCode );
virtual void OnReceive( int nErrorCode );
virtual void OnSend( int nErrorCode );
For this very simple demo application, the definitions are
very simple. This one is typical.
void C_Server::OnAccept(int nErrorCode)
{
m_server_on_accept_count ++;
mp_main_dialog->Set_Server_On_Call_Counts( m_server_on_accept_count,
m_server_on_close_count,
m_server_on_connect_count,
m_server_on_out_of_band_count,
m_server_on_receive_count,
m_server_on_send_count );
if(nErrorCode==0)
{
CAsyncSocket::OnAccept(nErrorCode);
}
}
All of the On*() methods in each class has a counter
that increments on each entry, and a method call to the main dialog so it will
immediately display the call count. All the classes have identical declarations
and essentially identical definitions.
The reader can begin with this demo application then add
complexity and see when, or if, each of the methods is called.
C_Server_Send_Time_Socket::Class_Send()
As noted this class is created by the
C_Server::Class_Accept() method. Windows and the base class manage of all the
details. After being handed to the base class as the argument, the object is
ready to handle all the communications with the client.
int C_Server_Send_Time_Socket::Class_Send( )
{
int chars_sent = 0;
int size = sizeof( m_current_time );
GetSystemTime( &m_current_time );
chars_sent = CAsyncSocket::Send( (const void *) &m_current_time, size, 0 );
return chars_sent;
}
This method fetches the current system time, then tell the
base class to send it. Sending the complete structure to the client makes it
simple.
Look through the remaining methods of this class and
discover that all the remaining code is support for the demo application. None
is needed for the core function to send data.
Again, in this simple application, that is all there is.
C_Client::Class_Initialize()
The client initialization is rather simple.
m_winsock_status = AfxSocketInit( NULL );
m_winsock_status = Create();
Again, there are just two worker lines of code.
C_Client::Class_Connect()
Here is our one worker bee.
m_winsock_status
= CAsyncSocket::Connect( m_ip_address, m_port_number );
When this method is initiated the base class goes to Windows
and its APIs and sends out a search party to find the server. This sets off
several events. In C_Server the OnAccept() method is called. That tells the
C_Server object that it now has a live client. The server responds across the
network and C_Client gets a message that the server has been found. The MFC
application detects this message and knows to call the client method OnConnect().
When you run the application that counter will advance to 1 (one). You can
write code in this method to respond to the connect event.
All these interactions at the Windows level results in the
client OnSend() being called. While the client method OnConnect() means that
we have connected, OnSend() means that we can now send to the server.
As noted earlier, this application is simple enough that,
other than counting them, all the On*() method calls are ignored. In this
application the user and the keyboard close the loops. In a real application
there will be code to close the loops and make it all functional.
In standard code, this is a blocking call and the
application waits until the server has been found. Class CAsyncSocket allows
the client application to attend to other matters while waiting for responses.
C_Client::Class_Receive()
Again, this is almost too simple.
int chars_received = 0;
int size = sizeof( m_current_time );
chars_received = CAsyncSocket::Receive( (void *) &m_current_time, size, 0 );
The client has an identical time structure to that of
server and simply reads the data into that structure. The code to display the
time is deliberately left out of this method for simplicity. Check out method
SYSTEMTIME C_Client::Class_Get_Time() and its caller to see how this is
handled.
Conclusion
When considering the lengths and the difficulty in getting
this together for the first time, the actual operations are rather simple.
(Recognizing that there is much to de before you have a working TCP/IP real
world application.)
A quick review is in order.
- Initialize
C_Server.
- Initiate
the server Listen mode, instructing Windows to listen for a client.
- Initialize
C_Client.
- Initiate
the connect method, instructing Windows to find and connect to the server.
- In
the Server,
Accept() the connection and create class C_Server_Send_Time_Socket
to communicate with the client.
- Use
C_Server_Send_Time_Socket to send information to the client.
- In
the Client, receive the information.
Observe that there are seven steps
here, and there are seven buttons in the application. The matchup is one to
one. I suggest you put a break point in each of the methods of each class then
start with 1:Initialize and see where you get. (Return to Part 1 for a
walkthrough.) Remove each breakpoint as you get to it, then step through the
code until you get back to the Windows MFC application code (the stuff you did
not write). Then hit Continue in the debugger, check the results, and go on to
the next button. Go back to Part 1 for a better walk through of these
activities.
To repeat an earlier comment, it
works much better when you put the application dialog on one monitor while you
step through the debugger in Visual Studio on the other monitor.
My career began as a navigation electronics technician on submarines in the U.S. Navy. (After a short stint on the battleship USS New Jersey, that was cool.) I worked the next several years as a technician while earning a BSCS in computer science. During my senior year I wrote code for part of a missile flight simulator. That was followed by too many years working with Fortran on the security system at Cape Canaveral Air Force Station. I worked a few years on CLCS (Checkout and Launch Control System for the space shuttle) until it was canceled by NASA, then became the lead antenna engineer for a portable range tracking system. Now I work on telemetry at an Air Force base.
If you work with telemetry please check out this BB: www.bkelly.ws/irig_106 and the support pages at www.bkelly.ws/irig. Thank you.