Q: How do I transfer structure?
Q: How do I impose a packet scheme?
A:Even though TCP/IP is all about sending data, it is often, in reality, necessary to maintain a few information about the data that is being passed. It is common therefore, to have a structure that contains such information and passed as a header to the data that is being send. There are a number of ways to transfer a structure over a TCP connection using Winsock.
Using 'struct and 'union' is one of the easiest ways to achieve this and provides a number of benefits. There is no extra copying, formatting or casting involved. You are basically sending and receiving strings. Moreover, one can send different headers depending on a variable value. This method is explained in the following section.
We'll take a look at a typical packet header.
Though most of the fields are self-explanatory, the first field 'sCode' needs a bit of explanation. This field indicates the type of header that we are using. For instance, there can be 2 headers, the one that is shown above, to communicate to the client, and another one that communicates between servers. If we are using variable headers, then at least the first field must be unique. Choosing the correct header depends on the value of this initial field, in our case 'sCode.Code:struct PACKET_HEADER { short sCode; // => 2 bytes (the header type or protocol) short sService; // => 2 bytes (the service requested/packet type) unsigned long ulHandle; // => 4 bytes (the client/connection handle) unsigned long ulLen; // => 4 bytes (the data len) };
The size of the packet header is fixed unlike the packet size, which can be unique or variable in length.
A packet is shown above. Other than the packet header, the only other element it contains is a character array to hold the data.Code:struct PACKET { PACKET_HEADER pktHdr; // => 12 bytes char sData[1012]; // => 1012 bytes // total: 1024 bytes (* See note) };
The above code shows the packet is unioned with a character array which is the same size as that of the packet (*see note). As you know, the elements of the 'union' occupy the same memory space, with each element aligned on the least significant bit. So in this case both the elements occupy the same memory space.Code:union DataPacket { PACKET pkt; char pktStr[1024]; };
Then we can actually define some services:
Code:enum Service { ACK, // Acknoledgement FRAG, // Fragment (continuation of the packet that was send previously) AUTH, // Authentiation packet REQ1, // Request 1 REQ2 // Request 2 };
The above code should be self-explanatory. For simplicity, we assume here that all the data is send and received in one go. However in reality you should never make such an assumption. You can find more information in the following FAQ: Does one call to 'send()' result in one call to 'recv()'?Code://The client part int main() { DataPacket dp; unsigned long ulErr; // ... dp.pkt.pktHdr.sCode = 0; dp.pkt.pktHdr.sService = AUTH; dp.pkt.pktHdr.ulHandle = 0; dp.pkt.pktHdr.ulLen = 100; // Copy 100 byte authentication code to dp.pkt.sData // sending the authentification packet to the server ulErr = send( cliSock, dp.pktStr, sizeof(PACKET_HEADER) + dp.pkt.pktHdr.dwLen, 0 ); if ( ulErr == SOCKET_ERROR ) { // Handle the error using 'WSAGetLastError()' } // continued...
The server side may look like this:
More explanation to the above code doesn't seem to be necessary. The server that receives the authentification data from the client verifies and then acknowledges it back.Code:// Receiving the AUTH packet from the client ulErr = recv( cliSock, dp.pktStr, sizeof(PACKET), 0 ); if ( ulErr == SOCKET_ERROR ) { // Handle the error using 'WSAGetLastError()' } if ( dp.pkt.pktHdr.sService == AUTH ) { // Verify the authentication data // Send the ACK if verified; else close the socket dp.pkt.pktHdr.ulHandle = 1; // issuing a new handle dp.pkt.pktHdr.sService = ACK; // acknowledging the authentication code dp.pkt.pktHdr.ulLen = 0; // Not sending any data ulErr = send( cliSock, dp.pktStr, sizeof(PACKET_HEADER) + dp.pkt.pktHdr.ulLen, 0 ); if ( ulErr == SOCKET_ERROR ) { // Handle the error using 'WSAGetLastError()' } } else { // Client is not authentated closesocket( cliSock ); }
The code below shows what can be the continuation on the client side.
From the above client/server interaction code, it should be clear how structures and unions can be used to pass data and information pertaining to them. Implementing the usage of variable headers won't be difficult once you know the basics. All you need is a different header structure (with a unique first field) and an additional field in the 'union'.Code:// Recving the acknowledge packet from the server ulErr = recv( cliSock, dp.pktStr, sizeof(PACKET), 0 ); if ( ulErr == SOCKET_ERROR ) { // Handle the error using 'WSAGetLastError()' } if ( dp.pkt.pktHdr.sService == ACK ) { ulCliHandle = dp.pkt.pktHdr.ulHandle; } else { // Wrong service code } // ...
Note: Another thing you have to keep in mind while dealing with structures is that 'sizeof()' may report a size different from the actual size of all the elements of the structure. This is due to the structure alignment done by the compiler for optimization. You can work around this by using the '#pragma pack(1)'...'#pragma pack()' compiler directive.
Thanks to Mr. Andreas Masur for his help with this FAQ.



Bookmarks