Introduction
This fourth part discusses the Client part of the ASyncSocket
articles. My application sends data to a vendor’s application. We needed the Server part only. However, when the Server did not work well with the vendor application, a Client had to be written to learn what was wrong with the Server end. This article describes that Client. The Server is presented in Part 3.
All Four Articles
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 a must read. If that sentence looks weird, please read Part 3. Listed below are links to each article:
Background
During my initial introduction to TCP/IP code and Microsoft’s class CAsyncSocket
, I wrote two articles and uploaded them to Code Project. They describe the fundamentals of how to use CAsyncSocket
. You can use these links to find Part 1 and Part 2. This link is for Part 3.
Caution: The server does not need an IP address to run. The clients do need the address of the server. This application was developed at home on my desktop. The LAN consists of a wireless router and switch combination, the desktop computer, and a laptop computer. The IP address of the desktop and laptop computer are set by my ISP. Generally the desktop gets IP address 192.168.2.2 and the laptop gets the dot 4. Sometimes not. Look in C_TCP_Constants.h to find these lines:
const char C_TCP_DEFAULT_IP_ADDRESS[] = ("192.168.2.3");
The 127 address is the loopback and works well. However, it does not stress the network and does not solicit would block errors as often. When using the 192 address, you may discover that your code worked yesterday but not today. You computer may have booted with address 192.168.2.2 or dot 4 instead of dot 3. Just change the constant and continue. Feel free to add some code to the dialog to ease this process. If you are adventurous, code can be written to find the address and display it in the dialogs of the server and client. I have reached and exceeded my maximum needs for this project and am moving on.
Note: If you are uncertain of the significance of the 127 and 198 addresses, you will benefit from some background reading about networking in general, before continuing this article.
Using the Code
Build the solution in Release mode. Copy one of the EXE files to another computer and start both. Start the Server, then start the Client. At the client, select connect. Then select the number of messages to send each timer cycle of the server and select Send Count. The Server will begin sending messages to the client. Watch the various status fields to see how the two projects are running.
If it does not work right, check the IP address of the Server and be sure the client matches it.
Project Client
This is the top level of the Client project. Its sole purpose is to drive and exercise the class C_TCP_Client
. This project in this solution is the stand in for your project. Use it to test and improve the Client utility, then incorporate the Client into your real project with confidence.
The Dialog
Button Start Client creates the object from class C_TCP_Client
, but does not instruct it to do anything. The various status values will populate and it waits until you are ready to connect. Button Connect begins the interesting part. Of course, the Server must have already been started and be listening for the Client to connect.
The field Port Number should be obvious. When the Client has been started, field Client Pointer will merely indicate that the pointer is valid.
Below that are counts for each of the On*()
methods, the number of times each has been called.
Move right to checkbox Validate Payload Packets. When unchecked, this client is just a sink for the server. It eats the payload packets allowing the Server to send data. When checked, the Client examines the payload packets. It checks them for proper formatting and proper sequence numbering. Below that are a few more check box options for logging data. The log files can be a tremendous asset when developing and testing code.
Now that the validate option has been mentioned, look in the bottom left at the three fields there. The payload packet in this solution has a sequence counter, incremented by one for each payload packet. The sequence counter can contain the expected value, or not. When not, that indicates a problem, a payload packet has been dropped somewhere. It is possible to overload the Server application with too much data and cause the loss of packets. But that is another topic.
The OS on the server side can combine two or more payload packets into a single TCP/IP packet. And, it can split a payload packet into two or more TCP/IP packets. Field Payload Split Count shows how many payload packets were split across two TCP/IP packets.
Move to the list box on the right side. When started, the Server has the value of zero for a send count. It is doing all the right steps but sending zero packets on each timer interrupt. Select a value in the list box and click Send Count. That tells the Server how many payload packets to send on each timer interrupt. The server, as provided, runs on a 200 millisecond interrupt rate. When you select 10 in the list box, the result is a burst of ten packets every 200 milliseconds. They are not evenly spaced out, but in bursts.
Client Project
This client is a bit unusual. Because of the nature of my needs, this demonstration client never needs to actively do a read. When the server sends a packet, and the client’s OS receives it, the OS calls the method OnReceive()
. That is the indicator that there is data to fetch from the OS. Take a look at the OnReceive()
method in C_TCP_Client
.
m_on_receive_count ++;
m_receive_byte_count = CAsyncSocket::Receive(
m_receive_buffer.char_array,
C_TCP_MAX_RECEIVE_SIZE, 0 );
m_wsa_error_code = WSAGetLastError( );
The second line by itself would be sufficient for a basic test utility for the server. It would serve as a data sink and allow the server to keep on sending data. Think about that for a bit before looking at the bottom of this short method.
if( m_data_valadation )
{
Validate_Received_Data( );
}
If the dialog check box to validation the data is set, the validate
method gets called. If not, the data is discarded and the OnReceive()
method exits. The client code can be real simple. All the dialog does is start the client, then poll it for status.
Validate Received Data
On the other hand, the validate
method has a difficult task to do.
If you wish to make this a full up client utility, here is the key section you must understand and modify to fit your needs. For my testing, this just validated the server packets and proved that all the packets were getting sent in good condition. For a full up client, this should be modified to extract the individual payload packets from the TCP/IP packets, which, under high traffic conditions, can carry multiple payload packets. I suggest that you modify this to put the payload packets in a buffer and tell your application how many are waiting. Then add a method so your application can fetch the payload packets one at a time. Keep all the complexity in this class so your application does little more than ask for the next payload packet.
On to the Description
Consider these problems. Any payload packet can be split across two or more TCP packets. Any TCP packet can contain two or more payload packets. Indeed, depending on the size, one TCP/IP packet may, for example, contain two and a half payload packets. Finally, remember that checkbox to turn on validation? When validation is turned on, the first TCP/IP packet may start with the first byte of a payload packet, the second byte, or anywhere up to just the very last byte of a payload packet. Realistically, it won’t get just one or two bytes of a payload packet, but something in between.
void C_TCP_Client::Set_Data_Validation_State( bool new_value )
{
m_data_valadation = new_value;
m_carry_over_bytes = 0;
m_payload_packet_count = 0
}
After taking out the fluff, this is all we have. The first line sets the flag true
or false
to enable or disable validation. Clear m_carry_over_bytes
because we are starting fresh with nothing left over from a previous packet. Any of those left over when we disabled validation are lost. Reset the counter. Now that m_data_validation
is set true
, look back up where OnReceive()
was covered and see that the validate method will be called.
Go to method Validate_Received_Data()
line 426 to find this near the top:
if( m_carry_over_bytes > 0 )
{ ... }
Note that we just described that enabled packet validation ensures that the first IF
is not taken. Skip this section for now and jump down to the DO
loop beginning at line 474.
do
{
mp_search_pointer = (RECEIVE_TYPE * ) &m_receive_buffer.char_array[ start_of_payload_packet ];
...
}
Member variable m_receive_buffer
fetches/receives the data from class ASyncSocket
. Member variable mp_search_pointer
is used to access that buffer and provide the structure so data can be viewed as the structure that was originally intended. Chase down the declaration to understand this. As we look at packets within the receive buffer, this pointer is set to the beginning of each packet. The UNION
then allows the code to directly access the various fields of the payload packet.
On the first entry to this method, mp_search_pointer
begins at the first byte of the receive buffer. start_of_payload_packet
is a method local variable set to zero on entry. It is set in the carry over section, that we just skipped for now, so after a partial payload packet has been processed, it points to the first complete payload packet of the just received TCP/IP packet.
Check the comments in the code. The packet is “validated” by checking two words in the header. If they are not present, the code sets up to start over again and does a quick return. Eventually, we will find a receive buffer that begins with a valid payload packet.
After that somewhat weak check for a valid packet, the sequence counter is checked and counted as good or bad. The server increments is by one each time a payload packet is output. This is just a simple check that the Client can display to show how things are progressing. In a real project, it can be very beneficial in verifying that all the data has been received.
After checking the current payload packet, there are three possible conditions to determine the next action. Check the comments on lines 532, 538, and 546. One of the three conditions will always be met, deciding the course of this utility.
Carry Over
Check the code at line 549.
else if( end_of_payload_packet > m_receive_byte_count )
{
m_payload_split_count ++;
get_another = false;
m_carry_over_bytes = m_receive_byte_count - start_of_payload_packet;
memcpy( &m_carry_over_buffer,
mp_search_pointer,
m_carry_over_bytes );
}
If the end of the payload packet is beyond the data that has been received, then that payload packet has been split across two TCP/IP packets. Member variable m_carry_over_bytes
is calculated and that many bytes are copied to the carry over buffer. When it is non-zero, the first IF
in this method is taken and the remainder of the payload packet is captured. Return to the top of this method to find:
if( m_carry_over_bytes > 0 )
{
true_packet_size = m_carry_over_buffer.iads_structure.header.packet_size + 4;
for( ;
m_carry_over_bytes < true_packet_size;
m_carry_over_bytes ++, start_of_payload_packet ++ )
{
m_carry_over_buffer.char_array[ m_carry_over_bytes] =
m_receive_buffer.char_array[ start_of_payload_packet ];
}
The true packet size is captured from the carry over buffer, then a somewhat unusual FOR
loop is used to copy the remainder of the payload packet to the carry over buffer.
It is a little unusual because the startup conditions are already set up on entry and there are two end of loop operations rather than one. Observe that start_of_payload_packet
is advanced in the FOR
loop. That sets up the DO
loop to begin with the first complete payload packet of the TCP/IP packet.
Hopefully, the remainder of this section is relatively straightforward.
Review of Articles 3 and 4
In part 3, we covered the activity of the Server. Sending data is not always simple. The server class, together with the application, must have the ability to buffer some amount of data when the OS is busy. Be careful, there is definitely a limit as to how big the buffer should be. The wrapper class CAsyncSocket
is a good class, but for high data rates, it can be overwhelmed. Monitor the depth of your send buffer. If it grows to the limit and overflows, the maximum throughput has probably been exceeded.
In part 4. we covered the activity of the Client. It must be able to handle received data when it arrives in bursts.
Side Note: Google Nagle's algorithm. It describes a commonly used method of improving TCP/IP efficiency by combining payload packets.
One TCP/IP packet may contain multiple payload packets placed end to end with no marker between them. The design of the payload packet must include a payload size so the client code can determine where each payload packet ends and the next one begins.
What is omitted here is the code to allow the application to fetch payload packets from this utility one packet at a time. I currently have other pressing needs and have neglected this. If you write that code, please write an article.
History
- 23rd March, 2013: First version submitted
- 26th May, 2013: Updated zip file, added common code, reduced size